| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- <?php
- namespace App\Jobs;
- use App\Enums\ReceivableStatus;
- use App\Models\FranchiseeAccountReceive;
- use Illuminate\Bus\Queueable;
- use Illuminate\Contracts\Queue\ShouldQueue;
- use Illuminate\Foundation\Bus\Dispatchable;
- use Illuminate\Queue\InteractsWithQueue;
- use Illuminate\Queue\SerializesModels;
- use Illuminate\Support\Facades\Log;
- class ProcessAsaasWebhookJob implements ShouldQueue
- {
- use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- private const EVENT_STATUS_MAP = [
- 'PAYMENT_RECEIVED' => ReceivableStatus::PAID,
- 'PAYMENT_CONFIRMED' => ReceivableStatus::PAID,
- 'PAYMENT_OVERDUE' => ReceivableStatus::OVERDUE,
- 'PAYMENT_DELETED' => ReceivableStatus::CANCELLED,
- 'PAYMENT_REFUNDED' => ReceivableStatus::CANCELLED,
- ];
- public array $payload;
- public function __construct(array $payload)
- {
- $this->payload = $payload;
- }
- public function handle(): void
- {
- $event = $this->payload['event'] ?? null;
- $payment = $this->payload['payment'] ?? [];
- if (!$event || empty($payment)) {
- Log::warning('Asaas Webhook Job: payload incompleto', $this->payload);
- return;
- }
- $newStatus = self::EVENT_STATUS_MAP[$event] ?? null;
- if (!$newStatus) {
- Log::info("Asaas Webhook Job: evento '{$event}' ignorado (não mapeado).");
- return;
- }
- $externalReference = $payment['externalReference'] ?? null;
- $asaasId = $payment['id'] ?? null;
- // Cobrança de aluno (parcela) cobrada pela conta Asaas PRÓPRIA da unidade.
- if ($externalReference && str_starts_with($externalReference, 'STU_')) {
- $this->handleStudentInstallment($externalReference, $payment, $event, $newStatus);
- return;
- }
- $receive = null;
- $type = 'unknown';
- if ($externalReference) {
- // Recebível de Franquia (TBR) cobrado pela conta da Matriz.
- if (str_starts_with($externalReference, 'TBR_')) {
- $id = str_replace('TBR_', '', $externalReference);
- $receive = FranchiseeAccountReceive::find($id);
- $type = 'FranchiseeAccountReceive';
- }
- // Backward compatibility para faturas de teste geradas antes do prefixo
- elseif (is_numeric($externalReference)) {
- $receive = FranchiseeAccountReceive::find($externalReference);
- $type = 'FranchiseeAccountReceive (legacy)';
- }
- }
- // Fallback por asaas_id caso externalReference venha nulo
- if (!$receive && $asaasId) {
- $receive = FranchiseeAccountReceive::where('asaas_id', $asaasId)->first();
- if ($receive) {
- $type = 'FranchiseeAccountReceive (by asaas_id)';
- }
- }
- if (!$receive) {
- Log::warning("Asaas Webhook Job: registro não encontrado", [
- 'externalReference' => $externalReference,
- 'asaas_id' => $asaasId,
- 'event' => $event,
- ]);
- return;
- }
- // Comparação segura de status (a model pode expor enum ou string).
- $currentStatus = $receive->status instanceof ReceivableStatus ? $receive->status->value : $receive->status;
- $newStatusValue = $newStatus instanceof ReceivableStatus ? $newStatus->value : $newStatus;
- // Idempotência
- if ($currentStatus === ReceivableStatus::PAID->value && $newStatusValue === ReceivableStatus::PAID->value) {
- Log::info("Asaas Webhook Job: registro {$type} #{$receive->id} já está pago. Ignorando duplicata.");
- return;
- }
- $updateData = [
- 'status' => $newStatusValue,
- 'asaas_status' => $payment['status'] ?? $event,
- ];
- if ($newStatusValue === ReceivableStatus::PAID->value) {
- $updateData['payment_date'] = $payment['paymentDate'] ?? $payment['confirmedDate'] ?? now();
- $updateData['paid_value'] = $payment['value'] ?? $receive->value;
- }
- $receive->update($updateData);
- // Reflete a baixa na Conta a Pagar da unidade quando o TBR é confirmado
- // pago via Asaas (mantém o espelho em dia sem baixa manual). A baixa manual
- // de cada lado continua independente para os demais casos.
- if ($newStatusValue === ReceivableStatus::PAID->value) {
- $payable = \App\Models\UnitAccountPayable::where('franchisee_account_receive_id', $receive->id)
- ->whereNotIn('status', ['paid', 'cancelled'])
- ->first();
- if ($payable) {
- $payable->update([
- 'status' => 'paid',
- 'paid_value' => $payable->value,
- 'payment_date' => $updateData['payment_date'] ?? now(),
- ]);
- Log::info("Asaas Webhook Job: Conta a Pagar da unidade #{$payable->id} baixada automaticamente (TBR pago).");
- }
- }
- Log::info("Asaas Webhook Job: {$type} #{$receive->id} atualizado para '{$newStatusValue}'", [
- 'event' => $event,
- 'asaas_id' => $asaasId,
- ]);
- }
- /**
- * Baixa/atualiza a parcela do aluno cobrada pela conta Asaas da unidade.
- */
- private function handleStudentInstallment(string $externalReference, array $payment, string $event, ReceivableStatus $newStatus): void
- {
- $id = str_replace('STU_', '', $externalReference);
- $installment = \App\Models\StudentContractInstallment::find($id);
- if (!$installment) {
- Log::warning("Asaas Webhook Job: parcela {$externalReference} não encontrada.", [
- 'event' => $event,
- 'asaas_id' => $payment['id'] ?? null,
- ]);
- return;
- }
- // Mapeia o status do recebível para o vocabulário das parcelas.
- $statusMap = [
- ReceivableStatus::PAID->value => 'paid',
- ReceivableStatus::OVERDUE->value => 'overdue',
- ReceivableStatus::CANCELLED->value => 'cancelled',
- ];
- $installmentStatus = $statusMap[$newStatus->value] ?? $installment->status;
- // Idempotência: já pago e continua pago → ignora duplicata.
- if ($installment->status === 'paid' && $installmentStatus === 'paid') {
- Log::info("Asaas Webhook Job: parcela #{$installment->id} já paga. Ignorando duplicata.");
- return;
- }
- $update = [
- 'status' => $installmentStatus,
- 'asaas_status' => $payment['status'] ?? $event,
- ];
- if ($installmentStatus === 'paid') {
- $update['payment_date'] = $payment['paymentDate'] ?? $payment['confirmedDate'] ?? now();
- $update['paid_value'] = $payment['value'] ?? $installment->value;
- }
- $installment->update($update);
- Log::info("Asaas Webhook Job: parcela #{$installment->id} atualizada para '{$installmentStatus}'.", [
- 'event' => $event,
- 'asaas_id' => $payment['id'] ?? null,
- ]);
- }
- }
|