Explorar o código

refactor: add gateway_code nas tabelas + code com uuid para evitar colisao no pagarme + reestrutura payloads do pagarme para objetos para uma leitura mais facil das informacoes

Gustavo Mantovani hai 3 semanas
pai
achega
71be14eaed
Modificáronse 42 ficheiros con 1523 adicións e 409 borrados
  1. 29 0
      app/Data/Pagarme/PagarmeData.php
  2. 25 0
      app/Data/Pagarme/Request/Objects/PagarmeArrayListData.php
  3. 22 0
      app/Data/Pagarme/Request/Objects/PagarmeArrayObjectData.php
  4. 29 0
      app/Data/Pagarme/Request/Objects/PagarmeCardBillingAddressData.php
  5. 29 0
      app/Data/Pagarme/Request/Objects/PagarmeCustomerAddressRequestData.php
  6. 23 0
      app/Data/Pagarme/Request/Objects/PagarmeCustomerPhoneData.php
  7. 21 0
      app/Data/Pagarme/Request/Objects/PagarmeCustomerPhonesRequestData.php
  8. 33 0
      app/Data/Pagarme/Request/Objects/PagarmeRecipientAddressData.php
  9. 19 0
      app/Data/Pagarme/Request/Objects/PagarmeRecipientAutomaticAnticipationSettingsData.php
  10. 50 0
      app/Data/Pagarme/Request/Objects/PagarmeRecipientBankAccountData.php
  11. 23 0
      app/Data/Pagarme/Request/Objects/PagarmeRecipientPhoneData.php
  12. 17 0
      app/Data/Pagarme/Request/Objects/PagarmeRecipientPhoneNumbersData.php
  13. 35 0
      app/Data/Pagarme/Request/Objects/PagarmeRecipientRegisterInformationData.php
  14. 23 0
      app/Data/Pagarme/Request/Objects/PagarmeRecipientTransferSettingsData.php
  15. 52 0
      app/Data/Pagarme/Request/PagarmeBankAccountUpdateRequestData.php
  16. 24 0
      app/Data/Pagarme/Request/PagarmeCardRequestData.php
  17. 35 0
      app/Data/Pagarme/Request/PagarmeCustomerRequestData.php
  18. 35 0
      app/Data/Pagarme/Request/PagarmeCustomerUpdateRequestData.php
  19. 35 0
      app/Data/Pagarme/Request/PagarmeOrderRequestData.php
  20. 31 0
      app/Data/Pagarme/Request/PagarmeRecipientRequestData.php
  21. 20 0
      app/Data/Pagarme/Response/Objects/PagarmeArrayListData.php
  22. 20 0
      app/Data/Pagarme/Response/Objects/PagarmeArrayObjectData.php
  23. 55 0
      app/Data/Pagarme/Response/Objects/PagarmeCustomerAddressResponseData.php
  24. 27 0
      app/Data/Pagarme/Response/Objects/PagarmeCustomerPhonesResponseData.php
  25. 34 0
      app/Data/Pagarme/Response/Objects/PagarmePhoneResponseData.php
  26. 48 0
      app/Data/Pagarme/Response/Objects/PagarmeRecipientBankAccountResponseData.php
  27. 69 0
      app/Data/Pagarme/Response/PagarmeCardResponseData.php
  28. 65 0
      app/Data/Pagarme/Response/PagarmeCustomerResponseData.php
  29. 71 0
      app/Data/Pagarme/Response/PagarmeOrderResponseData.php
  30. 62 0
      app/Data/Pagarme/Response/PagarmeRecipientResponseData.php
  31. 17 0
      app/Http/Resources/PaymentResource.php
  32. 4 0
      app/Models/Payment.php
  33. 60 0
      app/Services/Pagarme/Concerns/SendsPagarmeRequests.php
  34. 32 71
      app/Services/Pagarme/PagarmeCardService.php
  35. 119 111
      app/Services/Pagarme/PagarmeCustomerService.php
  36. 0 17
      app/Services/Pagarme/PagarmeHttpLogger.php
  37. 69 70
      app/Services/Pagarme/PagarmePaymentService.php
  38. 96 135
      app/Services/Pagarme/PagarmeRecipientService.php
  39. 19 5
      app/Services/PaymentService.php
  40. 1 0
      config/services.php
  41. 23 0
      database/migrations/2026_05_22_193327_add_gateway_code_to_payments_table.php
  42. 22 0
      database/migrations/2026_05_22_193330_add_unique_index_to_provider_recipient_code.php

+ 29 - 0
app/Data/Pagarme/PagarmeData.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Data\Pagarme;
+
+abstract readonly class PagarmeData
+{
+    abstract public function toArray(): array;
+
+    protected function filterFilledRecursive(array $data): array
+    {
+        $filtered = [];
+
+        foreach ($data as $key => $value) {
+            if ($value instanceof self) {
+                $value = $value->toArray();
+            }
+
+            if (is_array($value)) {
+                $value = $this->filterFilledRecursive($value);
+            }
+
+            if ($value !== null && $value !== '' && $value !== []) {
+                $filtered[$key] = $value;
+            }
+        }
+
+        return $filtered;
+    }
+}

+ 25 - 0
app/Data/Pagarme/Request/Objects/PagarmeArrayListData.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeArrayListData extends PagarmeData
+{
+    private function __construct(
+        private array $items,
+    ) {}
+
+    public static function fromArray(array $items): self
+    {
+        return new self($items);
+    }
+
+    public function toArray(): array
+    {
+        return array_map(
+            fn (mixed $item) => $item instanceof PagarmeData ? $item->toArray() : $item,
+            $this->items,
+        );
+    }
+}

+ 22 - 0
app/Data/Pagarme/Request/Objects/PagarmeArrayObjectData.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeArrayObjectData extends PagarmeData
+{
+    private function __construct(
+        private array $value,
+    ) {}
+
+    public static function fromArray(array $value): self
+    {
+        return new self($value);
+    }
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive($this->value);
+    }
+}

+ 29 - 0
app/Data/Pagarme/Request/Objects/PagarmeCardBillingAddressData.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeCardBillingAddressData extends PagarmeData
+{
+    public function __construct(
+        public string $line1,
+        public ?string $line2,
+        public string $zipCode,
+        public string $city,
+        public string $state,
+        public string $country,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'line_1'   => $this->line1,
+            'line_2'   => $this->line2,
+            'zip_code' => $this->zipCode,
+            'city'     => $this->city,
+            'state'    => $this->state,
+            'country'  => $this->country,
+        ]);
+    }
+}

+ 29 - 0
app/Data/Pagarme/Request/Objects/PagarmeCustomerAddressRequestData.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeCustomerAddressRequestData extends PagarmeData
+{
+    public function __construct(
+        public ?string $line1,
+        public ?string $line2,
+        public ?string $zipCode,
+        public ?string $city,
+        public ?string $state,
+        public ?string $country,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'line_1'   => $this->line1,
+            'line_2'   => $this->line2,
+            'zip_code' => $this->zipCode,
+            'city'     => $this->city,
+            'state'    => $this->state,
+            'country'  => $this->country,
+        ]);
+    }
+}

+ 23 - 0
app/Data/Pagarme/Request/Objects/PagarmeCustomerPhoneData.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeCustomerPhoneData extends PagarmeData
+{
+    public function __construct(
+        public string $countryCode,
+        public string $areaCode,
+        public string $number,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'country_code' => $this->countryCode,
+            'area_code'    => $this->areaCode,
+            'number'       => $this->number,
+        ]);
+    }
+}

+ 21 - 0
app/Data/Pagarme/Request/Objects/PagarmeCustomerPhonesRequestData.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeCustomerPhonesRequestData extends PagarmeData
+{
+    public function __construct(
+        public ?PagarmeCustomerPhoneData $homePhone = null,
+        public ?PagarmeCustomerPhoneData $mobilePhone = null,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'home_phone'   => $this->homePhone,
+            'mobile_phone' => $this->mobilePhone,
+        ]);
+    }
+}

+ 33 - 0
app/Data/Pagarme/Request/Objects/PagarmeRecipientAddressData.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeRecipientAddressData extends PagarmeData
+{
+    public function __construct(
+        public string $street,
+        public string $complementary,
+        public string $streetNumber,
+        public string $neighborhood,
+        public ?string $city,
+        public ?string $state,
+        public string $zipCode,
+        public string $referencePoint,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'street'          => $this->street,
+            'complementary'   => $this->complementary,
+            'street_number'   => $this->streetNumber,
+            'neighborhood'    => $this->neighborhood,
+            'city'            => $this->city,
+            'state'           => $this->state,
+            'zip_code'        => $this->zipCode,
+            'reference_point' => $this->referencePoint,
+        ]);
+    }
+}

+ 19 - 0
app/Data/Pagarme/Request/Objects/PagarmeRecipientAutomaticAnticipationSettingsData.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeRecipientAutomaticAnticipationSettingsData extends PagarmeData
+{
+    public function __construct(
+        public bool $enabled,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'enabled' => $this->enabled,
+        ]);
+    }
+}

