PagarmeRecipientService.php 12 KB

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