PagarmeCardService.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <?php
  2. namespace App\Services\Pagarme;
  3. use App\Models\Address;
  4. use App\Models\ClientPaymentMethod;
  5. use Illuminate\Support\Facades\Http;
  6. use Illuminate\Support\Facades\Log;
  7. class PagarmeCardService
  8. {
  9. public function __construct(
  10. private readonly PagarmeCustomerService $pagarmeCustomerService,
  11. ) {}
  12. public function createCardForPaymentMethod(ClientPaymentMethod $paymentMethod): ?string
  13. {
  14. if (! empty($paymentMethod->gateway_card_id)) {
  15. return $paymentMethod->gateway_card_id;
  16. }
  17. if (empty($paymentMethod->token)) {
  18. throw new \InvalidArgumentException('Token do cartao e obrigatorio para salvar cartao no Pagar.me.');
  19. }
  20. $paymentMethod->loadMissing('client.user');
  21. $customerId = $this->pagarmeCustomerService->createCustomerForClient(
  22. $paymentMethod->client,
  23. []
  24. );
  25. if (empty($customerId)) {
  26. throw new \RuntimeException('Cliente precisa ter customer_id do Pagar.me para salvar cartao.');
  27. }
  28. $payload = $this->filterFilledRecursive([
  29. 'token' => $paymentMethod->token,
  30. 'label' => $paymentMethod->card_name,
  31. 'billing_address' => $this->buildBillingAddress($paymentMethod),
  32. ]);
  33. $endpoint = $this->pagarmeUrl("/customers/{$customerId}/cards");
  34. PagarmeHttpLogger::logRequest('POST', $endpoint, $payload);
  35. $response = $this->pagarmeRequest($paymentMethod->id)
  36. ->post($endpoint, $payload);
  37. if ($response->failed()) {
  38. $responseBody = $response->json() ?? $response->body();
  39. Log::channel('pagarme')->error('Pagar.me card creation failed', [
  40. 'client_payment_method_id' => $paymentMethod->id,
  41. 'client_id' => $paymentMethod->client_id,
  42. 'customer_id' => $customerId,
  43. 'status' => $response->status(),
  44. 'body' => $responseBody,
  45. 'payload' => $payload,
  46. ]);
  47. throw new \RuntimeException($this->gatewayErrorMessage($responseBody));
  48. }
  49. $cardData = $response->json();
  50. $cardId = $cardData['id'] ?? null;
  51. if (empty($cardId)) {
  52. Log::channel('pagarme')->error('Pagar.me card creation returned empty id', [
  53. 'client_payment_method_id' => $paymentMethod->id,
  54. 'client_id' => $paymentMethod->client_id,
  55. 'customer_id' => $customerId,
  56. 'response' => $cardData,
  57. ]);
  58. throw new \RuntimeException('Pagar.me card creation returned an empty id.');
  59. }
  60. $paymentMethod->forceFill([
  61. 'gateway_card_id' => $cardId,
  62. 'brand' => $paymentMethod->brand ?: ($cardData['brand'] ?? null),
  63. 'last_four_digits' => $paymentMethod->last_four_digits ?: ($cardData['last_four_digits'] ?? null),
  64. ])->save();
  65. return $cardId;
  66. }
  67. //
  68. private function pagarmeRequest(int $paymentMethodId)
  69. {
  70. $secretKey = config('services.pagarme.secret_key');
  71. if (empty($secretKey)) {
  72. Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
  73. throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
  74. }
  75. return Http::withBasicAuth($secretKey, '')
  76. ->withHeaders([
  77. 'Idempotency-Key' => "client-payment-method-{$paymentMethodId}-card",
  78. 'Content-Type' => 'application/json',
  79. 'Accept' => 'application/json',
  80. ]);
  81. }
  82. private function pagarmeUrl(string $path): string
  83. {
  84. return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
  85. }
  86. //
  87. private function buildBillingAddress(ClientPaymentMethod $paymentMethod): array
  88. {
  89. $address = Address::query()
  90. ->with(['city.state', 'state'])
  91. ->where('source', 'client')
  92. ->where('source_id', $paymentMethod->client_id)
  93. ->orderByDesc('is_primary')
  94. ->latest('id')
  95. ->first();
  96. if (! $address) {
  97. throw new \InvalidArgumentException('Cliente precisa ter endereco para salvar cartao no Pagar.me.');
  98. }
  99. $state = $address->state?->code ?? $address->city?->state?->code;
  100. $city = $address->city?->name;
  101. $line1 = implode(', ', array_filter([
  102. $address->number ?: 'S/N',
  103. $address->address,
  104. $address->district,
  105. ]));
  106. foreach ([
  107. 'estado' => $state,
  108. 'cidade' => $city,
  109. 'cep' => $this->digits($address->zip_code),
  110. 'endereco' => $line1,
  111. ] as $field => $value) {
  112. if ($value === null || $value === '') {
  113. throw new \InvalidArgumentException("Cliente precisa ter {$field} valido para salvar cartao no Pagar.me.");
  114. }
  115. }
  116. return [
  117. 'line_1' => $line1,
  118. 'line_2' => $address->complement ?: $address->instructions,
  119. 'zip_code' => $this->digits($address->zip_code),
  120. 'city' => $city,
  121. 'state' => $state,
  122. 'country' => 'BR',
  123. ];
  124. }
  125. private function digits(?string $value): string
  126. {
  127. return preg_replace('/\D+/', '', (string) $value) ?? '';
  128. }
  129. private function filterFilledRecursive(array $data): array
  130. {
  131. $filtered = [];
  132. foreach ($data as $key => $value) {
  133. if (is_array($value)) {
  134. $value = $this->filterFilledRecursive($value);
  135. }
  136. if ($value !== null && $value !== '' && $value !== []) {
  137. $filtered[$key] = $value;
  138. }
  139. }
  140. return $filtered;
  141. }
  142. private function gatewayErrorMessage(mixed $responseBody): string
  143. {
  144. $message = is_array($responseBody) ? ($responseBody['message'] ?? null) : null;
  145. if ($message === 'Token not found.') {
  146. return 'Token do cartao nao encontrado no Pagar.me. Gere o token com a public key do mesmo ambiente da secret key.';
  147. }
  148. return 'Erro ao salvar cartao no Pagar.me.';
  149. }
  150. }