+ 50 - 0
app/Data/Pagarme/Request/Objects/PagarmeRecipientBankAccountData.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeRecipientBankAccountData extends PagarmeData
+{
+    public function __construct(
+        public string $holderName,
+        public string $holderType,
+        public string $holderDocument,
+        public string $bank,
+        public string $branchNumber,
+        public ?string $branchCheckDigit,
+        public string $accountNumber,
+        public string $accountCheckDigit,
+        public string $type,
+    ) {}
+
+    public static function fromArray(array $payload): self
+    {
+        return new self(
+            holderName: $payload['holder_name'],
+            holderType: $payload['holder_type'],
+            holderDocument: preg_replace('/\D+/', '', $payload['holder_document']),
+            bank: $payload['bank'],
+            branchNumber: $payload['branch_number'],
+            branchCheckDigit: $payload['branch_check_digit'] ?? null,
+            accountNumber: $payload['account_number'],
+            accountCheckDigit: $payload['account_check_digit'],
+            type: $payload['type'],
+        );
+    }
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'holder_name'         => $this->holderName,
+            'holder_type'         => $this->holderType,
+            'holder_document'     => $this->holderDocument,
+            'bank'                => $this->bank,
+            'branch_number'       => $this->branchNumber,
+            'branch_check_digit'  => $this->branchCheckDigit,
+            'account_number'      => $this->accountNumber,
+            'account_check_digit' => $this->accountCheckDigit,
+            'type'                => $this->type,
+        ]);
+    }
+}

+ 23 - 0
app/Data/Pagarme/Request/Objects/PagarmeRecipientPhoneData.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeRecipientPhoneData extends PagarmeData
+{
+    public function __construct(
+        public string $ddd,
+        public string $number,
+        public string $type,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'ddd'    => $this->ddd,
+            'number' => $this->number,
+            'type'   => $this->type,
+        ]);
+    }
+}

+ 17 - 0
app/Data/Pagarme/Request/Objects/PagarmeRecipientPhoneNumbersData.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeRecipientPhoneNumbersData extends PagarmeData
+{
+    public function __construct(
+        public PagarmeRecipientPhoneData $phone,
+    ) {}
+
+    public function toArray(): array
+    {
+        return [$this->phone->toArray()];
+    }
+}

+ 35 - 0
app/Data/Pagarme/Request/Objects/PagarmeRecipientRegisterInformationData.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeRecipientRegisterInformationData extends PagarmeData
+{
+    public function __construct(
+        public string $name,
+        public string $email,
+        public string $document,
+        public string $type,
+        public ?string $birthdate,
+        public int $monthlyIncome,
+        public string $professionalOccupation,
+        public PagarmeRecipientPhoneNumbersData $phoneNumbers,
+        public PagarmeRecipientAddressData $address,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'name'                    => $this->name,
+            'email'                   => $this->email,
+            'document'                => $this->document,
+            'type'                    => $this->type,
+            'birthdate'               => $this->birthdate,
+            'monthly_income'          => $this->monthlyIncome,
+            'professional_occupation' => $this->professionalOccupation,
+            'phone_numbers'           => $this->phoneNumbers,
+            'address'                 => $this->address,
+        ]);
+    }
+}

+ 23 - 0
app/Data/Pagarme/Request/Objects/PagarmeRecipientTransferSettingsData.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Data\Pagarme\Request\Objects;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeRecipientTransferSettingsData extends PagarmeData
+{
+    public function __construct(
+        public bool $transferEnabled,
+        public string $transferInterval,
+        public int $transferDay,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'transfer_enabled'  => $this->transferEnabled,
+            'transfer_interval' => $this->transferInterval,
+            'transfer_day'      => $this->transferDay,
+        ]);
+    }
+}

+ 52 - 0
app/Data/Pagarme/Request/PagarmeBankAccountUpdateRequestData.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Data\Pagarme\Request;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeBankAccountUpdateRequestData extends PagarmeData
+{
+    public function __construct(
+        public string $holderName,
+        public string $holderType,
+        public string $holderDocument,
+        public string $bank,
+        public string $branchNumber,
+        public ?string $branchCheckDigit,
+        public string $accountNumber,
+        public string $accountCheckDigit,
+        public string $type,
+    ) {}
+
+    public static function fromArray(array $payload): self
+    {
+        return new self(
+            holderName: $payload['holder_name'],
+            holderType: $payload['holder_type'],
+            holderDocument: preg_replace('/\D+/', '', $payload['holder_document']),
+            bank: $payload['bank'],
+            branchNumber: $payload['branch_number'],
+            branchCheckDigit: $payload['branch_check_digit'] ?? null,
+            accountNumber: $payload['account_number'],
+            accountCheckDigit: $payload['account_check_digit'],
+            type: $payload['type'],
+        );
+    }
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'bank_account' => [
+                'holder_name'         => $this->holderName,
+                'holder_type'         => $this->holderType,
+                'holder_document'     => $this->holderDocument,
+                'bank'                => $this->bank,
+                'branch_number'       => $this->branchNumber,
+                'branch_check_digit'  => $this->branchCheckDigit,
+                'account_number'      => $this->accountNumber,
+                'account_check_digit' => $this->accountCheckDigit,
+                'type'                => $this->type,
+            ],
+        ]);
+    }
+}

+ 24 - 0
app/Data/Pagarme/Request/PagarmeCardRequestData.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Data\Pagarme\Request;
+
+use App\Data\Pagarme\PagarmeData;
+use App\Data\Pagarme\Request\Objects\PagarmeCardBillingAddressData;
+
+readonly class PagarmeCardRequestData extends PagarmeData
+{
+    public function __construct(
+        public string $token,
+        public ?string $label = null,
+        public ?PagarmeCardBillingAddressData $billingAddress = null,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'token'           => $this->token,
+            'label'           => $this->label,
+            'billing_address' => $this->billingAddress,
+        ]);
+    }
+}

+ 35 - 0
app/Data/Pagarme/Request/PagarmeCustomerRequestData.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Data\Pagarme\Request;
+
+use App\Data\Pagarme\PagarmeData;
+use App\Data\Pagarme\Request\Objects\PagarmeCustomerAddressRequestData;
+use App\Data\Pagarme\Request\Objects\PagarmeCustomerPhonesRequestData;
+
+readonly class PagarmeCustomerRequestData extends PagarmeData
+{
+    public function __construct(
+        public string $name,
+        public string $email,
+        public string $document,
+        public string $type,
+        public string $documentType,
+        public string $code,
+        public ?PagarmeCustomerAddressRequestData $address = null,
+        public ?PagarmeCustomerPhonesRequestData $phones = null,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'name'          => $this->name,
+            'email'         => $this->email,
+            'document'      => $this->document,
+            'type'          => $this->type,
+            'document_type' => $this->documentType,
+            'code'          => $this->code,
+            'address'       => $this->address,
+            'phones'        => $this->phones,
+        ]);
+    }
+}

+ 35 - 0
app/Data/Pagarme/Request/PagarmeCustomerUpdateRequestData.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Data\Pagarme\Request;
+
+use App\Data\Pagarme\PagarmeData;
+use App\Data\Pagarme\Request\Objects\PagarmeCustomerAddressRequestData;
+use App\Data\Pagarme\Request\Objects\PagarmeCustomerPhonesRequestData;
+
+readonly class PagarmeCustomerUpdateRequestData extends PagarmeData
+{
+    public function __construct(
+        public ?string $name = null,
+        public ?string $email = null,
+        public ?string $document = null,
+        public ?string $type = null,
+        public ?string $documentType = null,
+        public ?string $code = null,
+        public ?PagarmeCustomerAddressRequestData $address = null,
+        public ?PagarmeCustomerPhonesRequestData $phones = null,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'name'          => $this->name,
+            'email'         => $this->email,
+            'document'      => $this->document,
+            'type'          => $this->type,
+            'document_type' => $this->documentType,
+            'code'          => $this->code,
+            'address'       => $this->address,
+            'phones'        => $this->phones,
+        ]);
+    }
+}

+ 35 - 0
app/Data/Pagarme/Request/PagarmeOrderRequestData.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Data\Pagarme\Request;
+
+use App\Data\Pagarme\PagarmeData;
+use App\Data\Pagarme\Request\Objects\PagarmeArrayListData;
+use App\Data\Pagarme\Request\Objects\PagarmeArrayObjectData;
+
+readonly class PagarmeOrderRequestData extends PagarmeData
+{
+    public function __construct(
+        public string $code,
+        public PagarmeArrayListData $items,
+        public PagarmeArrayListData $payments,
+        public PagarmeArrayObjectData $metadata,
+        public ?PagarmeArrayObjectData $customer = null,
+        public ?string $customerId = null,
+        public bool $closed = true,
+        public ?string $channel = null,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'code'        => $this->code,
+            'items'       => $this->items,
+            'payments'    => $this->payments,
+            'closed'      => $this->closed,
+            'metadata'    => $this->metadata,
+            'customer_id' => $this->customerId,
+            'customer'    => $this->customer,
+            'channel'     => $this->channel,
+        ]);
+    }
+}

+ 31 - 0
app/Data/Pagarme/Request/PagarmeRecipientRequestData.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Data\Pagarme\Request;
+
+use App\Data\Pagarme\PagarmeData;
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientAutomaticAnticipationSettingsData;
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientBankAccountData;
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientRegisterInformationData;
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientTransferSettingsData;
+
+readonly class PagarmeRecipientRequestData extends PagarmeData
+{
+    public function __construct(
+        public string $code,
+        public PagarmeRecipientRegisterInformationData $registerInformation,
+        public PagarmeRecipientBankAccountData $defaultBankAccount,
+        public PagarmeRecipientTransferSettingsData $transferSettings,
+        public PagarmeRecipientAutomaticAnticipationSettingsData $automaticAnticipationSettings,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'code'                            => $this->code,
+            'register_information'            => $this->registerInformation,
+            'default_bank_account'            => $this->defaultBankAccount,
+            'transfer_settings'               => $this->transferSettings,
+            'automatic_anticipation_settings' => $this->automaticAnticipationSettings,
+        ]);
+    }
+}

