AsaasAccountService.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. <?php
  2. namespace App\Services\Integrations\Asaas;
  3. use App\Exceptions\AsaasException;
  4. use App\Models\Unit;
  5. use App\Models\UnitPaymentAccount;
  6. use Exception;
  7. use Illuminate\Support\Facades\Log;
  8. class AsaasAccountService
  9. {
  10. protected AsaasClient $client;
  11. public function __construct(AsaasClient $client)
  12. {
  13. $this->client = $client;
  14. }
  15. public function ensureSubaccount(Unit $unit): UnitPaymentAccount
  16. {
  17. $existingAccount = UnitPaymentAccount::where('unit_id', $unit->id)->first();
  18. if ($existingAccount && $existingAccount->asaas_account_id) {
  19. return $existingAccount;
  20. }
  21. if (empty($unit->cnpj)) {
  22. throw new Exception("A unidade {$unit->fantasy_name} não possui um CNPJ cadastrado. O CNPJ é obrigatório para criar a subconta.");
  23. }
  24. // Garante a chave de criptografia ANTES de chamar o Asaas. Sem APP_KEY, o
  25. // POST cria a subconta lá, mas o save local falha ao encriptar a apiKey
  26. // (cast 'encrypted') -> subconta órfã: apiKey perdida e e-mail/CNPJ travados.
  27. if (empty(config('app.key'))) {
  28. throw new Exception("APP_KEY não configurada. Abortando antes de criar a subconta no Asaas para evitar conta órfã (Unidade {$unit->id}).");
  29. }
  30. $payload = [
  31. 'name' => $unit->fantasy_name ?? $unit->social_reason ?? 'Unidade Ginástica do Cérebro',
  32. 'email' => $unit->email,
  33. 'cpfCnpj' => preg_replace('/[^0-9]/', '', $unit->cnpj),
  34. 'mobilePhone' => preg_replace('/[^0-9]/', '', $unit->cell_number ?? $unit->phone_number ?? ''),
  35. 'address' => $unit->street,
  36. 'addressNumber' => $unit->address_number ?? 'S/N',
  37. 'province' => $unit->neighborhood,
  38. 'postalCode' => preg_replace('/[^0-9]/', '', $unit->postal_code),
  39. 'companyType' => 'LIMITED',
  40. 'incomeValue' => 10000.00, // Renda/Faturamento exigido pelo Asaas
  41. ];
  42. try {
  43. $response = $this->client->post('/accounts', $payload);
  44. } catch (AsaasException $e) {
  45. // Colisão de e-mail (400): diagnostica se a conta é SUA (órfã, recuperável
  46. // só pelo painel) ou EXTERNA (e-mail em uso fora do seu parent -> trocar e-mail).
  47. if ($e->getCode() === 400 && str_contains($e->getMessage(), 'já está em uso')) {
  48. $this->logEmailCollision($unit);
  49. }
  50. throw $e;
  51. }
  52. return UnitPaymentAccount::updateOrCreate(
  53. ['unit_id' => $unit->id],
  54. [
  55. 'asaas_account_id' => $response['id'],
  56. 'asaas_wallet_id' => $response['walletId'],
  57. // Cast 'encrypted' do model encripta no save; e SyncStudentChargeJob
  58. // espera mais uma camada manual. Mantém o encryptString para casar.
  59. 'asaas_api_key' => \Illuminate\Support\Facades\Crypt::encryptString($response['apiKey']),
  60. 'status' => 'ACTIVE',
  61. 'onboarded_at' => now(),
  62. ]
  63. );
  64. }
  65. /**
  66. * Loga, no erro de e-mail em uso, se a conta colidente pertence ao seu parent
  67. * (subconta órfã) ou é externa — para acelerar o diagnóstico.
  68. */
  69. private function logEmailCollision(Unit $unit): void
  70. {
  71. try {
  72. $accounts = $this->client->get('/accounts', ['email' => $unit->email])['data'] ?? [];
  73. if (count($accounts) > 0) {
  74. Log::warning("Asaas: e-mail {$unit->email} já é uma SUBCONTA SUA (órfã). Recuperável só pelo painel/encerramento.", [
  75. 'unit_id' => $unit->id,
  76. 'asaas_account_id' => $accounts[0]['id'] ?? null,
  77. 'wallet_id' => $accounts[0]['walletId'] ?? null,
  78. ]);
  79. } else {
  80. Log::warning("Asaas: e-mail {$unit->email} está em uso em conta EXTERNA (fora do seu parent). Cadastre a unidade com outro e-mail.", [
  81. 'unit_id' => $unit->id,
  82. ]);
  83. }
  84. } catch (\Throwable $t) {
  85. // Diagnóstico é best-effort: nunca mascara o erro original do POST.
  86. }
  87. }
  88. }