PagarmeCustomerService.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <?php
  2. namespace App\Services\Pagarme;
  3. use App\Models\Client;
  4. use Illuminate\Support\Facades\Http;
  5. use Illuminate\Support\Facades\Log;
  6. class PagarmeCustomerService
  7. {
  8. public function createCustomerForClient(Client $client, array $data): ?string
  9. {
  10. if (! empty($client->external_customer_id)) {
  11. return $client->external_customer_id;
  12. }
  13. $client->loadMissing('user');
  14. $name = $client->user?->name ?? $data['name'] ?? 'Cliente';
  15. $email = $client->user?->email ?? $data['email'] ?? null;
  16. $document = $this->sanitizeDigits($client->document ?? $data['document'] ?? null);
  17. if (empty($email) || empty($document)) {
  18. Log::channel('pagarme')->warning(
  19. 'Skipping customer creation because the client is missing email or document.',
  20. [
  21. 'client_id' => $client->id,
  22. 'user_id' => $client->user_id,
  23. ]
  24. );
  25. return null;
  26. }
  27. $address = $this->buildAddress($data);
  28. $phones = $this->buildPhones($client->user?->phone ?? $data['phone'] ?? null);
  29. $code = $client->external_customer_code ?? "client-{$client->id}";
  30. $payload = $this->filterFilledRecursive([
  31. 'name' => $name,
  32. 'email' => $email,
  33. 'document' => $document,
  34. 'type' => $this->personType($document),
  35. 'document_type' => $this->documentType($document),
  36. 'code' => $code,
  37. 'address' => $address,
  38. ]);
  39. if (! empty($phones)) {
  40. $payload['phones'] = $phones;
  41. }
  42. $endpoint = $this->pagarmeUrl('/customers');
  43. PagarmeHttpLogger::logRequest('POST', $endpoint, $payload);
  44. $response = $this->pagarmeRequest($client->id)
  45. ->post($endpoint, $payload);
  46. if ($response->failed()) {
  47. Log::channel('pagarme')->error('Pagar.me customer creation failed', [
  48. 'status' => $response->status(),
  49. 'body' => $response->json() ?? $response->body(),
  50. 'payload' => $payload,
  51. ]);
  52. throw new \RuntimeException('Erro ao criar cliente no Pagar.me.');
  53. }
  54. $customerData = $response->json();
  55. $customerId = $customerData['id'] ?? null;
  56. if (! $customerId) {
  57. Log::channel('pagarme')->error('Customer creation returned an empty id.', [
  58. 'client_id' => $client->id,
  59. 'response' => $customerData,
  60. ]);
  61. throw new \RuntimeException('Customer creation returned an empty id.');
  62. }
  63. $client->forceFill([
  64. 'external_customer_id' => $customerId,
  65. 'external_customer_code' => $code,
  66. ])->save();
  67. return $customerId;
  68. }
  69. //
  70. public function updateCustomer(string $customerId, int $clientId, array $data): void
  71. {
  72. $payload = $this->filterFilledRecursive([
  73. 'name' => $data['name'] ?? null,
  74. 'email' => $data['email'] ?? null,
  75. 'document' => $this->sanitizeDigits($data['document'] ?? null),
  76. 'type' => $data['type'] ?? null,
  77. 'document_type' => $data['document_type'] ?? null,
  78. 'code' => $data['code'] ?? null,
  79. 'address' => $data['address'] ?? null,
  80. 'phones' => $data['phones'] ?? null,
  81. ]);
  82. if (empty($payload)) {
  83. return;
  84. }
  85. $endpoint = $this->pagarmeUrl("/customers/{$customerId}");
  86. PagarmeHttpLogger::logRequest('PATCH', $endpoint, $payload);
  87. $request = $this->pagarmeRequest($clientId, "customer-update-{$customerId}");
  88. $response = $request->patch($endpoint, $payload);
  89. if ($response->status() === 405) {
  90. PagarmeHttpLogger::logRequest('PUT', $endpoint, $payload);
  91. $response = $request->put($endpoint, $payload);
  92. }
  93. if ($response->failed()) {
  94. Log::channel('pagarme')->error('Pagar.me customer update failed', [
  95. 'status' => $response->status(),
  96. 'body' => $response->json() ?? $response->body(),
  97. 'payload' => $payload,
  98. 'customer_id' => $customerId,
  99. 'client_id' => $clientId,
  100. ]);
  101. throw new \RuntimeException('Erro ao atualizar cliente no Pagar.me.');
  102. }
  103. }
  104. //
  105. private function idempotencyKey(int $clientId, string $suffix = 'customer'): string
  106. {
  107. return "client-{$clientId}-{$suffix}";
  108. }
  109. private function pagarmeRequest(int $clientId, string $suffix = 'customer')
  110. {
  111. $secretKey = config('services.pagarme.secret_key');
  112. if (empty($secretKey)) {
  113. Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
  114. throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
  115. }
  116. return Http::withBasicAuth($secretKey, '')
  117. ->withHeaders([
  118. 'Idempotency-Key' => $this->idempotencyKey($clientId, $suffix),
  119. 'Content-Type' => 'application/json',
  120. 'Accept' => 'application/json',
  121. ]);
  122. }
  123. private function pagarmeUrl(string $path): string
  124. {
  125. return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
  126. }
  127. //
  128. private function buildAddress(array $data): array
  129. {
  130. $zipCode = $this->sanitizeDigits($data['zip_code'] ?? null);
  131. $street = (string) ($data['address'] ?? '');
  132. $number = (string) ($data['number'] ?? '0');
  133. $neighborhood = (string) ($data['district'] ?? '');
  134. $city = (string) ($data['city'] ?? '');
  135. $state = (string) ($data['state'] ?? '');
  136. $country = (string) ($data['country'] ?? 'BR');
  137. $complement = (string) ($data['complement'] ?? '');
  138. $line1Parts = array_filter([$number, $street, $neighborhood], static fn ($value) => $value !== '');
  139. $line1 = implode(', ', $line1Parts);
  140. $line2 = $complement ?: (string) ($data['instructions'] ?? '');
  141. return [
  142. 'line_1' => $line1,
  143. 'line_2' => $line2,
  144. 'zip_code' => $zipCode,
  145. 'city' => $city,
  146. 'state' => $state,
  147. 'country' => $country,
  148. ];
  149. }
  150. private function buildPhones(?string $phone): array
  151. {
  152. $digits = $this->sanitizeDigits($phone);
  153. if (empty($digits)) {
  154. return [];
  155. }
  156. $countryCode = '55';
  157. $areaCode = substr($digits, 0, 2);
  158. $number = substr($digits, 2);
  159. if (strlen($digits) <= 2) {
  160. $areaCode = '';
  161. $number = $digits;
  162. }
  163. return [
  164. 'mobile_phone' => [
  165. 'country_code' => $countryCode,
  166. 'area_code' => $areaCode,
  167. 'number' => $number,
  168. ],
  169. ];
  170. }
  171. private function documentType(string $document): string
  172. {
  173. return strlen($document) === 14 ? 'CNPJ' : 'CPF';
  174. }
  175. private function filterFilledRecursive(array $data): array
  176. {
  177. $filtered = [];
  178. foreach ($data as $key => $value) {
  179. if (is_array($value)) {
  180. $value = $this->filterFilledRecursive($value);
  181. }
  182. if ($value !== null && $value !== '' && $value !== []) {
  183. $filtered[$key] = $value;
  184. }
  185. }
  186. return $filtered;
  187. }
  188. private function personType(string $document): string
  189. {
  190. return strlen($document) === 14 ? 'company' : 'individual';
  191. }
  192. private function sanitizeDigits(?string $value): string
  193. {
  194. return preg_replace('/\D+/', '', (string) $value) ?? '';
  195. }
  196. }