+ 20 - 0
app/Data/Pagarme/Response/Objects/PagarmeArrayListData.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Data\Pagarme\Response\Objects;
+
+readonly class PagarmeArrayListData
+{
+    private function __construct(
+        private array $items,
+    ) {}
+
+    public static function fromArray(array $items): self
+    {
+        return new self($items);
+    }
+
+    public function toArray(): array
+    {
+        return $this->items;
+    }
+}

+ 20 - 0
app/Data/Pagarme/Response/Objects/PagarmeArrayObjectData.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Data\Pagarme\Response\Objects;
+
+readonly class PagarmeArrayObjectData
+{
+    private function __construct(
+        private array $value,
+    ) {}
+
+    public static function fromArray(array $value): self
+    {
+        return new self($value);
+    }
+
+    public function toArray(): array
+    {
+        return $this->value;
+    }
+}

+ 55 - 0
app/Data/Pagarme/Response/Objects/PagarmeCustomerAddressResponseData.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace App\Data\Pagarme\Response\Objects;
+
+readonly class PagarmeCustomerAddressResponseData
+{
+    public function __construct(
+        public ?string $id,
+        public ?string $line1,
+        public ?string $line2,
+        public ?string $zipCode,
+        public ?string $city,
+        public ?string $state,
+        public ?string $country,
+        public ?string $status,
+        public ?string $createdAt = null,
+        public ?string $updatedAt = null,
+    ) {}
+
+    public static function fromArray(?array $payload): ?self
+    {
+        if (empty($payload)) {
+            return null;
+        }
+
+        return new self(
+            id: $payload['id'] ?? null,
+            line1: $payload['line_1'] ?? null,
+            line2: $payload['line_2'] ?? null,
+            zipCode: $payload['zip_code'] ?? null,
+            city: $payload['city'] ?? null,
+            state: $payload['state'] ?? null,
+            country: $payload['country'] ?? null,
+            status: $payload['status'] ?? null,
+            createdAt: $payload['created_at'] ?? null,
+            updatedAt: $payload['updated_at'] ?? null,
+        );
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'id'         => $this->id,
+            'line_1'     => $this->line1,
+            'line_2'     => $this->line2,
+            'zip_code'   => $this->zipCode,
+            'city'       => $this->city,
+            'state'      => $this->state,
+            'country'    => $this->country,
+            'status'     => $this->status,
+            'created_at' => $this->createdAt,
+            'updated_at' => $this->updatedAt,
+        ];
+    }
+}

+ 27 - 0
app/Data/Pagarme/Response/Objects/PagarmeCustomerPhonesResponseData.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Data\Pagarme\Response\Objects;
+
+readonly class PagarmeCustomerPhonesResponseData
+{
+    public function __construct(
+        public ?PagarmePhoneResponseData $homePhone,
+        public ?PagarmePhoneResponseData $mobilePhone,
+    ) {}
+
+    public static function fromArray(?array $payload): self
+    {
+        return new self(
+            homePhone: PagarmePhoneResponseData::fromArray($payload['home_phone'] ?? null),
+            mobilePhone: PagarmePhoneResponseData::fromArray($payload['mobile_phone'] ?? null),
+        );
+    }
+
+    public function toArray(): array
+    {
+        return array_filter([
+            'home_phone'   => $this->homePhone?->toArray(),
+            'mobile_phone' => $this->mobilePhone?->toArray(),
+        ]);
+    }
+}

+ 34 - 0
app/Data/Pagarme/Response/Objects/PagarmePhoneResponseData.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Data\Pagarme\Response\Objects;
+
+readonly class PagarmePhoneResponseData
+{
+    public function __construct(
+        public ?string $countryCode,
+        public ?string $areaCode,
+        public ?string $number,
+    ) {}
+
+    public static function fromArray(?array $payload): ?self
+    {
+        if (empty($payload)) {
+            return null;
+        }
+
+        return new self(
+            countryCode: $payload['country_code'] ?? null,
+            areaCode: $payload['area_code'] ?? null,
+            number: $payload['number'] ?? null,
+        );
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'country_code' => $this->countryCode,
+            'area_code'    => $this->areaCode,
+            'number'       => $this->number,
+        ];
+    }
+}

+ 48 - 0
app/Data/Pagarme/Response/Objects/PagarmeRecipientBankAccountResponseData.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Data\Pagarme\Response\Objects;
+
+readonly class PagarmeRecipientBankAccountResponseData
+{
+    public function __construct(
+        public ?string $holderName,
+        public ?string $holderType,
+        public ?string $holderDocument,
+        public ?string $bank,
+        public ?string $branchNumber,
+        public ?string $branchCheckDigit,
+        public ?string $accountNumber,
+        public ?string $accountCheckDigit,
+        public ?string $type,
+    ) {}
+
+    public static function fromArray(array $payload): self
+    {
+        return new self(
+            holderName: $payload['holder_name'] ?? null,
+            holderType: $payload['holder_type'] ?? null,
+            holderDocument: $payload['holder_document'] ?? null,
+            bank: $payload['bank'] ?? null,
+            branchNumber: $payload['branch_number'] ?? null,
+            branchCheckDigit: $payload['branch_check_digit'] ?? null,
+            accountNumber: $payload['account_number'] ?? null,
+            accountCheckDigit: $payload['account_check_digit'] ?? null,
+            type: $payload['type'] ?? null,
+        );
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'holder_name'         => $this->holderName,
+            'holder_type'         => $this->holderType,
+            'holder_document'     => $this->holderDocument,
+            'bank'                => $this->bank,
+            'branch_number'       => $this->branchNumber,
+            'branch_check_digit'  => $this->branchCheckDigit,
+            'account_number'      => $this->accountNumber,
+            'account_check_digit' => $this->accountCheckDigit,
+            'type'                => $this->type,
+        ];
+    }
+}

+ 69 - 0
app/Data/Pagarme/Response/PagarmeCardResponseData.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace App\Data\Pagarme\Response;
+
+readonly class PagarmeCardResponseData
+{
+    public function __construct(
+        public ?string $id,
+        public ?string $firstSixDigits,
+        public ?string $lastFourDigits,
+        public ?string $brand,
+        public ?string $holderName,
+        public ?int $expMonth,
+        public ?int $expYear,
+        public ?string $status,
+        public ?string $type,
+        public ?string $createdAt = null,
+        public ?string $updatedAt = null,
+    ) {}
+
+    public static function fromArray(array $payload): self
+    {
+        return new self(
+            id: $payload['id'] ?? null,
+            firstSixDigits: $payload['first_six_digits'] ?? null,
+            lastFourDigits: $payload['last_four_digits'] ?? null,
+            brand: $payload['brand'] ?? null,
+            holderName: $payload['holder_name'] ?? null,
+            expMonth: isset($payload['exp_month']) ? (int) $payload['exp_month'] : null,
+            expYear: isset($payload['exp_year']) ? (int) $payload['exp_year'] : null,
+            status: $payload['status'] ?? null,
+            type: $payload['type'] ?? null,
+            createdAt: $payload['created_at'] ?? null,
+            updatedAt: $payload['updated_at'] ?? null,
+        );
+    }
+
+    public function id(): ?string
+    {
+        return $this->id;
+    }
+
+    public function brand(): ?string
+    {
+        return $this->brand;
+    }
+
+    public function lastFourDigits(): ?string
+    {
+        return $this->lastFourDigits;
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'id'               => $this->id,
+            'first_six_digits' => $this->firstSixDigits,
+            'last_four_digits' => $this->lastFourDigits,
+            'brand'            => $this->brand,
+            'holder_name'      => $this->holderName,
+            'exp_month'        => $this->expMonth,
+            'exp_year'         => $this->expYear,
+            'status'           => $this->status,
+            'type'             => $this->type,
+            'created_at'       => $this->createdAt,
+            'updated_at'       => $this->updatedAt,
+        ];
+    }
+}

+ 65 - 0
app/Data/Pagarme/Response/PagarmeCustomerResponseData.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace App\Data\Pagarme\Response;
+
+use App\Data\Pagarme\Response\Objects\PagarmeCustomerAddressResponseData;
+use App\Data\Pagarme\Response\Objects\PagarmeCustomerPhonesResponseData;
+
+readonly class PagarmeCustomerResponseData
+{
+    public function __construct(
+        public ?string $id,
+        public ?string $name,
+        public ?string $email,
+        public ?string $code,
+        public ?string $document,
+        public ?string $documentType,
+        public ?string $type,
+        public ?bool $delinquent,
+        public ?PagarmeCustomerAddressResponseData $address,
+        public PagarmeCustomerPhonesResponseData $phones,
+        public ?string $createdAt = null,
+        public ?string $updatedAt = null,
+    ) {}
+
+    public static function fromArray(array $payload): self
+    {
+        return new self(
+            id: $payload['id'] ?? null,
+            name: $payload['name'] ?? null,
+            email: $payload['email'] ?? null,
+            code: $payload['code'] ?? null,
+            document: $payload['document'] ?? null,
+            documentType: $payload['document_type'] ?? null,
+            type: $payload['type'] ?? null,
+            delinquent: $payload['delinquent'] ?? null,
+            address: PagarmeCustomerAddressResponseData::fromArray($payload['address'] ?? null),
+            phones: PagarmeCustomerPhonesResponseData::fromArray($payload['phones'] ?? null),
+            createdAt: $payload['created_at'] ?? null,
+            updatedAt: $payload['updated_at'] ?? null,
+        );
+    }
+
+    public function id(): ?string
+    {
+        return $this->id;
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'id'            => $this->id,
+            'name'          => $this->name,
+            'email'         => $this->email,
+            'code'          => $this->code,
+            'document'      => $this->document,
+            'document_type' => $this->documentType,
+            'type'          => $this->type,
+            'delinquent'    => $this->delinquent,
+            'address'       => $this->address?->toArray(),
+            'phones'        => $this->phones->toArray(),
+            'created_at'    => $this->createdAt,
+            'updated_at'    => $this->updatedAt,
+        ];
+    }
+}

