PagarmeCustomerService.php 8.6 KB

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