ProcessAsaasWebhookJob.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. <?php
  2. namespace App\Jobs;
  3. use App\Enums\ReceivableStatus;
  4. use App\Models\FranchiseeAccountReceive;
  5. use App\Models\StudentContractInstallment;
  6. use Illuminate\Bus\Queueable;
  7. use Illuminate\Contracts\Queue\ShouldQueue;
  8. use Illuminate\Foundation\Bus\Dispatchable;
  9. use Illuminate\Queue\InteractsWithQueue;
  10. use Illuminate\Queue\SerializesModels;
  11. use Illuminate\Support\Facades\Log;
  12. class ProcessAsaasWebhookJob implements ShouldQueue
  13. {
  14. use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  15. private const EVENT_STATUS_MAP = [
  16. 'PAYMENT_RECEIVED' => ReceivableStatus::PAID,
  17. 'PAYMENT_CONFIRMED' => ReceivableStatus::PAID,
  18. 'PAYMENT_OVERDUE' => ReceivableStatus::OVERDUE,
  19. 'PAYMENT_DELETED' => ReceivableStatus::CANCELLED,
  20. 'PAYMENT_REFUNDED' => ReceivableStatus::CANCELLED,
  21. ];
  22. public array $payload;
  23. public function __construct(array $payload)
  24. {
  25. $this->payload = $payload;
  26. }
  27. public function handle(): void
  28. {
  29. $event = $this->payload['event'] ?? null;
  30. $payment = $this->payload['payment'] ?? [];
  31. if (!$event || empty($payment)) {
  32. Log::warning('Asaas Webhook Job: payload incompleto', $this->payload);
  33. return;
  34. }
  35. $newStatus = self::EVENT_STATUS_MAP[$event] ?? null;
  36. if (!$newStatus) {
  37. Log::info("Asaas Webhook Job: evento '{$event}' ignorado (não mapeado).");
  38. return;
  39. }
  40. $externalReference = $payment['externalReference'] ?? null;
  41. $asaasId = $payment['id'] ?? null;
  42. $receive = null;
  43. $type = 'unknown';
  44. if ($externalReference) {
  45. // Se for um recebível de Franquia
  46. if (str_starts_with($externalReference, 'TBR_')) {
  47. $id = str_replace('TBR_', '', $externalReference);
  48. $receive = FranchiseeAccountReceive::find($id);
  49. $type = 'FranchiseeAccountReceive';
  50. }
  51. // Se for parcela de aluno
  52. elseif (str_starts_with($externalReference, 'STU_')) {
  53. $id = str_replace('STU_', '', $externalReference);
  54. $receive = StudentContractInstallment::find($id);
  55. $type = 'StudentContractInstallment';
  56. }
  57. // Backward compatibility para faturas de teste geradas antes do prefixo
  58. elseif (is_numeric($externalReference)) {
  59. $receive = FranchiseeAccountReceive::find($externalReference);
  60. $type = 'FranchiseeAccountReceive (legacy)';
  61. }
  62. }
  63. // Fallback por asaas_id caso externalReference venha nulo
  64. if (!$receive && $asaasId) {
  65. $receive = FranchiseeAccountReceive::where('asaas_id', $asaasId)->first();
  66. if ($receive) {
  67. $type = 'FranchiseeAccountReceive (by asaas_id)';
  68. } else {
  69. $receive = StudentContractInstallment::where('asaas_id', $asaasId)->first();
  70. if ($receive) {
  71. $type = 'StudentContractInstallment (by asaas_id)';
  72. }
  73. }
  74. }
  75. if (!$receive) {
  76. Log::warning("Asaas Webhook Job: registro não encontrado", [
  77. 'externalReference' => $externalReference,
  78. 'asaas_id' => $asaasId,
  79. 'event' => $event,
  80. ]);
  81. return;
  82. }
  83. // Conversão de status: se a model usa string (como StudentContractInstallment)
  84. // Precisamos comparar de forma segura
  85. $currentStatus = $receive->status instanceof ReceivableStatus ? $receive->status->value : $receive->status;
  86. $newStatusValue = $newStatus instanceof ReceivableStatus ? $newStatus->value : $newStatus;
  87. // Idempotência
  88. if ($currentStatus === ReceivableStatus::PAID->value && $newStatusValue === ReceivableStatus::PAID->value) {
  89. Log::info("Asaas Webhook Job: registro {$type} #{$receive->id} já está pago. Ignorando duplicata.");
  90. return;
  91. }
  92. $updateData = [
  93. 'status' => $newStatusValue,
  94. 'asaas_status' => $payment['status'] ?? $event,
  95. ];
  96. if ($newStatusValue === ReceivableStatus::PAID->value) {
  97. $updateData['payment_date'] = $payment['paymentDate'] ?? $payment['confirmedDate'] ?? now();
  98. // A tabela do Aluno usa 'paid_value', a tabela da Franquia também.
  99. $updateData['paid_value'] = $payment['value'] ?? $receive->value;
  100. }
  101. $receive->update($updateData);
  102. Log::info("Asaas Webhook Job: {$type} #{$receive->id} atualizado para '{$newStatusValue}'", [
  103. 'event' => $event,
  104. 'asaas_id' => $asaasId,
  105. ]);
  106. }
  107. }