+ 71 - 0
app/Data/Pagarme/Response/PagarmeOrderResponseData.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Data\Pagarme\Response;
+
+use App\Data\Pagarme\Response\Objects\PagarmeArrayListData;
+use App\Data\Pagarme\Response\Objects\PagarmeArrayObjectData;
+
+readonly class PagarmeOrderResponseData
+{
+    public function __construct(
+        public ?string $id,
+        public ?string $code,
+        public ?int $amount,
+        public ?string $currency,
+        public ?bool $closed,
+        public ?string $status,
+        public PagarmeArrayListData $items,
+        public ?PagarmeArrayObjectData $customer,
+        public PagarmeArrayListData $charges,
+        public PagarmeArrayListData $checkouts,
+        public PagarmeArrayObjectData $metadata,
+        public ?string $createdAt = null,
+        public ?string $updatedAt = null,
+        public ?string $closedAt = null,
+    ) {}
+
+    public static function fromArray(array $payload): self
+    {
+        return new self(
+            id: $payload['id'] ?? null,
+            code: $payload['code'] ?? null,
+            amount: isset($payload['amount']) ? (int) $payload['amount'] : null,
+            currency: $payload['currency'] ?? null,
+            closed: $payload['closed'] ?? null,
+            status: $payload['status'] ?? null,
+            items: PagarmeArrayListData::fromArray($payload['items'] ?? []),
+            customer: ! empty($payload['customer']) ? PagarmeArrayObjectData::fromArray($payload['customer']) : null,
+            charges: PagarmeArrayListData::fromArray($payload['charges'] ?? []),
+            checkouts: PagarmeArrayListData::fromArray($payload['checkouts'] ?? []),
+            metadata: PagarmeArrayObjectData::fromArray($payload['metadata'] ?? []),
+            createdAt: $payload['created_at'] ?? null,
+            updatedAt: $payload['updated_at'] ?? null,
+            closedAt: $payload['closed_at'] ?? null,
+        );
+    }
+
+    public function id(): ?string
+    {
+        return $this->id;
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'id'         => $this->id,
+            'code'       => $this->code,
+            'amount'     => $this->amount,
+            'currency'   => $this->currency,
+            'closed'     => $this->closed,
+            'items'      => $this->items->toArray(),
+            'customer'   => $this->customer?->toArray(),
+            'status'     => $this->status,
+            'created_at' => $this->createdAt,
+            'updated_at' => $this->updatedAt,
+            'closed_at'  => $this->closedAt,
+            'charges'    => $this->charges->toArray(),
+            'checkouts'  => $this->checkouts->toArray(),
+            'metadata'   => $this->metadata->toArray(),
+        ];
+    }
+}

+ 62 - 0
app/Data/Pagarme/Response/PagarmeRecipientResponseData.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace App\Data\Pagarme\Response;
+
+use App\Data\Pagarme\Response\Objects\PagarmeRecipientBankAccountResponseData;
+
+readonly class PagarmeRecipientResponseData
+{
+    public function __construct(
+        public ?string $id,
+        public ?string $name,
+        public ?string $email,
+        public ?string $document,
+        public ?string $type,
+        public ?string $status,
+        public ?PagarmeRecipientBankAccountResponseData $defaultBankAccount = null,
+        public ?string $createdAt = null,
+        public ?string $updatedAt = null,
+    ) {}
+
+    public static function fromArray(array $payload): self
+    {
+        return new self(
+            id: $payload['id'] ?? null,
+            name: $payload['name'] ?? null,
+            email: $payload['email'] ?? null,
+            document: $payload['document'] ?? null,
+            type: $payload['type'] ?? null,
+            status: $payload['status'] ?? null,
+            defaultBankAccount: ! empty($payload['default_bank_account'])
+                ? PagarmeRecipientBankAccountResponseData::fromArray($payload['default_bank_account'])
+                : null,
+            createdAt: $payload['created_at'] ?? null,
+            updatedAt: $payload['updated_at'] ?? null,
+        );
+    }
+
+    public function id(): ?string
+    {
+        return $this->id;
+    }
+
+    public function defaultBankAccount(): ?PagarmeRecipientBankAccountResponseData
+    {
+        return $this->defaultBankAccount;
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'id'                   => $this->id,
+            'name'                 => $this->name,
+            'email'                => $this->email,
+            'document'             => $this->document,
+            'type'                 => $this->type,
+            'status'               => $this->status,
+            'default_bank_account' => $this->defaultBankAccount?->toArray(),
+            'created_at'           => $this->createdAt,
+            'updated_at'           => $this->updatedAt,
+        ];
+    }
+}

+ 17 - 0
app/Http/Resources/PaymentResource.php

@@ -47,6 +47,7 @@ class PaymentResource extends JsonResource
 
             'failure_code'    => $this->failure_code,
             'failure_message' => $this->failure_message,
+            'pix'             => $this->pixData(),
             'gateway_payload' => $this->gateway_payload,
             'metadata'        => $this->metadata,
 
@@ -63,4 +64,20 @@ class PaymentResource extends JsonResource
     {
         return parent::collection($resource);
     }
+
+    private function pixData(): ?array
+    {
+        if ($this->payment_method !== 'pix') {
+            return null;
+        }
+
+        $transaction = $this->gateway_payload['charges'][0]['last_transaction'] ?? [];
+
+        return [
+            'qr_code'     => $transaction['qr_code'] ?? null,
+            'qr_code_url' => $transaction['qr_code_url'] ?? null,
+            'expires_at'  => $transaction['expires_at'] ?? $this->expires_at?->toISOString(),
+            'status'      => $transaction['status'] ?? null,
+        ];
+    }
 }

+ 4 - 0
app/Models/Payment.php

@@ -13,6 +13,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property int|null $provider_id
  * @property int|null $client_payment_method_id
  * @property string $gateway_provider
+ * @property string|null $gateway_code
  * @property string|null $gateway_entity_reference
  * @property string|null $gateway_entity_label
  * @property string|null $gateway_operation_reference
@@ -45,6 +46,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property-read int|null $transfers_count
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Webhook> $webhooks
  * @property-read int|null $webhooks_count
+ *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment onlyTrashed()
@@ -81,6 +83,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment withoutTrashed()
+ *
  * @mixin \Eloquent
  */
 class Payment extends Model
@@ -95,6 +98,7 @@ class Payment extends Model
         'provider_id',
         'client_payment_method_id',
         'gateway_provider',
+        'gateway_code',
         'gateway_entity_reference',
         'gateway_entity_label',
         'gateway_operation_reference',

+ 60 - 0
app/Services/Pagarme/Concerns/SendsPagarmeRequests.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace App\Services\Pagarme\Concerns;
+
+use App\Data\Pagarme\PagarmeData;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Log;
+use Throwable;
+
+trait SendsPagarmeRequests
+{
+    protected function pagarmeRequest(
+        string $method,
+        string $path,
+        string $idempotencyKey,
+        string $errorMessage,
+        array|PagarmeData $payload,
+    ): array {
+        $payload  = $payload instanceof PagarmeData ? $payload->toArray() : $payload;
+        $endpoint = $this->pagarmeUrl($path);
+
+        try {
+            $response = $this->pagarmeHttp($idempotencyKey)
+                ->send($method, $endpoint, ['json' => $payload])
+                ->throw();
+
+            return $response->json() ?? [];
+        } catch (Throwable $e) {
+            Log::channel('pagarme')->error('Pagar.me request failed', [
+                'method'    => strtoupper($method),
+                'endpoint'  => $endpoint,
+                'payload'   => $payload,
+                'exception' => $e->getMessage(),
+            ]);
+        }
+    }
+
+    protected function pagarmeHttp(string $idempotencyKey)
+    {
+        $secretKey = config('services.pagarme.secret_key');
+
+        if (empty($secretKey)) {
+            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
+
+            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
+        }
+
+        return Http::withBasicAuth($secretKey, '')
+            ->withHeaders([
+                'Idempotency-Key' => $idempotencyKey,
+                'Content-Type'    => 'application/json',
+                'Accept'          => 'application/json',
+            ]);
+    }
+
+    protected function pagarmeUrl(string $path): string
+    {
+        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
+    }
+}

+ 32 - 71
app/Services/Pagarme/PagarmeCardService.php

@@ -2,13 +2,18 @@
 
 namespace App\Services\Pagarme;
 
+use App\Data\Pagarme\Request\Objects\PagarmeCardBillingAddressData;
+use App\Data\Pagarme\Request\PagarmeCardRequestData;
+use App\Data\Pagarme\Response\PagarmeCardResponseData;
 use App\Models\Address;
 use App\Models\ClientPaymentMethod;
-use Illuminate\Support\Facades\Http;
+use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
 use Illuminate\Support\Facades\Log;
 
 class PagarmeCardService
 {
+    use SendsPagarmeRequests;
+
     public function __construct(
         private readonly PagarmeCustomerService $pagarmeCustomerService,
     ) {}
@@ -34,43 +39,28 @@ class PagarmeCardService
             throw new \RuntimeException('Cliente precisa ter customer_id do Pagar.me para salvar cartao.');
         }
 
