PagarmeRecipientService.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <?php
  2. namespace App\Services\Pagarme;
  3. use App\Data\Pagarme\Request\PagarmeBankAccountUpdateRequestData;
  4. use App\Data\Pagarme\Request\PagarmeRecipientRequestData\PagarmeRecipientAddressData;
  5. use App\Data\Pagarme\Request\PagarmeRecipientRequestData\PagarmeRecipientAutomaticAnticipationSettingsData;
  6. use App\Data\Pagarme\Request\PagarmeRecipientRequestData\PagarmeRecipientBankAccountData;
  7. use App\Data\Pagarme\Request\PagarmeRecipientRequestData\PagarmeRecipientPhoneData;
  8. use App\Data\Pagarme\Request\PagarmeRecipientRequestData\PagarmeRecipientPhoneNumbersData;
  9. use App\Data\Pagarme\Request\PagarmeRecipientRequestData\PagarmeRecipientRegisterInformationData;
  10. use App\Data\Pagarme\Request\PagarmeRecipientRequestData\PagarmeRecipientRequestData;
  11. use App\Data\Pagarme\Request\PagarmeRecipientRequestData\PagarmeRecipientTransferSettingsData;
  12. use App\Data\Pagarme\Response\PagarmeRecipientResponseData\PagarmeRecipientBankAccountResponseData;
  13. use App\Data\Pagarme\Response\PagarmeRecipientResponseData\PagarmeRecipientResponseData;
  14. use App\Models\Provider;
  15. use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
  16. use Carbon\Carbon;
  17. use Illuminate\Support\Str;
  18. class PagarmeRecipientService
  19. {
  20. use SendsPagarmeRequests;
  21. public function createRecipientForProvider(Provider $provider, array $data): string
  22. {
  23. if (! empty($provider->recipient_id)) {
  24. return $provider->recipient_id;
  25. }
  26. $metadata = $data['recipient_metadata'] ?? [];
  27. $paymentMode = $data['recipient_payment_mode'];
  28. $recipientCode = $this->ensureRecipientCode($provider);
  29. $addressParts = $this->extractAddressParts($data);
  30. $registerInformation = new PagarmeRecipientRegisterInformationData(
  31. name: $data['recipient_name'],
  32. email: $data['recipient_email'],
  33. document: $this->onlyDigits($data['recipient_document'] ?? null),
  34. type: $data['recipient_type'] ?? 'individual',
  35. birthdate: $this->formatBirthdate($data['birth_date'] ?? null),
  36. monthlyIncome: isset($data['monthly_income']) ? (int) $data['monthly_income'] : 1000,
  37. professionalOccupation: $data['professional_occupation'] ?? 'autonomo',
  38. phoneNumbers: new PagarmeRecipientPhoneNumbersData(
  39. $this->buildRecipientPhone($data['phone'] ?? null),
  40. ),
  41. address: new PagarmeRecipientAddressData(
  42. street: $data['address'],
  43. complementary: $addressParts['complementary'],
  44. streetNumber: $addressParts['street_number'],
  45. neighborhood: $addressParts['neighborhood'],
  46. city: $data['city'] ?? null,
  47. state: $data['state'] ?? null,
  48. zipCode: $this->onlyDigits($data['zip_code'] ?? null),
  49. referencePoint: $addressParts['reference_point'],
  50. ),
  51. );
  52. $defaultBankAccount = $this->buildRecipientBankAccount(
  53. $data['recipient_default_bank_account'],
  54. );
  55. $payload = new PagarmeRecipientRequestData(
  56. code: $recipientCode,
  57. registerInformation: $registerInformation,
  58. defaultBankAccount: $defaultBankAccount,
  59. transferSettings: new PagarmeRecipientTransferSettingsData(
  60. transferEnabled: false,
  61. transferInterval: 'Daily',
  62. transferDay: 0,
  63. ),
  64. automaticAnticipationSettings: new PagarmeRecipientAutomaticAnticipationSettingsData(
  65. enabled: false,
  66. ),
  67. );
  68. $bankAccountData = $payload->defaultBankAccount->toArray();
  69. $raw = $this->pagarmeRequest(
  70. method: 'POST',
  71. path: '/recipients',
  72. payload: $payload,
  73. idempotencyKey: $this->idempotencyKey($provider->id),
  74. errorMessage: 'Erro ao criar recebedor no Pagar.me.',
  75. );
  76. $recipientData = $this->buildRecipientResponse($raw);
  77. $recipientId = $recipientData->requireId();
  78. $provider->forceFill([
  79. 'recipient_id' => $recipientId,
  80. 'recipient_name' => $data['recipient_name'],
  81. 'recipient_email' => $data['recipient_email'],
  82. 'recipient_description' => $data['recipient_description'],
  83. 'recipient_document' => $data['recipient_document'],
  84. 'recipient_type' => $payload->registerInformation->type,
  85. 'recipient_code' => $recipientCode,
  86. 'recipient_payment_mode' => $paymentMode,
  87. 'recipient_default_bank_account' => $bankAccountData,
  88. 'recipient_transfer_settings' => [
  89. 'transfer_enabled' => false,
  90. 'transfer_interval' => 'daily',
  91. 'transfer_day' => 0,
  92. ],
  93. 'recipient_automatic_anticipation_settings' => [
  94. 'enabled' => false,
  95. ],
  96. 'recipient_metadata' => $metadata,
  97. ])->save();
  98. return $recipientId;
  99. }
  100. public function updateDefaultBankAccount(Provider $provider, array $bankAccountData): Provider
  101. {
  102. $payload = new PagarmeBankAccountUpdateRequestData(
  103. holderName: $this->normalizeHolderName($bankAccountData['holder_name']),
  104. holderType: $bankAccountData['holder_type'],
  105. holderDocument: $this->onlyDigits($bankAccountData['holder_document']),
  106. bank: $bankAccountData['bank'],
  107. branchNumber: $bankAccountData['branch_number'],
  108. branchCheckDigit: $bankAccountData['branch_check_digit'] ?? null,
  109. accountNumber: $bankAccountData['account_number'],
  110. accountCheckDigit: $bankAccountData['account_check_digit'],
  111. type: $bankAccountData['type'],
  112. );
  113. $raw = $this->pagarmeRequest(
  114. method: 'PATCH',
  115. path: "/recipients/{$provider->recipient_id}/default-bank-account",
  116. payload: $payload,
  117. idempotencyKey: $this->idempotencyKey($provider->id, 'default-bank-account-'.sha1(json_encode($payload->toArray()))),
  118. errorMessage: 'Erro ao atualizar conta bancaria do recebedor no Pagar.me.',
  119. );
  120. $recipientData = $this->buildRecipientResponse($raw);
  121. $provider->forceFill([
  122. 'recipient_default_bank_account' => $recipientData->defaultBankAccount()?->toArray() ?: $payload->toArray()['bank_account'],
  123. ])->save();
  124. return $provider->fresh();
  125. }
  126. //
  127. private function buildRecipientBankAccount(array $data): PagarmeRecipientBankAccountData
  128. {
  129. return new PagarmeRecipientBankAccountData(
  130. holderName: $this->normalizeHolderName($data['holder_name']),
  131. holderType: $data['holder_type'],
  132. holderDocument: $this->onlyDigits($data['holder_document']),
  133. bank: $data['bank'],
  134. branchNumber: $data['branch_number'],
  135. branchCheckDigit: $data['branch_check_digit'] ?? null,
  136. accountNumber: $data['account_number'],
  137. accountCheckDigit: $data['account_check_digit'],
  138. type: $data['type'],
  139. );
  140. }
  141. private function buildRecipientPhone(?string $phone): PagarmeRecipientPhoneData
  142. {
  143. $digits = $this->onlyDigits($phone);
  144. if (strlen($digits) < 10) {
  145. return new PagarmeRecipientPhoneData(
  146. ddd: '11',
  147. number: '999999999',
  148. type: 'mobile',
  149. );
  150. }
  151. if (str_starts_with($digits, '55')) {
  152. $digits = substr($digits, 2);
  153. }
  154. return new PagarmeRecipientPhoneData(
  155. ddd: substr($digits, 0, 2),
  156. number: substr($digits, 2),
  157. type: 'mobile',
  158. );
  159. }
  160. private function extractAddressParts(array $data): array
  161. {
  162. $addressLine = trim((string) ($data['address'] ?? ''));
  163. $segments = array_map('trim', explode(',', $addressLine));
  164. $streetSegment = $segments[0] ?? '';
  165. if (($data['number'] ?? null) === null) {
  166. preg_match('/^(\d+)/', $streetSegment, $matches);
  167. }
  168. return [
  169. 'street_number' => (string) ($data['number'] ?? $matches[1] ?? 'S/N'),
  170. 'neighborhood' => (string) ($data['district'] ?? $segments[1] ?? 'N/A'),
  171. 'reference_point' => (string) ($data['reference_point'] ?? 'N/A'),
  172. 'complementary' => (string) ($data['complement'] ?? 'N/A'),
  173. ];
  174. }
  175. private function formatBirthdate(mixed $birthdate): ?string
  176. {
  177. if ($birthdate === null || $birthdate === '') {
  178. return null;
  179. }
  180. if ($birthdate instanceof \DateTimeInterface) {
  181. return Carbon::instance($birthdate)->format('d/m/Y');
  182. }
  183. $birthdate = trim((string) $birthdate);
  184. if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $birthdate) === 1) {
  185. return $birthdate;
  186. }
  187. if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $birthdate) === 1) {
  188. return Carbon::createFromFormat('Y-m-d', $birthdate)->format('d/m/Y');
  189. }
  190. return Carbon::parse($birthdate)->format('d/m/Y');
  191. }
  192. private function normalizeHolderName(string $holderName): string
  193. {
  194. $holderName = trim(preg_replace('/\s+/', ' ', $holderName) ?? '');
  195. if (Str::length($holderName) < 30) {
  196. return $holderName;
  197. }
  198. $parts = explode(' ', $holderName);
  199. if (count($parts) >= 3) {
  200. $firstName = array_shift($parts);
  201. $lastName = array_pop($parts);
  202. $initials = array_map(
  203. static fn (string $part): string => Str::upper(Str::substr($part, 0, 1)),
  204. $parts
  205. );
  206. $abbreviated = trim($firstName.' '.implode(' ', $initials).' '.$lastName);
  207. if (Str::length($abbreviated) < 30) {
  208. return $abbreviated;
  209. }
  210. $firstAndLast = trim($firstName.' '.$lastName);
  211. if (Str::length($firstAndLast) < 30) {
  212. return $firstAndLast;
  213. }
  214. }
  215. return Str::limit($holderName, 29, '');
  216. }
  217. private function buildRecipientResponse(array $raw): PagarmeRecipientResponseData
  218. {
  219. return new PagarmeRecipientResponseData(
  220. id: $raw['id'] ?? null,
  221. name: $raw['name'] ?? null,
  222. email: $raw['email'] ?? null,
  223. document: $raw['document'] ?? null,
  224. type: $raw['type'] ?? null,
  225. status: $raw['status'] ?? null,
  226. defaultBankAccount: ! empty($raw['default_bank_account'])
  227. ? new PagarmeRecipientBankAccountResponseData(
  228. holderName: $raw['default_bank_account']['holder_name'] ?? null,
  229. holderType: $raw['default_bank_account']['holder_type'] ?? null,
  230. holderDocument: $raw['default_bank_account']['holder_document'] ?? null,
  231. bank: $raw['default_bank_account']['bank'] ?? null,
  232. branchNumber: $raw['default_bank_account']['branch_number'] ?? null,
  233. branchCheckDigit: $raw['default_bank_account']['branch_check_digit'] ?? null,
  234. accountNumber: $raw['default_bank_account']['account_number'] ?? null,
  235. accountCheckDigit: $raw['default_bank_account']['account_check_digit'] ?? null,
  236. type: $raw['default_bank_account']['type'] ?? null,
  237. )
  238. : null,
  239. createdAt: $raw['created_at'] ?? null,
  240. updatedAt: $raw['updated_at'] ?? null,
  241. );
  242. }
  243. private function onlyDigits(?string $value): string
  244. {
  245. return preg_replace('/\D+/', '', (string) $value) ?? '';
  246. }
  247. // evita criacao duplica de recipient
  248. private function idempotencyKey(int $providerId, string $suffix = 'recipient'): string
  249. {
  250. return "provider-{$providerId}-{$suffix}";
  251. }
  252. private function ensureRecipientCode(Provider $provider): string
  253. {
  254. if (! empty($provider->recipient_code)) {
  255. return $provider->recipient_code;
  256. }
  257. $recipientCode = 'provider-'.(string) Str::uuid();
  258. $provider->forceFill(['recipient_code' => $recipientCode])->save();
  259. return $recipientCode;
  260. }
  261. }