PagarmeRecipientService.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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' => $this->buildRecipientCode($provider, $data),
  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. $endpoint = $this->pagarmeUrl('/recipients');
  66. PagarmeHttpLogger::logRequest('POST', $endpoint, $payload);
  67. $response = $this->pagarmeRequest($provider->id)
  68. ->post($endpoint, $payload);
  69. if ($response->failed()) {
  70. Log::channel('pagarme')->error('Pagar.me recipient creation failed', [
  71. 'status' => $response->status(),
  72. 'body' => $response->json() ?? $response->body(),
  73. 'payload' => $payload,
  74. ]);
  75. throw new \RuntimeException('Erro ao criar recebedor no Pagar.me.');
  76. }
  77. $recipientData = $response->json();
  78. $recipientId = $recipientData['id'] ?? null;
  79. if (! $recipientId) {
  80. Log::channel('pagarme')->error('Pagar.me recipient creation returned empty id', [
  81. 'response' => $recipientData,
  82. ]);
  83. throw new \RuntimeException('Pagar.me recipient creation returned an empty id.');
  84. }
  85. $provider->forceFill([
  86. 'recipient_id' => $recipientId,
  87. 'recipient_name' => $data['recipient_name'],
  88. 'recipient_email' => $data['recipient_email'],
  89. 'recipient_description' => $data['recipient_description'],
  90. 'recipient_document' => $data['recipient_document'],
  91. 'recipient_type' => $recipientType,
  92. 'recipient_code' => $data['recipient_code'],
  93. 'recipient_payment_mode' => $paymentMode,
  94. 'recipient_default_bank_account' => $bankAccountData,
  95. 'recipient_transfer_settings' => [
  96. 'transfer_enabled' => false,
  97. 'transfer_interval' => 'daily',
  98. 'transfer_day' => 0,
  99. ],
  100. 'recipient_automatic_anticipation_settings' => [
  101. 'enabled' => false,
  102. ],
  103. 'recipient_metadata' => $metadata,
  104. ])->save();
  105. return $recipientId;
  106. }
  107. public function updateDefaultBankAccount(Provider $provider, array $bankAccountData): Provider
  108. {
  109. if (empty($provider->recipient_id)) {
  110. throw new \InvalidArgumentException('Prestador precisa ter recipient_id do Pagar.me para atualizar a conta bancaria.');
  111. }
  112. $bankAccountData = $this->normalizeBankAccountPayload($bankAccountData);
  113. $bankAccountData['holder_name'] = $this->normalizeBankAccountHolderName($bankAccountData['holder_name']);
  114. $payload = [
  115. 'bank_account' => $this->filterFilledRecursive($bankAccountData),
  116. ];
  117. $endpoint = $this->pagarmeUrl("/recipients/{$provider->recipient_id}/default-bank-account");
  118. PagarmeHttpLogger::logRequest('PATCH', $endpoint, $payload);
  119. $response = $this->pagarmeRequest($provider->id, 'default-bank-account-'.sha1(json_encode($payload)))
  120. ->patch($endpoint, $payload);
  121. if ($response->failed()) {
  122. Log::channel('pagarme')->error('Pagar.me recipient bank account update failed', [
  123. 'provider_id' => $provider->id,
  124. 'recipient_id' => $provider->recipient_id,
  125. 'status' => $response->status(),
  126. 'body' => $response->json() ?? $response->body(),
  127. 'payload' => $payload,
  128. ]);
  129. throw new \RuntimeException('Erro ao atualizar conta bancaria do recebedor no Pagar.me.');
  130. }
  131. $recipientData = $response->json();
  132. $provider->forceFill([
  133. 'recipient_default_bank_account' => $recipientData['default_bank_account'] ?? $bankAccountData,
  134. ])->save();
  135. return $provider->fresh();
  136. }
  137. //
  138. private function idempotencyKey(int $providerId, string $suffix = 'recipient'): string
  139. {
  140. return "provider-{$providerId}-{$suffix}";
  141. }
  142. private function pagarmeRequest(int $providerId, string $suffix = 'recipient')
  143. {
  144. $secretKey = config('services.pagarme.secret_key');
  145. if (empty($secretKey)) {
  146. Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
  147. throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
  148. }
  149. return Http::withBasicAuth($secretKey, '')
  150. ->withHeaders([
  151. 'Idempotency-Key' => $this->idempotencyKey($providerId, $suffix),
  152. 'Content-Type' => 'application/json',
  153. 'Accept' => 'application/json',
  154. ]);
  155. }
  156. private function pagarmeUrl(string $path): string
  157. {
  158. return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
  159. }
  160. //
  161. private function buildPhoneNumbers(?string $phone): array
  162. {
  163. $digits = preg_replace('/\D+/', '', (string) $phone) ?? '';
  164. if (strlen($digits) < 10) {
  165. return [[
  166. 'ddd' => '11',
  167. 'number' => '999999999',
  168. 'type' => 'mobile',
  169. ]];
  170. }
  171. if (str_starts_with($digits, '55')) {
  172. $digits = substr($digits, 2);
  173. }
  174. $areaCode = substr($digits, 0, 2);
  175. $number = substr($digits, 2);
  176. return [[
  177. 'ddd' => $areaCode,
  178. 'number' => $number,
  179. 'type' => 'mobile',
  180. ]];
  181. }
  182. private function buildRecipientCode(Provider $provider, array $data): string
  183. {
  184. $baseCode = preg_replace('/\D+/', '', (string) ($data['recipient_code'] ?? '')) ?: (string) $provider->id;
  185. // Pagar.me exige external_id unico; usar code deterministico por provider evita colisao entre contas.
  186. return Str::limit("provider-{$provider->id}-{$baseCode}", 52, '');
  187. }
  188. private function extractAddressParts(array $data): array
  189. {
  190. $addressLine = trim((string) ($data['address'] ?? ''));
  191. $segments = array_map('trim', explode(',', $addressLine));
  192. $streetSegment = $segments[0] ?? '';
  193. $streetNumber = $data['number'] ?? null;
  194. $neighborhood = $data['district'] ?? null;
  195. $referencePoint = $data['reference_point'] ?? null;
  196. $complementary = $data['complement'] ?? null;
  197. if ($streetNumber === null) {
  198. preg_match('/^(\d+)/', $streetSegment, $matches);
  199. $streetNumber = $matches[1] ?? 'S/N';
  200. }
  201. if ($neighborhood === null) {
  202. $neighborhood = $segments[1] ?? 'N/A';
  203. }
  204. if ($referencePoint === null) {
  205. $referencePoint = 'N/A';
  206. }
  207. if ($complementary === null) {
  208. $complementary = 'N/A';
  209. }
  210. return [
  211. 'street_number' => (string) $streetNumber,
  212. 'neighborhood' => (string) $neighborhood,
  213. 'reference_point' => (string) $referencePoint,
  214. 'complementary' => (string) $complementary,
  215. ];
  216. }
  217. private function filterFilledRecursive(array $data): array
  218. {
  219. $filtered = [];
  220. foreach ($data as $key => $value) {
  221. if (is_array($value)) {
  222. $value = $this->filterFilledRecursive($value);
  223. }
  224. if ($value !== null && $value !== '' && $value !== []) {
  225. $filtered[$key] = $value;
  226. }
  227. }
  228. return $filtered;
  229. }
  230. private function formatBirthdate(mixed $birthdate): ?string
  231. {
  232. if ($birthdate === null || $birthdate === '') {
  233. return null;
  234. }
  235. if ($birthdate instanceof \DateTimeInterface) {
  236. return Carbon::instance($birthdate)->format('d/m/Y');
  237. }
  238. $birthdate = trim((string) $birthdate);
  239. if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $birthdate) === 1) {
  240. return $birthdate;
  241. }
  242. if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $birthdate) === 1) {
  243. return Carbon::createFromFormat('Y-m-d', $birthdate)->format('d/m/Y');
  244. }
  245. return Carbon::parse($birthdate)->format('d/m/Y');
  246. }
  247. private function normalizeBankAccountHolderName(string $holderName): string
  248. {
  249. $holderName = trim(preg_replace('/\s+/', ' ', $holderName) ?? '');
  250. if (Str::length($holderName) < 30) {
  251. return $holderName;
  252. }
  253. $parts = explode(' ', $holderName);
  254. if (count($parts) >= 3) {
  255. $firstName = array_shift($parts);
  256. $lastName = array_pop($parts);
  257. $initials = array_map(
  258. static fn (string $part): string => Str::upper(Str::substr($part, 0, 1)),
  259. $parts
  260. );
  261. $abbreviated = trim($firstName.' '.implode(' ', $initials).' '.$lastName);
  262. if (Str::length($abbreviated) < 30) {
  263. return $abbreviated;
  264. }
  265. $firstAndLast = trim($firstName.' '.$lastName);
  266. if (Str::length($firstAndLast) < 30) {
  267. return $firstAndLast;
  268. }
  269. }
  270. return Str::limit($holderName, 29, '');
  271. }
  272. private function normalizeBankAccountPayload(array $bankAccountData): array
  273. {
  274. return [
  275. 'holder_name' => $bankAccountData['holder_name'],
  276. 'holder_type' => $bankAccountData['holder_type'],
  277. 'holder_document' => preg_replace('/\D+/', '', $bankAccountData['holder_document']),
  278. 'bank' => $bankAccountData['bank'],
  279. 'branch_number' => $bankAccountData['branch_number'],
  280. 'branch_check_digit' => $bankAccountData['branch_check_digit'] ?? null,
  281. 'account_number' => $bankAccountData['account_number'],
  282. 'account_check_digit' => $bankAccountData['account_check_digit'],
  283. 'type' => $bankAccountData['type'],
  284. 'metadata' => $bankAccountData['metadata'] ?? null,
  285. ];
  286. }
  287. }