-        $payload = $this->filterFilledRecursive([
-            'token'           => $paymentMethod->token,
-            'label'           => $paymentMethod->card_name,
-            'billing_address' => $this->buildBillingAddress($paymentMethod),
-        ]);
-
-        $endpoint = $this->pagarmeUrl("/customers/{$customerId}/cards");
+        $cardData = PagarmeCardResponseData::fromArray($this->pagarmeRequest(
+            method: 'POST',
+            path: "/customers/{$customerId}/cards",
 
-        PagarmeHttpLogger::logRequest('POST', $endpoint, $payload);
+            payload: new PagarmeCardRequestData(
+                token: $paymentMethod->token,
+                label: $paymentMethod->card_name,
+                billingAddress: $this->buildBillingAddress($paymentMethod),
+            ),
 
-        $response = $this->pagarmeRequest($paymentMethod->id)
-            ->post($endpoint, $payload);
+            idempotencyKey: $this->idempotencyKey($paymentMethod),
+            errorMessage: 'Erro ao salvar cartao no Pagar.me.',
+        ));
 
-        if ($response->failed()) {
-            $responseBody = $response->json() ?? $response->body();
-
-            Log::channel('pagarme')->error('Pagar.me card creation failed', [
-                'client_payment_method_id' => $paymentMethod->id,
-                'client_id'                => $paymentMethod->client_id,
-                'customer_id'              => $customerId,
-                'status'                   => $response->status(),
-                'body'                     => $responseBody,
-                'payload'                  => $payload,
-            ]);
-
-            throw new \RuntimeException($this->gatewayErrorMessage($responseBody));
-        }
-
-        $cardData = $response->json();
-        $cardId   = $cardData['id'] ?? null;
+        $cardId = $cardData->id();
 
         if (empty($cardId)) {
             Log::channel('pagarme')->error('Pagar.me card creation returned empty id', [
                 'client_payment_method_id' => $paymentMethod->id,
                 'client_id'                => $paymentMethod->client_id,
                 'customer_id'              => $customerId,
-                'response'                 => $cardData,
+                'response'                 => $cardData->toArray(),
             ]);
 
             throw new \RuntimeException('Pagar.me card creation returned an empty id.');
@@ -78,8 +68,8 @@ class PagarmeCardService
 
         $paymentMethod->forceFill([
             'gateway_card_id'  => $cardId,
-            'brand'            => $paymentMethod->brand ?: ($cardData['brand'] ?? null),
-            'last_four_digits' => $paymentMethod->last_four_digits ?: ($cardData['last_four_digits'] ?? null),
+            'brand'            => $paymentMethod->brand ?: $cardData->brand(),
+            'last_four_digits' => $paymentMethod->last_four_digits ?: $cardData->lastFourDigits(),
         ])->save();
 
         return $cardId;
@@ -87,32 +77,14 @@ class PagarmeCardService
 
     //
 
-    private function pagarmeRequest(int $paymentMethodId)
+    private function idempotencyKey(ClientPaymentMethod $paymentMethod): string
     {
-        $secretKey = config('services.pagarme.secret_key');
-
-        if (empty($secretKey)) {
-            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
-
-            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
-        }
-
-        return Http::withBasicAuth($secretKey, '')
-            ->withHeaders([
-                'Idempotency-Key' => "client-payment-method-{$paymentMethodId}-card",
-                'Content-Type'    => 'application/json',
-                'Accept'          => 'application/json',
-            ]);
-    }
-
-    private function pagarmeUrl(string $path): string
-    {
-        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
+        return "client-payment-method-{$paymentMethod->id}-card";
     }
 
     //
 
-    private function buildBillingAddress(ClientPaymentMethod $paymentMethod): array
+    private function buildBillingAddress(ClientPaymentMethod $paymentMethod): PagarmeCardBillingAddressData
     {
         $address = Address::query()
             ->with(['city.state', 'state'])
@@ -146,14 +118,14 @@ class PagarmeCardService
             }
         }
 
-        return [
-            'line_1'   => $line1,
-            'line_2'   => $address->complement ?: $address->instructions,
-            'zip_code' => $this->digits($address->zip_code),
-            'city'     => $city,
-            'state'    => $state,
-            'country'  => 'BR',
-        ];
+        return new PagarmeCardBillingAddressData(
+            line1: $line1,
+            line2: $address->complement ?: $address->instructions,
+            zipCode: $this->digits($address->zip_code),
+            city: $city,
+            state: $state,
+            country: 'BR',
+        );
     }
 
     private function digits(?string $value): string
@@ -177,15 +149,4 @@ class PagarmeCardService
 
         return $filtered;
     }
-
-    private function gatewayErrorMessage(mixed $responseBody): string
-    {
-        $message = is_array($responseBody) ? ($responseBody['message'] ?? null) : null;
-
-        if ($message === 'Token not found.') {
-            return 'Token do cartao nao encontrado no Pagar.me. Gere o token com a public key do mesmo ambiente da secret key.';
-        }
-
-        return 'Erro ao salvar cartao no Pagar.me.';
-    }
 }

+ 119 - 111
app/Services/Pagarme/PagarmeCustomerService.php

@@ -2,12 +2,21 @@
 
 namespace App\Services\Pagarme;
 
+use App\Data\Pagarme\Request\Objects\PagarmeCustomerAddressRequestData;
+use App\Data\Pagarme\Request\Objects\PagarmeCustomerPhoneData;
+use App\Data\Pagarme\Request\Objects\PagarmeCustomerPhonesRequestData;
+use App\Data\Pagarme\Request\PagarmeCustomerRequestData;
+use App\Data\Pagarme\Request\PagarmeCustomerUpdateRequestData;
+use App\Data\Pagarme\Response\PagarmeCustomerResponseData;
 use App\Models\Client;
