PagarmeRecipientService.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. namespace App\Services\Pagarme;
  3. use App\Models\Provider;
  4. use Carbon\Carbon;
  5. use Illuminate\Support\Facades\Http;
  6. use Illuminate\Support\Facades\Log;
  7. use Illuminate\Support\Str;
  8. class PagarmeRecipientService
  9. {
  10. public function createRecipientForProvider(Provider $provider, array $data): string
  11. {
  12. if (! empty($provider->recipient_id)) {
  13. return $provider->recipient_id;
  14. }
  15. $bankAccountData = $data['recipient_default_bank_account'];
  16. $bankAccountData['holder_name'] = $this->normalizeBankAccountHolderName($bankAccountData['holder_name']);
  17. $metadata = $data['recipient_metadata'] ?? [];
  18. $paymentMode = $data['recipient_payment_mode'];
  19. $recipientType = $data['recipient_type'] ?? 'individual';
  20. $addressParts = $this->extractAddressParts($data);
  21. $monthlyIncome = isset($data['monthly_income']) ? (int) $data['monthly_income'] : 1000;
  22. $occupation = $data['professional_occupation'] ?? 'autonomo';
  23. $payload = $this->filterFilledRecursive([
  24. 'code' => preg_replace('/\D+/', '', $data['recipient_code']),
  25. 'register_information' => [
  26. 'name' => $data['recipient_name'],
  27. 'email' => $data['recipient_email'],
  28. 'document' => preg_replace('/\D+/', '', $data['recipient_document']),
  29. 'type' => $recipientType,
  30. 'birthdate' => $this->formatBirthdate($data['birth_date'] ?? null),
  31. 'monthly_income' => $monthlyIncome,
  32. 'professional_occupation' => $occupation,
  33. 'phone_numbers' => $this->buildPhoneNumbers($data['phone'] ?? null),
  34. 'address' => [
  35. 'street' => $data['address'],
  36. 'complementary' => $addressParts['complementary'],
  37. 'street_number' => $addressParts['street_number'],
  38. 'neighborhood' => $addressParts['neighborhood'],
  39. 'city' => $data['city'] ?? null,
  40. 'state' => $data['state'] ?? null,
  41. 'zip_code' => preg_replace('/\D+/', '', $data['zip_code']),
  42. 'reference_point' => $addressParts['reference_point'],
  43. ],
  44. ],
  45. 'default_bank_account' => [
  46. 'holder_name' => $bankAccountData['holder_name'],
  47. 'holder_type' => $bankAccountData['holder_type'],
  48. 'holder_document' => preg_replace('/\D+/', '', $bankAccountData['holder_document']),
  49. 'bank' => $bankAccountData['bank'],
  50. 'branch_number' => $bankAccountData['branch_number'],
  51. 'branch_check_digit' => $bankAccountData['branch_check_digit'] ?? null,
  52. 'account_number' => $bankAccountData['account_number'],
  53. 'account_check_digit' => $bankAccountData['account_check_digit'],
  54. 'type' => $bankAccountData['type'],
  55. ],
  56. 'transfer_settings' => [
  57. 'transfer_enabled' => false,
  58. 'transfer_interval' => 'Daily',
  59. 'transfer_day' => 0,
  60. ],
  61. 'automatic_anticipation_settings' => [
  62. 'enabled' => false,
  63. ],
  64. ]);
  65. $response = $this->pagarmeRequest($provider->id)
  66. ->post($this->pagarmeUrl('/recipients'), $payload);
  67. if ($response->failed()) {
  68. Log::channel('pagarme')->error('Pagar.me recipient creation failed', [
  69. 'status' => $response->status(),
  70. 'body' => $response->json() ?? $response->body(),
  71. 'payload' => $payload,
  72. ]);
  73. throw new \RuntimeException('Erro ao criar recebedor no Pagar.me.');
  74. }
  75. $recipientData = $response->json();
  76. $recipientId = $recipientData['id'] ?? null;
  77. if (! $recipientId) {
  78. Log::channel('pagarme')->error('Pagar.me recipient creation returned empty id', [
  79. 'response' => $recipientData,
  80. ]);
  81. throw new \RuntimeException('Pagar.me recipient creation returned an empty id.');
  82. }
  83. $provider->forceFill([
  84. 'recipient_id' => $recipientId,
  85. 'recipient_name' => $data['recipient_name'],
  86. 'recipient_email' => $data['recipient_email'],
  87. 'recipient_description' => $data['recipient_description'],
  88. 'recipient_document' => $data['recipient_document'],
  89. 'recipient_type' => $recipientType,
  90. 'recipient_code' => $data['recipient_code'],
  91. 'recipient_payment_mode' => $paymentMode,
  92. 'recipient_default_bank_account' => $bankAccountData,
  93. 'recipient_transfer_settings' => [
  94. 'transfer_enabled' => false,
  95. 'transfer_interval' => 'daily',
  96. 'transfer_day' => 0,
  97. ],
  98. 'recipient_automatic_anticipation_settings' => [
  99. 'enabled' => false,
  100. ],
  101. 'recipient_metadata' => $metadata,
  102. ])->save();
  103. return $recipientId;
  104. }
  105. //
  106. private function pagarmeUrl(string $path): string
  107. {
  108. return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
  109. }
  110. private function idempotencyKey(int $providerId, string $suffix = 'recipient'): string
  111. {
  112. return "provider-{$providerId}-{$suffix}";
  113. }
  114. //
  115. private function buildPhoneNumbers(?string $phone): array
  116. {
  117. $digits = preg_replace('/\D+/', '', (string) $phone) ?? '';
  118. if (strlen($digits) < 10) {
  119. return [[
  120. 'ddd' => '11',
  121. 'number' => '999999999',
  122. 'type' => 'mobile',
  123. ]];
  124. }
  125. if (str_starts_with($digits, '55')) {
  126. $digits = substr($digits, 2);
  127. }
  128. $areaCode = substr($digits, 0, 2);
  129. $number = substr($digits, 2);
  130. return [[
  131. 'ddd' => $areaCode,
  132. 'number' => $number,
  133. 'type' => 'mobile',
  134. ]];
  135. }
  136. private function extractAddressParts(array $data): array
  137. {
  138. $addressLine = trim((string) ($data['address'] ?? ''));
  139. $segments = array_map('trim', explode(',', $addressLine));
  140. $streetSegment = $segments[0] ?? '';
  141. $streetNumber = $data['number'] ?? null;
  142. $neighborhood = $data['district'] ?? null;
  143. $referencePoint = $data['reference_point'] ?? null;
  144. $complementary = $data['complement'] ?? null;
  145. if ($streetNumber === null) {
  146. preg_match('/^(\d+)/', $streetSegment, $matches);
  147. $streetNumber = $matches[1] ?? 'S/N';
  148. }
  149. if ($neighborhood === null) {
  150. $neighborhood = $segments[1] ?? 'N/A';
  151. }
  152. if ($referencePoint === null) {
  153. $referencePoint = 'N/A';
  154. }
  155. if ($complementary === null) {
  156. $complementary = 'N/A';
  157. }
  158. return [
  159. 'street_number' => (string) $streetNumber,
  160. 'neighborhood' => (string) $neighborhood,
  161. 'reference_point' => (string) $referencePoint,
  162. 'complementary' => (string) $complementary,
  163. ];
  164. }
  165. //
  166. private function filterFilledRecursive(array $data): array
  167. {
  168. $filtered = [];
  169. foreach ($data as $key => $value) {
  170. if (is_array($value)) {
  171. $value = $this->filterFilledRecursive($value);
  172. }
  173. if ($value !== null && $value !== '' && $value !== []) {
  174. $filtered[$key] = $value;
  175. }
  176. }
  177. return $filtered;
  178. }
  179. private function formatBirthdate(mixed $birthdate): ?string
  180. {
  181. if ($birthdate === null || $birthdate === '') {
  182. return null;
  183. }
  184. if ($birthdate instanceof \DateTimeInterface) {
  185. return Carbon::instance($birthdate)->format('d/m/Y');
  186. }
  187. $birthdate = trim((string) $birthdate);
  188. if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $birthdate) === 1) {
  189. return $birthdate;
  190. }
  191. if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $birthdate) === 1) {
  192. return Carbon::createFromFormat('Y-m-d', $birthdate)->format('d/m/Y');
  193. }
  194. return Carbon::parse($birthdate)->format('d/m/Y');
  195. }
  196. private function normalizeBankAccountHolderName(string $holderName): string
  197. {
  198. $holderName = trim(preg_replace('/\s+/', ' ', $holderName) ?? '');
  199. if (Str::length($holderName) < 30) {
  200. return $holderName;
  201. }
  202. $parts = explode(' ', $holderName);
  203. if (count($parts) >= 3) {
  204. $firstName = array_shift($parts);
  205. $lastName = array_pop($parts);
  206. $initials = array_map(
  207. static fn (string $part): string => Str::upper(Str::substr($part, 0, 1)),
  208. $parts
  209. );
  210. $abbreviated = trim($firstName.' '.implode(' ', $initials).' '.$lastName);
  211. if (Str::length($abbreviated) < 30) {
  212. return $abbreviated;
  213. }
  214. $firstAndLast = trim($firstName.' '.$lastName);
  215. if (Str::length($firstAndLast) < 30) {
  216. return $firstAndLast;
  217. }
  218. }
  219. return Str::limit($holderName, 29, '');
  220. }
  221. //
  222. private function pagarmeRequest(int $providerId, string $suffix = 'recipient')
  223. {
  224. $secretKey = config('services.pagarme.secret_key');
  225. if (empty($secretKey)) {
  226. Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
  227. throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
  228. }
  229. return Http::withBasicAuth($secretKey, '')
  230. ->withHeaders([
  231. 'Idempotency-Key' => $this->idempotencyKey($providerId, $suffix),
  232. 'Content-Type' => 'application/json',
  233. 'Accept' => 'application/json',
  234. ]);
  235. }
  236. }