-use Illuminate\Support\Facades\Http;
+use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
 use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Str;
 
 class PagarmeCustomerService
 {
+    use SendsPagarmeRequests;
+
     public function createCustomerForClient(Client $client, array $data): ?string
     {
         if (! empty($client->external_customer_id)) {
@@ -36,47 +45,31 @@ class PagarmeCustomerService
         $address = $this->buildAddress($data);
         $phones  = $this->buildPhones($client->user?->phone ?? $data['phone'] ?? null);
 
-        $code = $client->external_customer_code ?? "client-{$client->id}";
-
-        $payload = $this->filterFilledRecursive([
-            'name'          => $name,
-            'email'         => $email,
-            'document'      => $document,
-            'type'          => $this->personType($document),
-            'document_type' => $this->documentType($document),
-            'code'          => $code,
-            'address'       => $address,
-        ]);
-
-        if (! empty($phones)) {
-            $payload['phones'] = $phones;
-        }
-
-        $endpoint = $this->pagarmeUrl('/customers');
-
-        PagarmeHttpLogger::logRequest('POST', $endpoint, $payload);
-
-        $response = $this->pagarmeRequest($client->id)
-            ->post($endpoint, $payload);
-
-        if ($response->failed()) {
-            Log::channel('pagarme')->error('Pagar.me customer creation failed', [
-                'status'  => $response->status(),
-                'body'    => $response->json() ?? $response->body(),
-                'payload' => $payload,
-            ]);
-
-            throw new \RuntimeException('Erro ao criar cliente no Pagar.me.');
-        }
-
-        $customerData = $response->json();
-
-        $customerId = $customerData['id'] ?? null;
+        $code = $this->ensureCustomerCode($client);
+
+        $customerData = PagarmeCustomerResponseData::fromArray($this->pagarmeRequest(
+            method: 'POST',
+            path: '/customers',
+            payload: new PagarmeCustomerRequestData(
+                name: $name,
+                email: $email,
+                document: $document,
+                type: $this->personType($document),
+                documentType: $this->documentType($document),
+                code: $code,
+                address: $address,
+                phones: $phones,
+            ),
+            idempotencyKey: $this->idempotencyKey($client->id),
+            errorMessage: 'Erro ao criar cliente no Pagar.me.',
+        ));
+
+        $customerId = $customerData->id();
 
         if (! $customerId) {
             Log::channel('pagarme')->error('Customer creation returned an empty id.', [
                 'client_id' => $client->id,
-                'response'  => $customerData,
+                'response'  => $customerData->toArray(),
             ]);
 
             throw new \RuntimeException('Customer creation returned an empty id.');
@@ -90,83 +83,57 @@ class PagarmeCustomerService
         return $customerId;
     }
 
-    //
-
     public function updateCustomer(string $customerId, int $clientId, array $data): void
     {
-        $payload = $this->filterFilledRecursive([
-            'name'          => $data['name'] ?? null,
-            'email'         => $data['email'] ?? null,
-            'document'      => $this->sanitizeDigits($data['document'] ?? null),
-            'type'          => $data['type'] ?? null,
-            'document_type' => $data['document_type'] ?? null,
-            'code'          => $data['code'] ?? null,
-            'address'       => $data['address'] ?? null,
-            'phones'        => $data['phones'] ?? null,
-        ]);
-
-        if (empty($payload)) {
+        $payload = new PagarmeCustomerUpdateRequestData(
+            name: $data['name'] ?? null,
+            email: $data['email'] ?? null,
+            document: $this->sanitizeDigits($data['document'] ?? null),
+            type: $data['type'] ?? null,
+            documentType: $data['document_type'] ?? null,
+            code: $data['code'] ?? null,
+            address: $this->addressFromPayload($data['address'] ?? null),
+            phones: $this->phonesFromPayload($data['phones'] ?? null),
+        );
+
+        if (empty($payload->toArray())) {
             return;
         }
 
-        $endpoint = $this->pagarmeUrl("/customers/{$customerId}");
-
-        PagarmeHttpLogger::logRequest('PATCH', $endpoint, $payload);
-
-        $request  = $this->pagarmeRequest($clientId, "customer-update-{$customerId}");
-        $response = $request->patch($endpoint, $payload);
-
-        if ($response->status() === 405) {
-            PagarmeHttpLogger::logRequest('PUT', $endpoint, $payload);
-            $response = $request->put($endpoint, $payload);
-        }
-
-        if ($response->failed()) {
-            Log::channel('pagarme')->error('Pagar.me customer update failed', [
-                'status'      => $response->status(),
-                'body'        => $response->json() ?? $response->body(),
-                'payload'     => $payload,
-                'customer_id' => $customerId,
-                'client_id'   => $clientId,
-            ]);
-
-            throw new \RuntimeException('Erro ao atualizar cliente no Pagar.me.');
-        }
+        $this->pagarmeRequest(
+            method: 'PATCH',
+            path: "/customers/{$customerId}",
+            payload: $payload,
+            idempotencyKey: $this->idempotencyKey($clientId, "customer-update-{$customerId}"),
+            errorMessage: 'Erro ao atualizar cliente no Pagar.me.',
+        );
     }
 
-    //
-
     private function idempotencyKey(int $clientId, string $suffix = 'customer'): string
     {
         return "client-{$clientId}-{$suffix}";
     }
 
-    private function pagarmeRequest(int $clientId, string $suffix = 'customer')
+    private function ensureCustomerCode(Client $client): string
     {
-        $secretKey = config('services.pagarme.secret_key');
+        if ($this->hasUuidCode($client->external_customer_code, 'client')) {
+            return $client->external_customer_code;
+        }
 
-        if (empty($secretKey)) {
-            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
+        $code = 'client-'.(string) Str::uuid();
 
-            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
-        }
+        $client->forceFill(['external_customer_code' => $code])->save();
 
-        return Http::withBasicAuth($secretKey, '')
-            ->withHeaders([
-                'Idempotency-Key' => $this->idempotencyKey($clientId, $suffix),
-                'Content-Type'    => 'application/json',
-                'Accept'          => 'application/json',
-            ]);
+        return $code;
     }
 
-    private function pagarmeUrl(string $path): string
+    private function hasUuidCode(?string $code, string $prefix): bool
     {
-        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
+        return is_string($code)
+            && 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;
     }
 
-    //
-
-    private function buildAddress(array $data): array
+    private function buildAddress(array $data): PagarmeCustomerAddressRequestData
     {
         $zipCode = $this->sanitizeDigits($data['zip_code'] ?? null);
 
@@ -185,22 +152,22 @@ class PagarmeCustomerService
         $line1 = implode(', ', $line1Parts);
         $line2 = $complement ?: (string) ($data['instructions'] ?? '');
 
-        return [
-            'line_1'   => $line1,
-            'line_2'   => $line2,
-            'zip_code' => $zipCode,
-            'city'     => $city,
-            'state'    => $state,
-            'country'  => $country,
-        ];
+        return new PagarmeCustomerAddressRequestData(
+            line1: $line1,
+            line2: $line2,
+            zipCode: $zipCode,
+            city: $city,
+            state: $state,
+            country: $country,
+        );
     }
 
-    private function buildPhones(?string $phone): array
+    private function buildPhones(?string $phone): PagarmeCustomerPhonesRequestData
     {
         $digits = $this->sanitizeDigits($phone);
 
         if (empty($digits)) {
-            return [];
+            return new PagarmeCustomerPhonesRequestData;
         }
 
         $countryCode = '55';
@@ -213,13 +180,54 @@ class PagarmeCustomerService
             $number   = $digits;
         }
 
-        return [
-            'mobile_phone' => [
-                'country_code' => $countryCode,
-                'area_code'    => $areaCode,
-                'number'       => $number,
-            ],
-        ];
+        return new PagarmeCustomerPhonesRequestData(
+            mobilePhone: new PagarmeCustomerPhoneData(
+                countryCode: $countryCode,
+                areaCode: $areaCode,
+                number: $number,
+            ),
+        );
+    }
+
+    private function addressFromPayload(mixed $address): ?PagarmeCustomerAddressRequestData
+    {
+        if (! is_array($address) || empty($address)) {
+            return null;
+        }
+
+        return new PagarmeCustomerAddressRequestData(
+            line1: $address['line_1'] ?? null,
+            line2: $address['line_2'] ?? null,
+            zipCode: $address['zip_code'] ?? null,
+            city: $address['city'] ?? null,
+            state: $address['state'] ?? null,
+            country: $address['country'] ?? null,
+        );
+    }
+
+    private function phonesFromPayload(mixed $phones): ?PagarmeCustomerPhonesRequestData
+    {
+        if (! is_array($phones) || empty($phones)) {
+            return null;
+        }
+
+        return new PagarmeCustomerPhonesRequestData(
+            homePhone: $this->phoneFromPayload($phones['home_phone'] ?? null),
+            mobilePhone: $this->phoneFromPayload($phones['mobile_phone'] ?? null),
+        );
+    }
+
+    private function phoneFromPayload(mixed $phone): ?PagarmeCustomerPhoneData
+    {
+        if (! is_array($phone) || empty($phone)) {
+            return null;
+        }
+
+        return new PagarmeCustomerPhoneData(
+            countryCode: (string) ($phone['country_code'] ?? ''),
+            areaCode: (string) ($phone['area_code'] ?? ''),
+            number: (string) ($phone['number'] ?? ''),
+        );
     }
 
     private function documentType(string $document): string

+ 0 - 17
app/Services/Pagarme/PagarmeHttpLogger.php

@@ -1,17 +0,0 @@
-<?php
-
-namespace App\Services\Pagarme;
-
-use Illuminate\Support\Facades\Log;
-
-class PagarmeHttpLogger
-{
-    public static function logRequest(string $method, string $endpoint, array $payload): void
-    {
-        Log::channel('pagarme')->info('Pagar.me request', [
-            'method'   => strtoupper($method),
-            'endpoint' => $endpoint,
-            'payload'  => $payload,
-        ]);
-    }
-}

+ 69 - 70
app/Services/Pagarme/PagarmePaymentService.php

@@ -2,14 +2,20 @@
 
 namespace App\Services\Pagarme;
 
+use App\Data\Pagarme\Request\Objects\PagarmeArrayListData;
+use App\Data\Pagarme\Request\Objects\PagarmeArrayObjectData;
+use App\Data\Pagarme\Request\PagarmeOrderRequestData;
+use App\Data\Pagarme\Response\PagarmeOrderResponseData;
 use App\Models\Payment;
 use App\Models\PaymentTransfer;
+use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
 use Illuminate\Support\Collection;
-use Illuminate\Support\Facades\Http;
-use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Str;
 
 class PagarmePaymentService
 {
+    use SendsPagarmeRequests;
+
     public function createOrderWithCreditCard(
         Payment $payment,
         array $items,
@@ -88,61 +94,54 @@ class PagarmePaymentService
             throw new \InvalidArgumentException('customer ou customer_id e obrigatorio.');
         }
 
-        $payload = [
-            'code'     => $options['order_code'] ?? "payment-{$payment->id}",
-            'items'    => $this->validateItems($items),
-            'payments' => [$this->filterFilledRecursive($paymentMethod)],
-            'closed'   => $options['closed'] ?? true,
-
-            'metadata' => array_merge([
+        $requestData = new PagarmeOrderRequestData(
+            code: $this->ensurePaymentCode($payment),
+            items: PagarmeArrayListData::fromArray($this->validateItems($items)),
+            payments: PagarmeArrayListData::fromArray([$this->filterFilledRecursive($paymentMethod)]),
+            metadata: PagarmeArrayObjectData::fromArray(array_merge([
                 'payment_id'  => (string) $payment->id,
                 'schedule_id' => (string) $payment->schedule_id,
                 'client_id'   => (string) $payment->client_id,
                 'provider_id' => (string) $payment->provider_id,
-            ], $options['metadata'] ?? []),
-        ];
-
-        if ($this->filled($customerIdPayload)) {
-            $payload['customer_id'] = $customerIdPayload;
-        }
+            ], $options['metadata'] ?? [])),
+            customer: ! empty($customerObjectPayload) ? PagarmeArrayObjectData::fromArray($customerObjectPayload) : null,
+            customerId: $this->filled($customerIdPayload) ? (string) $customerIdPayload : null,
+            closed: $options['closed'] ?? true,
+            channel: $options['channel'] ?? null,
+        );
 
-        if (! empty($customerObjectPayload)) {
-            $payload['customer'] = $customerObjectPayload;
-        }
+        $order = PagarmeOrderResponseData::fromArray($this->pagarmeRequest(
+            method: 'POST',
+            path: '/orders',
+            payload: $requestData,
+            idempotencyKey: $this->idempotencyKey($payment),
+            errorMessage: 'Erro ao criar pedido de pagamento no Pagar.me.',
+        ));
 
-        if (! empty($options['channel'])) {
-            $payload['channel'] = $options['channel'];
+        if (empty($order->id())) {
+            throw new \RuntimeException('Pagar.me order creation returned an empty id.');
         }
 
-        PagarmeHttpLogger::logRequest('POST', $this->pagarmeUrl('/orders'), $payload);
-
-        $response = $this->pagarmeRequest($this->idempotencyKey($payment))
-            ->post($this->pagarmeUrl('/orders'), $payload);
-
-        if ($response->failed()) {
-            $responseBody = $response->json() ?? $response->body();
-
-            Log::channel('pagarme')->error('Pagar.me order creation failed', [
-                'status'  => $response->status(),
-                'body'    => $responseBody,
-                'payload' => $payload,
-            ]);
+        return $order->toArray();
+    }
 
-            throw new \RuntimeException($this->gatewayErrorMessage($responseBody));
+    private function ensurePaymentCode(Payment $payment): string
+    {
+        if ($this->hasUuidCode($payment->gateway_code, 'payment')) {
+            return $payment->gateway_code;
         }
 
-        $order = $response->json();
+        $code = 'payment-'.(string) Str::uuid();
 
-        if (empty($order['id'])) {
-            Log::channel('pagarme')->error('Pagar.me order creation returned empty id', [
-                'payment_id' => $payment->id,
-                'response'   => $order,
-            ]);
+        $payment->forceFill(['gateway_code' => $code])->save();
 
-            throw new \RuntimeException('Pagar.me order creation returned an empty id.');
-        }
+        return $code;
+    }
 
-        return $order;
+    private function hasUuidCode(?string $code, string $prefix): bool
+    {
+        return is_string($code)
+            && 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;
     }
 
     //
@@ -207,31 +206,6 @@ class PagarmePaymentService
         return "payment-{$payment->id}-schedule-{$payment->schedule_id}";
     }
 
-    private function pagarmeRequest(string $idempotencyKey)
-    {
-        $secretKey = config('services.pagarme.secret_key');
-
-        if (empty($secretKey)) {
-            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
-
-            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
-        }
-
-        return Http::withBasicAuth($secretKey, '')
-            ->withHeaders([
-                'Idempotency-Key' => $idempotencyKey,
-                'Content-Type'    => 'application/json',
-                'Accept'          => 'application/json',
-            ]);
-    }
-
-    private function pagarmeUrl(string $path): string
-    {
-        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
-    }
-
-    //
-
     private function buildCreditCardPayload(array $creditCard): array
     {
         $payload = [];
@@ -314,10 +288,12 @@ class PagarmePaymentService
             return null;
         }
 
-        return collect($gatewayErrors)
+        $message = collect($gatewayErrors)
             ->pluck('message')
             ->filter()
             ->implode('; ') ?: null;
+
+        return $this->translateGatewayMessage($message);
     }
 
     private function filled(mixed $value): bool
@@ -359,9 +335,32 @@ class PagarmePaymentService
             return 'Token do cartao nao encontrado no Pagar.me. Gere um novo card_token ou informe um card_id valido.';
         }
 
+        $errorMessages = is_array($responseBody)
+            ? collect($responseBody['errors'] ?? [])->pluck('message')->filter()->implode('; ')
+            : '';
+
+        $translatedMessage = $this->translateGatewayMessage($message ?: $errorMessages);
+
+        if ($translatedMessage) {
+            return $translatedMessage;
+        }
+
         return 'Erro ao criar pedido de pagamento no Pagar.me.';
     }
 
+    private function translateGatewayMessage(?string $message): ?string
+    {
+        if (! $message) {
+            return null;
+        }
+
+        if (str_contains($message, 'Sem ambiente configurado')) {
+            return 'Pix nao esta habilitado ou configurado neste ambiente do Pagar.me.';
+        }
+
+        return $message;
+    }
+
     private function mapPaymentStatus(?string $chargeStatus, ?string $transactionStatus): string
     {
         $status = strtolower((string) ($transactionStatus ?: $chargeStatus));
@@ -369,7 +368,7 @@ class PagarmePaymentService
         return match ($status) {
             'captured', 'paid', 'overpaid' => 'paid',
             'authorized_pending_capture', 'waiting_capture' => 'authorized',
-            'pending'    => 'pending',
+            'pending', 'waiting_payment' => 'pending',
             'processing' => 'processing',
             'not_authorized', 'with_error', 'failed',
             'underpaid', 'chargedback' => 'failed',

+ 96 - 135
app/Services/Pagarme/PagarmeRecipientService.php

@@ -2,14 +2,26 @@
 
 namespace App\Services\Pagarme;
 
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientAddressData;
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientAutomaticAnticipationSettingsData;
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientBankAccountData;
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientPhoneData;
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientPhoneNumbersData;
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientRegisterInformationData;
+use App\Data\Pagarme\Request\Objects\PagarmeRecipientTransferSettingsData;
+use App\Data\Pagarme\Request\PagarmeBankAccountUpdateRequestData;
+use App\Data\Pagarme\Request\PagarmeRecipientRequestData;
+use App\Data\Pagarme\Response\PagarmeRecipientResponseData;
 use App\Models\Provider;
+use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
 use Carbon\Carbon;
-use Illuminate\Support\Facades\Http;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Str;
 
 class PagarmeRecipientService
 {
+    use SendsPagarmeRequests;
+
     public function createRecipientForProvider(Provider $provider, array $data): string
     {
         if (! empty($provider->recipient_id)) {
@@ -26,78 +38,56 @@ class PagarmeRecipientService
         $monthlyIncome = isset($data['monthly_income']) ? (int) $data['monthly_income'] : 1000;
         $occupation    = $data['professional_occupation'] ?? 'autonomo';
 
-        $payload = $this->filterFilledRecursive([
-            'code' => $this->buildRecipientCode($provider, $data),
-
-            'register_information' => [
-                'name'                    => $data['recipient_name'],
-                'email'                   => $data['recipient_email'],
-                'document'                => preg_replace('/\D+/', '', $data['recipient_document']),
-                'type'                    => $recipientType,
-                'birthdate'               => $this->formatBirthdate($data['birth_date'] ?? null),
-                'monthly_income'          => $monthlyIncome,
-                'professional_occupation' => $occupation,
-                'phone_numbers'           => $this->buildPhoneNumbers($data['phone'] ?? null),
-
-                'address' => [
-                    'street'          => $data['address'],
-                    'complementary'   => $addressParts['complementary'],
-                    'street_number'   => $addressParts['street_number'],
-                    'neighborhood'    => $addressParts['neighborhood'],
-                    'city'            => $data['city'] ?? null,
-                    'state'           => $data['state'] ?? null,
-                    'zip_code'        => preg_replace('/\D+/', '', $data['zip_code']),
-                    'reference_point' => $addressParts['reference_point'],
-                ],
-            ],
-
-            'default_bank_account' => [
-                'holder_name'         => $bankAccountData['holder_name'],
-                'holder_type'         => $bankAccountData['holder_type'],
-                'holder_document'     => preg_replace('/\D+/', '', $bankAccountData['holder_document']),
-                'bank'                => $bankAccountData['bank'],
-                'branch_number'       => $bankAccountData['branch_number'],
-                'branch_check_digit'  => $bankAccountData['branch_check_digit'] ?? null,
-                'account_number'      => $bankAccountData['account_number'],
-                'account_check_digit' => $bankAccountData['account_check_digit'],
-                'type'                => $bankAccountData['type'],
-            ],
-
-            'transfer_settings' => [
-                'transfer_enabled'  => false,
-                'transfer_interval' => 'Daily',
-                'transfer_day'      => 0,
-            ],
-
-            'automatic_anticipation_settings' => [
-                'enabled' => false,
-            ],
-        ]);
-
-        $endpoint = $this->pagarmeUrl('/recipients');
-
-        PagarmeHttpLogger::logRequest('POST', $endpoint, $payload);
-
-        $response = $this->pagarmeRequest($provider->id)
-            ->post($endpoint, $payload);
-
-        if ($response->failed()) {
-            Log::channel('pagarme')->error('Pagar.me recipient creation failed', [
-                'status'  => $response->status(),
-                'body'    => $response->json() ?? $response->body(),
-                'payload' => $payload,
-            ]);
-
-            throw new \RuntimeException('Erro ao criar recebedor no Pagar.me.');
-        }
-
-        $recipientData = $response->json();
-
-        $recipientId = $recipientData['id'] ?? null;
+        $recipientCode = $this->ensureRecipientCode($provider);
+
+        $defaultBankAccount = PagarmeRecipientBankAccountData::fromArray($bankAccountData);
+
+        $payload = new PagarmeRecipientRequestData(
+            code: $recipientCode,
+            registerInformation: new PagarmeRecipientRegisterInformationData(
+                name: $data['recipient_name'],
+                email: $data['recipient_email'],
+                document: preg_replace('/\D+/', '', $data['recipient_document']),
+                type: $recipientType,
+                birthdate: $this->formatBirthdate($data['birth_date'] ?? null),
+                monthlyIncome: $monthlyIncome,
+                professionalOccupation: $occupation,
+                phoneNumbers: new PagarmeRecipientPhoneNumbersData($this->buildPhoneNumber($data['phone'] ?? null)),
+                address: new PagarmeRecipientAddressData(
+                    street: $data['address'],
+                    complementary: $addressParts['complementary'],
+                    streetNumber: $addressParts['street_number'],
+                    neighborhood: $addressParts['neighborhood'],
+                    city: $data['city'] ?? null,
+                    state: $data['state'] ?? null,
+                    zipCode: preg_replace('/\D+/', '', $data['zip_code']),
+                    referencePoint: $addressParts['reference_point'],
+                ),
+            ),
+            defaultBankAccount: $defaultBankAccount,
+            transferSettings: new PagarmeRecipientTransferSettingsData(
+                transferEnabled: false,
+                transferInterval: 'Daily',
+                transferDay: 0,
+            ),
+            automaticAnticipationSettings: new PagarmeRecipientAutomaticAnticipationSettingsData(
+                enabled: false,
+            ),
+        );
+
+        $recipientData = PagarmeRecipientResponseData::fromArray($this->pagarmeRequest(
+            method: 'POST',
+            path: '/recipients',
+            payload: $payload,
+            idempotencyKey: $this->idempotencyKey($provider->id),
+            errorMessage: 'Erro ao criar recebedor no Pagar.me.',
+        ));
+
+        $recipientId = $recipientData->id();
 
         if (! $recipientId) {
             Log::channel('pagarme')->error('Pagar.me recipient creation returned empty id', [
-                'response' => $recipientData,
+                'response' => $recipientData->toArray(),
             ]);
 
             throw new \RuntimeException('Pagar.me recipient creation returned an empty id.');
@@ -110,7 +100,7 @@ class PagarmeRecipientService
             'recipient_description'          => $data['recipient_description'],
             'recipient_document'             => $data['recipient_document'],
             'recipient_type'                 => $recipientType,
-            'recipient_code'                 => $data['recipient_code'],
+            'recipient_code'                 => $recipientCode,
             'recipient_payment_mode'         => $paymentMode,
             'recipient_default_bank_account' => $bankAccountData,
 
@@ -139,33 +129,18 @@ class PagarmeRecipientService
         $bankAccountData                = $this->normalizeBankAccountPayload($bankAccountData);
         $bankAccountData['holder_name'] = $this->normalizeBankAccountHolderName($bankAccountData['holder_name']);
 
-        $payload = [
-            'bank_account' => $this->filterFilledRecursive($bankAccountData),
-        ];
-
-        $endpoint = $this->pagarmeUrl("/recipients/{$provider->recipient_id}/default-bank-account");
-
-        PagarmeHttpLogger::logRequest('PATCH', $endpoint, $payload);
-
-        $response = $this->pagarmeRequest($provider->id, 'default-bank-account-'.sha1(json_encode($payload)))
-            ->patch($endpoint, $payload);
+        $payload = PagarmeBankAccountUpdateRequestData::fromArray($bankAccountData);
 
-        if ($response->failed()) {
-            Log::channel('pagarme')->error('Pagar.me recipient bank account update failed', [
-                'provider_id'  => $provider->id,
-                'recipient_id' => $provider->recipient_id,
-                'status'       => $response->status(),
-                'body'         => $response->json() ?? $response->body(),
-                'payload'      => $payload,
-            ]);
-
-            throw new \RuntimeException('Erro ao atualizar conta bancaria do recebedor no Pagar.me.');
-        }
-
-        $recipientData = $response->json();
+        $recipientData = PagarmeRecipientResponseData::fromArray($this->pagarmeRequest(
+            method: 'PATCH',
+            path: "/recipients/{$provider->recipient_id}/default-bank-account",
+            payload: $payload,
+            idempotencyKey: $this->idempotencyKey($provider->id, 'default-bank-account-'.sha1(json_encode($payload->toArray()))),
+            errorMessage: 'Erro ao atualizar conta bancaria do recebedor no Pagar.me.',
+        ));
 
         $provider->forceFill([
-            'recipient_default_bank_account' => $recipientData['default_bank_account'] ?? $bankAccountData,
+            'recipient_default_bank_account' => $recipientData->defaultBankAccount()?->toArray() ?: $bankAccountData,
         ])->save();
 
         return $provider->fresh();
@@ -178,41 +153,16 @@ class PagarmeRecipientService
         return "provider-{$providerId}-{$suffix}";
     }
 
-    private function pagarmeRequest(int $providerId, string $suffix = 'recipient')
-    {
-        $secretKey = config('services.pagarme.secret_key');
-
-        if (empty($secretKey)) {
-            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
-
-            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
-        }
-
-        return Http::withBasicAuth($secretKey, '')
-            ->withHeaders([
-                'Idempotency-Key' => $this->idempotencyKey($providerId, $suffix),
-                'Content-Type'    => 'application/json',
-                'Accept'          => 'application/json',
-            ]);
-    }
-
-    private function pagarmeUrl(string $path): string
-    {
-        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
-    }
-
-    //
-
-    private function buildPhoneNumbers(?string $phone): array
+    private function buildPhoneNumber(?string $phone): PagarmeRecipientPhoneData
     {
         $digits = preg_replace('/\D+/', '', (string) $phone) ?? '';
 
         if (strlen($digits) < 10) {
-            return [[
-                'ddd'    => '11',
-                'number' => '999999999',
-                'type'   => 'mobile',
-            ]];
+            return new PagarmeRecipientPhoneData(
+                ddd: '11',
+                number: '999999999',
+                type: 'mobile',
+            );
         }
 
         if (str_starts_with($digits, '55')) {
@@ -223,19 +173,30 @@ class PagarmeRecipientService
 
         $number = substr($digits, 2);
 
-        return [[
-            'ddd'    => $areaCode,
-            'number' => $number,
-            'type'   => 'mobile',
-        ]];
+        return new PagarmeRecipientPhoneData(
+            ddd: $areaCode,
+            number: $number,
+            type: 'mobile',
+        );
     }
 
-    private function buildRecipientCode(Provider $provider, array $data): string
+    private function ensureRecipientCode(Provider $provider): string
     {
-        $baseCode = preg_replace('/\D+/', '', (string) ($data['recipient_code'] ?? '')) ?: (string) $provider->id;
+        if ($this->hasUuidCode($provider->recipient_code, 'provider')) {
+            return $provider->recipient_code;
+        }
+
+        $recipientCode = 'provider-'.(string) Str::uuid();
+
+        $provider->forceFill(['recipient_code' => $recipientCode])->save();
 
-        // Pagar.me exige external_id unico; usar code deterministico por provider evita colisao entre contas.
-        return Str::limit("provider-{$provider->id}-{$baseCode}", 52, '');
+        return $recipientCode;
+    }
+
+    private function hasUuidCode(?string $code, string $prefix): bool
+    {
+        return is_string($code)
+            && 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;
     }
 
     private function extractAddressParts(array $data): array

+ 19 - 5
app/Services/PaymentService.php

@@ -145,8 +145,11 @@ class PaymentService
         $platformFee   = round($serviceAmount * 0.11, 2);
         $grossAmount   = round($serviceAmount + $platformFee, 2);
         $items         = $this->buildOrderItems($schedule, $grossAmount);
+
         $this->ensureCustomerPhoneForPayment($schedule, $options);
-        $customer            = $this->buildCustomerPayload(schedule: $schedule, options: $options, requirePhone: true);
+
+        $customer = $this->buildCustomerPayload(schedule: $schedule, options: $options, requirePhone: true);
+
         $platformRecipientId = config('services.pagarme.platform_recipient_id');
 
         if ($platformFee > 0 && empty($platformRecipientId)) {
@@ -159,6 +162,7 @@ class PaymentService
             'provider_id'              => $schedule->provider_id,
             'client_payment_method_id' => $paymentMethod === 'credit_card' ? $clientPaymentMethod->id : null,
             'gateway_provider'         => 'pagarme',
+            'gateway_code'             => 'payment-'.(string) Str::uuid(),
             'payment_method'           => $paymentMethod,
             'status'                   => 'pending',
             'gross_amount'             => $grossAmount,
@@ -207,6 +211,12 @@ class PaymentService
             ];
         }
 
+        $pixOptions = config('services.pagarme.pix_disable_split')
+            ? []
+            : ['split' => $split];
+
+        \Log::info('pixOptions: '.json_encode($pixOptions));
+
         try {
             $creditCardReference = $paymentMethod === 'credit_card'
                 ? $this->resolveCreditCardReference($clientPaymentMethod, $options)
@@ -240,11 +250,15 @@ class PaymentService
                     items: $items,
                     customer: $customer,
                     pix: [
-                        'expires_at' => $payment->expires_at?->toISOString(),
-                    ],
-                    options: [
-                        'split' => $split,
+                        'expires_in'             => '1800',
+                        'additional_information' => [
+                            [
+                                'name'  => 'Agendamento',
+                                'value' => (string) $schedule->id,
+                            ],
+                        ],
                     ],
+                    options: $pixOptions,
                 );
         } catch (\Throwable $e) {
             $payment->forceFill([

+ 1 - 0
config/services.php

@@ -41,6 +41,7 @@ return [
         'base_url'              => env('PAGARME_BASE_URL', 'https://api.pagar.me/core/v5'),
         'webhook_token'         => env('PAGARME_WEBHOOK_TOKEN'),
         'platform_recipient_id' => env('PAGARME_PLATFORM_RECIPIENT_ID'),
+        'pix_disable_split'     => env('PAGARME_PIX_DISABLE_SPLIT', false),
     ],
 
 ];

+ 23 - 0
database/migrations/2026_05_22_193327_add_gateway_code_to_payments_table.php

@@ -0,0 +1,23 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::table('payments', function (Blueprint $table) {
+            $table->string('gateway_code')->nullable()->after('gateway_provider')->unique();
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('payments', function (Blueprint $table) {
+            $table->dropUnique(['gateway_code']);
+            $table->dropColumn('gateway_code');
+        });
+    }
+};

+ 22 - 0
database/migrations/2026_05_22_193330_add_unique_index_to_provider_recipient_code.php

@@ -0,0 +1,22 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::table('providers', function (Blueprint $table) {
+            $table->unique('recipient_code');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('providers', function (Blueprint $table) {
+            $table->dropUnique(['recipient_code']);
+        });
+    }
+};