diff --git a/CLAUDE.md b/CLAUDE.md index 94252ff..00681c0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -135,6 +135,25 @@ e rode `php examples/asaas/charges.php` (ou `make asaas resource=charges`). `PHPay\Efi\`, `PHPay\MercadoPago\` → `src/Gateways//`. Gateway novo precisa de um root novo no `composer.json` — não introduza `PHPay\Gateways\...`. +## Cliente + +Use **`PHPay\Support\Customer`**. O mesmo campo tem seis grafias entre os gateways +(`cpfCnpj`, `tax_id`, `document`, `taxId`, `taxID`, `cpf_cnpj`), e o VO é a forma única. + +- `Customer::make()` limpa pontuação de documento e telefone; o construtor não. +- **O mapeamento mora na classe `*CustomerRequest` de cada gateway**, em + `fromCustomer(Customer): array` — ela já detém o conhecimento do schema daquele + gateway, então validação e mapeamento ficam juntos. Gateway novo com cliente deve + ter o mesmo método. +- Todo `setCustomer()` e `customer()` aceita `Customer|array`. Array continua + funcionando; não remova esse caminho sem major. +- Lógica derivada mora no VO, não nos gateways: `isIndividual()`, `documentType()`, + `firstName()`/`lastName()`, `phoneParts()`. Se um gateway novo precisar de outra + derivação, acrescente lá em vez de calcular no mapper. +- `withExtra()` é para campo específico de um gateway. **Nenhum campo obrigatório + precisa dele hoje** — se um gateway novo precisar, é sinal de que o VO está + faltando algo. + ## Valores monetários Use **`PHPay\Support\Money`**. Os gateways discordam da unidade — Asaas e Mercado diff --git a/README.md b/README.md index 12517f4..d08bed3 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,50 @@ Nos dois últimos, `isSandbox()` diz em qual ambiente você está: > **Nunca** versione credenciais. Os arquivos `examples/*/credentials.php` são > ignorados pelo git por padrão. +### Cliente + +O mesmo campo tem **seis grafias** entre os gateways: `cpfCnpj` no Asaas, +`tax_id` no PagBank, `document` no Pagar.me, `taxId` no AbacatePay, `taxID` no +Woovi, `cpf_cnpj` no Efí. Código escrito para um não migra para outro, e nada +no tipo avisa. + +`Customer` é a forma única. Cada gateway mapeia para o formato dele: + +```php +use PHPay\Support\Customer; + +$cliente = Customer::make( + name: 'Mário Lucas', + document: '123.456.789-01', // pontuação é limpa + email: 'fale@phpay.io', + phone: '(11) 94002-8922', +); + +$phpay->charge()->setCustomer($cliente); // funciona nos nove +``` + +Ele também carrega o que cada gateway deriva do cliente, e que antes ficava +espalhado: + +```php +$cliente->isIndividual(); // CPF: o Pagar.me precisa como type: 'individual' +$cliente->documentType(); // 'CPF' | 'CNPJ': a Cielo quer em IdentityType +$cliente->firstName(); // o Mercado Pago quer nome e sobrenome separados +$cliente->phoneParts(); // ['country' => '55', 'area' => '11', ...] para o PagBank +``` + +Para um cliente que já existe no gateway, ou para campos que só aquele gateway +tem: + +```php +$cliente->withId('cus_000006337812'); // reaproveita em vez de criar +$cliente->withExtra(['externalReference' => 'x']); // vai junto no payload +``` + +> O `withExtra()` existe para o que é específico de um gateway — endereço, +> data de nascimento, referência externa. **Nenhum campo obrigatório de +> nenhum dos nove gateways precisa dele**: o value object cobre todos. + ### Unidade monetária Os gateways discordam sobre a unidade, e **errar não quebra a integração — ela diff --git a/src/Contracts/SupportsCustomers.php b/src/Contracts/SupportsCustomers.php index b849540..3bbd798 100644 --- a/src/Contracts/SupportsCustomers.php +++ b/src/Contracts/SupportsCustomers.php @@ -2,6 +2,8 @@ namespace PHPay\Contracts; +use PHPay\Support\Customer as CustomerData; + /** * the gateway exposes customers as a resource of their own. */ @@ -13,5 +15,5 @@ interface SupportsCustomers extends GatewayInterface * @param array $customer * @return object */ - public function customer(array $customer = []): object; + public function customer(CustomerData|array $customer = []): object; } diff --git a/src/Gateways/AbacatePay/AbacatePayGateway.php b/src/Gateways/AbacatePay/AbacatePayGateway.php index 9da9d44..b65c305 100644 --- a/src/Gateways/AbacatePay/AbacatePayGateway.php +++ b/src/Gateways/AbacatePay/AbacatePayGateway.php @@ -4,9 +4,11 @@ use GuzzleHttp\Client; use PHPay\AbacatePay\Interface\AbacatePayGatewayInterface; +use PHPay\AbacatePay\Requests\AbacatePayCustomerRequest; use PHPay\AbacatePay\Resources\Charge\Charge; use PHPay\AbacatePay\Resources\Coupon\Coupon; use PHPay\AbacatePay\Resources\Customer\Customer; +use PHPay\Support\Customer as CustomerData; class AbacatePayGateway implements AbacatePayGatewayInterface { @@ -43,8 +45,12 @@ public function name(): string * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer + public function customer(CustomerData|array $customer = []): Customer { + if ($customer instanceof CustomerData) { + $customer = AbacatePayCustomerRequest::fromCustomer($customer); + } + return new Customer($this->token, $customer, $this->client); } diff --git a/src/Gateways/AbacatePay/Interface/AbacatePayGatewayInterface.php b/src/Gateways/AbacatePay/Interface/AbacatePayGatewayInterface.php index c244fe6..dfdbc40 100644 --- a/src/Gateways/AbacatePay/Interface/AbacatePayGatewayInterface.php +++ b/src/Gateways/AbacatePay/Interface/AbacatePayGatewayInterface.php @@ -6,6 +6,7 @@ use PHPay\AbacatePay\Resources\Coupon\Coupon; use PHPay\AbacatePay\Resources\Customer\Customer; use PHPay\Contracts\{SupportsCharges, SupportsCustomers}; +use PHPay\Support\Customer as CustomerData; /** * the AbacatePay gateway offers customers and charges. @@ -28,7 +29,7 @@ interface AbacatePayGatewayInterface extends * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer; + public function customer(CustomerData|array $customer = []): Customer; /** * get resource charge from gateway. diff --git a/src/Gateways/AbacatePay/Requests/AbacatePayCustomerRequest.php b/src/Gateways/AbacatePay/Requests/AbacatePayCustomerRequest.php index c5cb752..4452ec2 100644 --- a/src/Gateways/AbacatePay/Requests/AbacatePayCustomerRequest.php +++ b/src/Gateways/AbacatePay/Requests/AbacatePayCustomerRequest.php @@ -3,6 +3,7 @@ namespace PHPay\AbacatePay\Requests; use PHPay\Exceptions\ValidationException; +use PHPay\Support\Customer; class AbacatePayCustomerRequest { @@ -57,4 +58,21 @@ public static function messages(): object 'taxId' => 'O campo taxId é obrigatório — é o CPF ou CNPJ do cliente.', ]; } + + /** + * map the library's Customer onto the payload AbacatePay expects. + * + * @param Customer $customer + * @return array + */ + public static function fromCustomer(Customer $customer): array + { + return array_filter([ + 'name' => $customer->name, + 'email' => $customer->email, + 'cellphone' => $customer->phone, + 'taxId' => $customer->document, + ], fn ($value) => $value !== null) + $customer->extra(); + } + } diff --git a/src/Gateways/AbacatePay/Resources/Charge/Charge.php b/src/Gateways/AbacatePay/Resources/Charge/Charge.php index bc8cc7e..38d2f95 100644 --- a/src/Gateways/AbacatePay/Resources/Charge/Charge.php +++ b/src/Gateways/AbacatePay/Resources/Charge/Charge.php @@ -4,11 +4,11 @@ use GuzzleHttp\Client; use PHPay\AbacatePay\Enums\{BillingFrequencyEnum, BillingMethodEnum}; -use PHPay\AbacatePay\Requests\AbacatePayBillingRequest; +use PHPay\AbacatePay\Requests\{AbacatePayBillingRequest, AbacatePayCustomerRequest}; use PHPay\AbacatePay\Resources\Charge\Interface\ChargeInterface; use PHPay\AbacatePay\Traits\HasAbacatePayClient; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\Support\Money; +use PHPay\Support\{Customer as CustomerData, Money}; /** * billings of the AbacatePay API. @@ -86,8 +86,16 @@ public function setCustomerId(string $customerId): ChargeInterface * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface + public function setCustomer(CustomerData|array $customer): ChargeInterface { + if ($customer instanceof CustomerData) { + if ($customer->id !== null) { + return $this->setCustomerId($customer->id); + } + + $customer = AbacatePayCustomerRequest::fromCustomer($customer); + } + if (isset($customer['id']) && is_string($customer['id']) && $customer['id'] !== '') { return $this->setCustomerId($customer['id']); } diff --git a/src/Gateways/AbacatePay/Resources/Charge/Interface/ChargeInterface.php b/src/Gateways/AbacatePay/Resources/Charge/Interface/ChargeInterface.php index c41303f..9ad303a 100644 --- a/src/Gateways/AbacatePay/Resources/Charge/Interface/ChargeInterface.php +++ b/src/Gateways/AbacatePay/Resources/Charge/Interface/ChargeInterface.php @@ -2,7 +2,7 @@ namespace PHPay\AbacatePay\Resources\Charge\Interface; -use PHPay\Support\Money; +use PHPay\Support\{Customer as CustomerData, Money}; interface ChargeInterface { @@ -28,7 +28,7 @@ public function setCustomerId(string $customerId): ChargeInterface; * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface; + public function setCustomer(CustomerData|array $customer): ChargeInterface; /** * set the products being charged diff --git a/src/Gateways/Asaas/AsaasGateway.php b/src/Gateways/Asaas/AsaasGateway.php index 0400c14..406f3bd 100644 --- a/src/Gateways/Asaas/AsaasGateway.php +++ b/src/Gateways/Asaas/AsaasGateway.php @@ -4,11 +4,13 @@ use GuzzleHttp\Client; use PHPay\Asaas\Interface\AsaasGatewayInterface; +use PHPay\Asaas\Requests\AsaasCustomerRequest; use PHPay\Asaas\Resources\Charge\Charge; use PHPay\Asaas\Resources\Customer\Customer; use PHPay\Asaas\Resources\Pix\Pix; use PHPay\Asaas\Resources\Subscription\Subscription; use PHPay\Asaas\Resources\Webhook\Webhook; +use PHPay\Support\Customer as CustomerData; class AsaasGateway implements AsaasGatewayInterface { @@ -42,8 +44,12 @@ public function name(): string * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer + public function customer(CustomerData|array $customer = []): Customer { + if ($customer instanceof CustomerData) { + $customer = AsaasCustomerRequest::fromCustomer($customer); + } + return new Customer($this->token, $customer, $this->sandbox, $this->client); } diff --git a/src/Gateways/Asaas/Interface/AsaasGatewayInterface.php b/src/Gateways/Asaas/Interface/AsaasGatewayInterface.php index 9fc4a00..f1ef170 100644 --- a/src/Gateways/Asaas/Interface/AsaasGatewayInterface.php +++ b/src/Gateways/Asaas/Interface/AsaasGatewayInterface.php @@ -8,6 +8,7 @@ use PHPay\Asaas\Resources\Subscription\Subscription; use PHPay\Asaas\Resources\Webhook\Webhook; use PHPay\Contracts\{SupportsCharges, SupportsCustomers, SupportsPixKeys, SupportsSubscriptions, SupportsWebhooks}; +use PHPay\Support\Customer as CustomerData; /** * the Asaas gateway offers every capability the library models. @@ -25,7 +26,7 @@ interface AsaasGatewayInterface extends * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer; + public function customer(CustomerData|array $customer = []): Customer; /** * get resource charge from gateway. diff --git a/src/Gateways/Asaas/Requests/AsaasCustomerRequest.php b/src/Gateways/Asaas/Requests/AsaasCustomerRequest.php index ce460ee..483391a 100644 --- a/src/Gateways/Asaas/Requests/AsaasCustomerRequest.php +++ b/src/Gateways/Asaas/Requests/AsaasCustomerRequest.php @@ -3,6 +3,7 @@ namespace PHPay\Asaas\Requests; use PHPay\Exceptions\ValidationException; +use PHPay\Support\Customer; class AsaasCustomerRequest { @@ -38,4 +39,21 @@ public static function messages(): object 'cpfCnpj' => 'CPF/CNPJ do cliente é obrigatório e deve ser uma string não vazia.', ]; } + + /** + * map the library's Customer onto the payload Asaas expects. + * + * @param Customer $customer + * @return array + */ + public static function fromCustomer(Customer $customer): array + { + return array_filter([ + 'name' => $customer->name, + 'cpfCnpj' => $customer->document, + 'email' => $customer->email, + 'mobilePhone' => $customer->phone, + ], fn ($value) => $value !== null) + $customer->extra(); + } + } diff --git a/src/Gateways/Asaas/Resources/Charge/Charge.php b/src/Gateways/Asaas/Resources/Charge/Charge.php index 0e42c8f..52cf999 100644 --- a/src/Gateways/Asaas/Resources/Charge/Charge.php +++ b/src/Gateways/Asaas/Resources/Charge/Charge.php @@ -3,12 +3,12 @@ namespace PHPay\Asaas\Resources\Charge; use GuzzleHttp\Client; -use PHPay\Asaas\Requests\AsaasChargeRequest; +use PHPay\Asaas\Requests\{AsaasChargeRequest, AsaasCustomerRequest}; use PHPay\Asaas\Resources\Charge\Interface\ChargeInterface; use PHPay\Asaas\Resources\Customer\Customer; use PHPay\Asaas\Traits\HasAsaasClient; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\Support\Money; +use PHPay\Support\{Customer as CustomerData, Money}; class Charge implements ChargeInterface { @@ -113,8 +113,16 @@ public function setCustomerId(string $customerId): ChargeInterface * @return ChargeInterface * @throws ValidationException|ApiException */ - public function setCustomer(array $customer): ChargeInterface + public function setCustomer(CustomerData|array $customer): ChargeInterface { + if ($customer instanceof CustomerData) { + if ($customer->id !== null) { + return $this->setCustomerId($customer->id); + } + + $customer = AsaasCustomerRequest::fromCustomer($customer); + } + if (isset($customer['id']) && is_string($customer['id']) && $customer['id'] !== '') { return $this->setCustomerId($customer['id']); } diff --git a/src/Gateways/Asaas/Resources/Charge/Interface/ChargeInterface.php b/src/Gateways/Asaas/Resources/Charge/Interface/ChargeInterface.php index d6ebfc5..fefe1ec 100644 --- a/src/Gateways/Asaas/Resources/Charge/Interface/ChargeInterface.php +++ b/src/Gateways/Asaas/Resources/Charge/Interface/ChargeInterface.php @@ -2,7 +2,7 @@ namespace PHPay\Asaas\Resources\Charge\Interface; -use PHPay\Support\Money; +use PHPay\Support\{Customer as CustomerData, Money}; interface ChargeInterface { @@ -67,7 +67,7 @@ public function setCustomerId(string $customerId): ChargeInterface; * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface; + public function setCustomer(CustomerData|array $customer): ChargeInterface; /** * set the amount of the charge, in reais diff --git a/src/Gateways/Asaas/Resources/Subscription/Interface/SubscriptionInterface.php b/src/Gateways/Asaas/Resources/Subscription/Interface/SubscriptionInterface.php index d53977a..175af7e 100644 --- a/src/Gateways/Asaas/Resources/Subscription/Interface/SubscriptionInterface.php +++ b/src/Gateways/Asaas/Resources/Subscription/Interface/SubscriptionInterface.php @@ -2,6 +2,8 @@ namespace PHPay\Asaas\Resources\Subscription\Interface; +use PHPay\Support\Customer as CustomerData; + interface SubscriptionInterface { /** @@ -18,7 +20,7 @@ public function setCustomerId(string $customerId): SubscriptionInterface; * @param array $customer * @return SubscriptionInterface */ - public function setCustomer(array $customer): SubscriptionInterface; + public function setCustomer(CustomerData|array $customer): SubscriptionInterface; /** * create subscription diff --git a/src/Gateways/Asaas/Resources/Subscription/Subscription.php b/src/Gateways/Asaas/Resources/Subscription/Subscription.php index a4b823b..b37741b 100644 --- a/src/Gateways/Asaas/Resources/Subscription/Subscription.php +++ b/src/Gateways/Asaas/Resources/Subscription/Subscription.php @@ -3,11 +3,13 @@ namespace PHPay\Asaas\Resources\Subscription; use GuzzleHttp\Client; +use PHPay\Asaas\Requests\AsaasCustomerRequest; use PHPay\Asaas\Resources\Customer\Customer; use PHPay\Asaas\Resources\Subscription\Interface\SubscriptionInterface; use PHPay\Asaas\Resources\Subscription\Requests\StoreSubscriptionAsaasRequest; use PHPay\Asaas\Traits\HasAsaasClient; use PHPay\Exceptions\{ApiException, ValidationException}; +use PHPay\Support\Customer as CustomerData; class Subscription implements SubscriptionInterface { @@ -65,8 +67,16 @@ public function setCustomerId(string $customerId): SubscriptionInterface * @return SubscriptionInterface * @throws ValidationException|ApiException */ - public function setCustomer(array $customer): SubscriptionInterface + public function setCustomer(CustomerData|array $customer): SubscriptionInterface { + if ($customer instanceof CustomerData) { + if ($customer->id !== null) { + return $this->setCustomerId($customer->id); + } + + $customer = AsaasCustomerRequest::fromCustomer($customer); + } + if (isset($customer['id']) && is_string($customer['id']) && $customer['id'] !== '') { return $this->setCustomerId($customer['id']); } diff --git a/src/Gateways/Cielo/Requests/CieloSaleRequest.php b/src/Gateways/Cielo/Requests/CieloSaleRequest.php index 11dd9ab..9cf8929 100644 --- a/src/Gateways/Cielo/Requests/CieloSaleRequest.php +++ b/src/Gateways/Cielo/Requests/CieloSaleRequest.php @@ -4,6 +4,7 @@ use PHPay\Cielo\Enums\PaymentTypeEnum; use PHPay\Exceptions\ValidationException; +use PHPay\Support\Customer; class CieloSaleRequest { @@ -71,4 +72,23 @@ public static function messages(): object 'amount' => 'O campo Payment.Amount é obrigatório e deve ser um inteiro em CENTAVOS maior que zero. A Cielo não aceita valor decimal: R$ 157,00 é 15700.', ]; } + + /** + * map the library's Customer onto the payload Cielo expects. + * + * Cielo is the only gateway that capitalises the keys. + * + * @param Customer $customer + * @return array + */ + public static function fromCustomer(Customer $customer): array + { + return array_filter([ + 'Name' => $customer->name, + 'Email' => $customer->email, + 'Identity' => $customer->document, + 'IdentityType' => $customer->documentType(), + ], fn ($value) => $value !== null) + $customer->extra(); + } + } diff --git a/src/Gateways/Cielo/Resources/Charge/Charge.php b/src/Gateways/Cielo/Resources/Charge/Charge.php index dce5a39..4fb55ba 100644 --- a/src/Gateways/Cielo/Resources/Charge/Charge.php +++ b/src/Gateways/Cielo/Resources/Charge/Charge.php @@ -8,7 +8,7 @@ use PHPay\Cielo\Resources\Charge\Interface\ChargeInterface; use PHPay\Cielo\Traits\HasCieloClient; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\Support\Money; +use PHPay\Support\{Customer, Money}; /** * sales of the Cielo E-commerce API 3.0. @@ -95,8 +95,12 @@ public function setOrderId(string $merchantOrderId): ChargeInterface * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface + public function setCustomer(Customer|array $customer): ChargeInterface { + if ($customer instanceof Customer) { + $customer = CieloSaleRequest::fromCustomer($customer); + } + $this->sale['Customer'] = $customer; return $this; diff --git a/src/Gateways/Cielo/Resources/Charge/Interface/ChargeInterface.php b/src/Gateways/Cielo/Resources/Charge/Interface/ChargeInterface.php index e0904ad..1705bb7 100644 --- a/src/Gateways/Cielo/Resources/Charge/Interface/ChargeInterface.php +++ b/src/Gateways/Cielo/Resources/Charge/Interface/ChargeInterface.php @@ -2,7 +2,7 @@ namespace PHPay\Cielo\Resources\Charge\Interface; -use PHPay\Support\Money; +use PHPay\Support\{Customer, Money}; interface ChargeInterface { @@ -28,7 +28,7 @@ public function setOrderId(string $merchantOrderId): ChargeInterface; * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface; + public function setCustomer(Customer|array $customer): ChargeInterface; /** * pay with Pix diff --git a/src/Gateways/Cielo/Resources/Subscription/Interface/SubscriptionInterface.php b/src/Gateways/Cielo/Resources/Subscription/Interface/SubscriptionInterface.php index e13f281..0de6ee4 100644 --- a/src/Gateways/Cielo/Resources/Subscription/Interface/SubscriptionInterface.php +++ b/src/Gateways/Cielo/Resources/Subscription/Interface/SubscriptionInterface.php @@ -3,7 +3,7 @@ namespace PHPay\Cielo\Resources\Subscription\Interface; use PHPay\Cielo\Enums\RecurrentIntervalEnum; -use PHPay\Support\Money; +use PHPay\Support\{Customer, Money}; interface SubscriptionInterface { @@ -21,7 +21,7 @@ public function setOrderId(string $merchantOrderId): SubscriptionInterface; * @param array $customer * @return SubscriptionInterface */ - public function setCustomer(array $customer): SubscriptionInterface; + public function setCustomer(Customer|array $customer): SubscriptionInterface; /** * set the credit card the recurrence charges diff --git a/src/Gateways/Cielo/Resources/Subscription/Subscription.php b/src/Gateways/Cielo/Resources/Subscription/Subscription.php index a163c6d..884c3c2 100644 --- a/src/Gateways/Cielo/Resources/Subscription/Subscription.php +++ b/src/Gateways/Cielo/Resources/Subscription/Subscription.php @@ -4,11 +4,11 @@ use GuzzleHttp\Client; use PHPay\Cielo\Enums\{PaymentTypeEnum, RecurrentIntervalEnum}; -use PHPay\Cielo\Requests\CieloRecurrentRequest; +use PHPay\Cielo\Requests\{CieloRecurrentRequest, CieloSaleRequest}; use PHPay\Cielo\Resources\Subscription\Interface\SubscriptionInterface; use PHPay\Cielo\Traits\HasCieloClient; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\Support\Money; +use PHPay\Support\{Customer, Money}; /** * recurrences of the Cielo E-commerce API 3.0. @@ -83,8 +83,12 @@ public function setOrderId(string $merchantOrderId): SubscriptionInterface * @param array $customer * @return SubscriptionInterface */ - public function setCustomer(array $customer): SubscriptionInterface + public function setCustomer(Customer|array $customer): SubscriptionInterface { + if ($customer instanceof Customer) { + $customer = CieloSaleRequest::fromCustomer($customer); + } + $this->sale['Customer'] = $customer; return $this; diff --git a/src/Gateways/Efi/Requests/EfiCustomerRequest.php b/src/Gateways/Efi/Requests/EfiCustomerRequest.php index 190c39a..8e05973 100644 --- a/src/Gateways/Efi/Requests/EfiCustomerRequest.php +++ b/src/Gateways/Efi/Requests/EfiCustomerRequest.php @@ -3,6 +3,7 @@ namespace PHPay\Efi\Requests; use PHPay\Exceptions\ValidationException; +use PHPay\Support\Customer; class EfiCustomerRequest { @@ -43,4 +44,21 @@ public static function messages(): object 'cpfCnpjLength' => 'CPF/CNPJ deve conter 11 dígitos (CPF) ou 14 dígitos (CNPJ), somente números.', ]; } + + /** + * map the library's Customer onto the payload Efí expects. + * + * @param Customer $customer + * @return array + */ + public static function fromCustomer(Customer $customer): array + { + return array_filter([ + 'name' => $customer->name, + 'cpf_cnpj' => $customer->document, + 'email' => $customer->email, + 'phone_number' => $customer->phone, + ], fn ($value) => $value !== null) + $customer->extra(); + } + } diff --git a/src/Gateways/Efi/Resources/Charge/Charge.php b/src/Gateways/Efi/Resources/Charge/Charge.php index 64b1cfd..d3c8640 100644 --- a/src/Gateways/Efi/Resources/Charge/Charge.php +++ b/src/Gateways/Efi/Resources/Charge/Charge.php @@ -7,7 +7,7 @@ use PHPay\Efi\Resources\Charge\Interface\ChargeInterface; use PHPay\Efi\Traits\HasEfiClient; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\Support\Money; +use PHPay\Support\{Customer, Money}; class Charge implements ChargeInterface { @@ -74,8 +74,12 @@ public function __construct( * @param array $customer * @return Charge */ - public function setCustomer(array $customer): Charge + public function setCustomer(Customer|array $customer): Charge { + if ($customer instanceof Customer) { + $customer = EfiCustomerRequest::fromCustomer($customer); + } + $this->customer = $this->bootCustomer($customer); return $this; diff --git a/src/Gateways/Efi/Resources/Charge/Interface/ChargeInterface.php b/src/Gateways/Efi/Resources/Charge/Interface/ChargeInterface.php index 2d4ef23..400d705 100644 --- a/src/Gateways/Efi/Resources/Charge/Interface/ChargeInterface.php +++ b/src/Gateways/Efi/Resources/Charge/Interface/ChargeInterface.php @@ -3,7 +3,7 @@ namespace PHPay\Efi\Resources\Charge\Interface; use PHPay\Efi\Resources\Charge\Charge; -use PHPay\Support\Money; +use PHPay\Support\{Customer, Money}; interface ChargeInterface { @@ -36,7 +36,7 @@ public function find(string $id): array; * @param array $customer * @return Charge */ - public function setCustomer(array $customer): Charge; + public function setCustomer(Customer|array $customer): Charge; /** * set query params diff --git a/src/Gateways/MercadoPago/Interface/MercadoPagoGatewayInterface.php b/src/Gateways/MercadoPago/Interface/MercadoPagoGatewayInterface.php index 7fdb792..584c96b 100644 --- a/src/Gateways/MercadoPago/Interface/MercadoPagoGatewayInterface.php +++ b/src/Gateways/MercadoPago/Interface/MercadoPagoGatewayInterface.php @@ -6,6 +6,7 @@ use PHPay\MercadoPago\Resources\Charge\Charge; use PHPay\MercadoPago\Resources\Customer\Customer; use PHPay\MercadoPago\Resources\Subscription\Subscription; +use PHPay\Support\Customer as CustomerData; /** * the Mercado Pago gateway offers customers, charges and subscriptions. @@ -28,7 +29,7 @@ interface MercadoPagoGatewayInterface extends * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer; + public function customer(CustomerData|array $customer = []): Customer; /** * get resource charge from gateway. diff --git a/src/Gateways/MercadoPago/MercadoPagoGateway.php b/src/Gateways/MercadoPago/MercadoPagoGateway.php index fde594a..860a192 100644 --- a/src/Gateways/MercadoPago/MercadoPagoGateway.php +++ b/src/Gateways/MercadoPago/MercadoPagoGateway.php @@ -4,9 +4,11 @@ use GuzzleHttp\Client; use PHPay\MercadoPago\Interface\MercadoPagoGatewayInterface; +use PHPay\MercadoPago\Requests\MercadoPagoCustomerRequest; use PHPay\MercadoPago\Resources\Charge\Charge; use PHPay\MercadoPago\Resources\Customer\Customer; use PHPay\MercadoPago\Resources\Subscription\Subscription; +use PHPay\Support\Customer as CustomerData; class MercadoPagoGateway implements MercadoPagoGatewayInterface { @@ -57,8 +59,12 @@ public function isSandbox(): bool * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer + public function customer(CustomerData|array $customer = []): Customer { + if ($customer instanceof CustomerData) { + $customer = MercadoPagoCustomerRequest::fromCustomer($customer); + } + return new Customer($this->accessToken, $customer, $this->client); } diff --git a/src/Gateways/MercadoPago/Requests/MercadoPagoCustomerRequest.php b/src/Gateways/MercadoPago/Requests/MercadoPagoCustomerRequest.php index 4e4ce5b..5ac52f9 100644 --- a/src/Gateways/MercadoPago/Requests/MercadoPagoCustomerRequest.php +++ b/src/Gateways/MercadoPago/Requests/MercadoPagoCustomerRequest.php @@ -3,6 +3,7 @@ namespace PHPay\MercadoPago\Requests; use PHPay\Exceptions\ValidationException; +use PHPay\Support\Customer; class MercadoPagoCustomerRequest { @@ -36,4 +37,32 @@ public static function messages(): object 'email' => 'O campo email é obrigatório e deve ser um e-mail válido.', ]; } + + /** + * map the library's Customer onto the payer Mercado Pago expects. + * + * Mercado Pago wants the name split in two and the document inside an + * identification object. + * + * @param Customer $customer + * @return array + */ + public static function fromCustomer(Customer $customer): array + { + $payload = array_filter([ + 'email' => $customer->email, + 'first_name' => $customer->firstName(), + 'last_name' => $customer->lastName(), + ], fn ($value) => $value !== null); + + if ($customer->documentType() !== null) { + $payload['identification'] = [ + 'type' => $customer->documentType(), + 'number' => $customer->document, + ]; + } + + return $payload + $customer->extra(); + } + } diff --git a/src/Gateways/MercadoPago/Resources/Charge/Charge.php b/src/Gateways/MercadoPago/Resources/Charge/Charge.php index c316a05..a67f8fb 100644 --- a/src/Gateways/MercadoPago/Resources/Charge/Charge.php +++ b/src/Gateways/MercadoPago/Resources/Charge/Charge.php @@ -4,10 +4,10 @@ use GuzzleHttp\Client; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\MercadoPago\Requests\MercadoPagoChargeRequest; +use PHPay\MercadoPago\Requests\{MercadoPagoChargeRequest, MercadoPagoCustomerRequest}; use PHPay\MercadoPago\Resources\Charge\Interface\ChargeInterface; use PHPay\MercadoPago\Traits\HasMercadoPagoClient; -use PHPay\Support\Money; +use PHPay\Support\{Customer as CustomerData, Money}; class Charge implements ChargeInterface { @@ -87,8 +87,12 @@ public function setAmount(Money|int|float $amount): ChargeInterface * @param array $payer * @return ChargeInterface */ - public function setPayer(array $payer): ChargeInterface + public function setPayer(CustomerData|array $payer): ChargeInterface { + if ($payer instanceof CustomerData) { + $payer = MercadoPagoCustomerRequest::fromCustomer($payer); + } + $this->charge['payer'] = $payer; return $this; diff --git a/src/Gateways/MercadoPago/Resources/Charge/Interface/ChargeInterface.php b/src/Gateways/MercadoPago/Resources/Charge/Interface/ChargeInterface.php index b74915c..556149c 100644 --- a/src/Gateways/MercadoPago/Resources/Charge/Interface/ChargeInterface.php +++ b/src/Gateways/MercadoPago/Resources/Charge/Interface/ChargeInterface.php @@ -2,7 +2,7 @@ namespace PHPay\MercadoPago\Resources\Charge\Interface; -use PHPay\Support\Money; +use PHPay\Support\{Customer as CustomerData, Money}; interface ChargeInterface { @@ -28,7 +28,7 @@ public function setAmount(Money|int|float $amount): ChargeInterface; * @param array $payer * @return ChargeInterface */ - public function setPayer(array $payer): ChargeInterface; + public function setPayer(CustomerData|array $payer): ChargeInterface; /** * set the idempotency key used on create diff --git a/src/Gateways/PagBank/Interface/PagBankGatewayInterface.php b/src/Gateways/PagBank/Interface/PagBankGatewayInterface.php index e9528fb..c0665bb 100644 --- a/src/Gateways/PagBank/Interface/PagBankGatewayInterface.php +++ b/src/Gateways/PagBank/Interface/PagBankGatewayInterface.php @@ -6,6 +6,7 @@ use PHPay\PagBank\Resources\Charge\Charge; use PHPay\PagBank\Resources\Customer\Customer; use PHPay\PagBank\Resources\Subscription\Subscription; +use PHPay\Support\Customer as CustomerData; /** * the PagBank gateway offers charges, subscribers and subscriptions. @@ -28,7 +29,7 @@ interface PagBankGatewayInterface extends * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer; + public function customer(CustomerData|array $customer = []): Customer; /** * get resource charge from gateway. diff --git a/src/Gateways/PagBank/PagBankGateway.php b/src/Gateways/PagBank/PagBankGateway.php index 570c26c..bd068a2 100644 --- a/src/Gateways/PagBank/PagBankGateway.php +++ b/src/Gateways/PagBank/PagBankGateway.php @@ -4,9 +4,11 @@ use GuzzleHttp\Client; use PHPay\PagBank\Interface\PagBankGatewayInterface; +use PHPay\PagBank\Requests\PagBankCustomerRequest; use PHPay\PagBank\Resources\Charge\Charge; use PHPay\PagBank\Resources\Customer\Customer; use PHPay\PagBank\Resources\Subscription\Subscription; +use PHPay\Support\Customer as CustomerData; class PagBankGateway implements PagBankGatewayInterface { @@ -40,8 +42,12 @@ public function name(): string * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer + public function customer(CustomerData|array $customer = []): Customer { + if ($customer instanceof CustomerData) { + $customer = PagBankCustomerRequest::fromCustomer($customer); + } + return new Customer($this->token, $customer, $this->sandbox, $this->client); } diff --git a/src/Gateways/PagBank/Requests/PagBankCustomerRequest.php b/src/Gateways/PagBank/Requests/PagBankCustomerRequest.php index d4fd7f8..58fc2cc 100644 --- a/src/Gateways/PagBank/Requests/PagBankCustomerRequest.php +++ b/src/Gateways/PagBank/Requests/PagBankCustomerRequest.php @@ -3,6 +3,7 @@ namespace PHPay\PagBank\Requests; use PHPay\Exceptions\ValidationException; +use PHPay\Support\Customer; class PagBankCustomerRequest { @@ -49,4 +50,31 @@ public static function messages(): object 'taxId' => 'O campo tax_id é obrigatório e deve ter 11 dígitos (CPF) ou 14 (CNPJ), somente números.', ]; } + + /** + * map the library's Customer onto the payload PagBank expects. + * + * PagBank wants the phone split into country, area and number, which is + * what Customer::phoneParts() produces. + * + * @param Customer $customer + * @return array + */ + public static function fromCustomer(Customer $customer): array + { + $payload = array_filter([ + 'name' => $customer->name, + 'email' => $customer->email, + 'tax_id' => $customer->document, + ], fn ($value) => $value !== null); + + $telefone = $customer->phoneParts(); + + if ($telefone !== null) { + $payload['phones'] = [$telefone + ['type' => 'MOBILE']]; + } + + return $payload + $customer->extra(); + } + } diff --git a/src/Gateways/PagBank/Resources/Charge/Charge.php b/src/Gateways/PagBank/Resources/Charge/Charge.php index ac67039..a6593fe 100644 --- a/src/Gateways/PagBank/Resources/Charge/Charge.php +++ b/src/Gateways/PagBank/Resources/Charge/Charge.php @@ -4,10 +4,10 @@ use GuzzleHttp\Client; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\PagBank\Requests\PagBankOrderRequest; +use PHPay\PagBank\Requests\{PagBankCustomerRequest, PagBankOrderRequest}; use PHPay\PagBank\Resources\Charge\Interface\ChargeInterface; use PHPay\PagBank\Traits\HasPagBankClient; -use PHPay\Support\Money; +use PHPay\Support\{Customer, Money}; /** * orders and charges of the PagBank Orders API. @@ -69,8 +69,12 @@ public function setOrder(array $order): ChargeInterface * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface + public function setCustomer(Customer|array $customer): ChargeInterface { + if ($customer instanceof Customer) { + $customer = PagBankCustomerRequest::fromCustomer($customer); + } + $this->order['customer'] = $customer; return $this; diff --git a/src/Gateways/PagBank/Resources/Charge/Interface/ChargeInterface.php b/src/Gateways/PagBank/Resources/Charge/Interface/ChargeInterface.php index 4ade719..ac8d966 100644 --- a/src/Gateways/PagBank/Resources/Charge/Interface/ChargeInterface.php +++ b/src/Gateways/PagBank/Resources/Charge/Interface/ChargeInterface.php @@ -2,7 +2,7 @@ namespace PHPay\PagBank\Resources\Charge\Interface; -use PHPay\Support\Money; +use PHPay\Support\{Customer, Money}; interface ChargeInterface { @@ -20,7 +20,7 @@ public function setOrder(array $order): ChargeInterface; * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface; + public function setCustomer(Customer|array $customer): ChargeInterface; /** * set the items of the order diff --git a/src/Gateways/PagBank/Resources/Subscription/Interface/SubscriptionInterface.php b/src/Gateways/PagBank/Resources/Subscription/Interface/SubscriptionInterface.php index 456a011..2cb5ac1 100644 --- a/src/Gateways/PagBank/Resources/Subscription/Interface/SubscriptionInterface.php +++ b/src/Gateways/PagBank/Resources/Subscription/Interface/SubscriptionInterface.php @@ -2,6 +2,8 @@ namespace PHPay\PagBank\Resources\Subscription\Interface; +use PHPay\Support\Customer; + interface SubscriptionInterface { /** @@ -26,7 +28,7 @@ public function setCustomerId(string $customerId): SubscriptionInterface; * @param array $customer * @return SubscriptionInterface */ - public function setCustomer(array $customer): SubscriptionInterface; + public function setCustomer(Customer|array $customer): SubscriptionInterface; /** * set list filter diff --git a/src/Gateways/PagBank/Resources/Subscription/Subscription.php b/src/Gateways/PagBank/Resources/Subscription/Subscription.php index d720ca8..3a541f9 100644 --- a/src/Gateways/PagBank/Resources/Subscription/Subscription.php +++ b/src/Gateways/PagBank/Resources/Subscription/Subscription.php @@ -4,9 +4,10 @@ use GuzzleHttp\Client; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\PagBank\Requests\PagBankSubscriptionRequest; +use PHPay\PagBank\Requests\{PagBankCustomerRequest, PagBankSubscriptionRequest}; use PHPay\PagBank\Resources\Subscription\Interface\SubscriptionInterface; use PHPay\PagBank\Traits\HasPagBankClient; +use PHPay\Support\Customer; /** * plans and subscriptions of the PagBank subscriptions API. @@ -87,8 +88,16 @@ public function setCustomerId(string $customerId): SubscriptionInterface * @param array $customer * @return SubscriptionInterface */ - public function setCustomer(array $customer): SubscriptionInterface + public function setCustomer(Customer|array $customer): SubscriptionInterface { + if ($customer instanceof Customer) { + if ($customer->id !== null) { + return $this->setCustomerId($customer->id); + } + + $customer = PagBankCustomerRequest::fromCustomer($customer); + } + $this->subscription['customer'] = $customer; return $this; diff --git a/src/Gateways/PagarMe/Interface/PagarMeGatewayInterface.php b/src/Gateways/PagarMe/Interface/PagarMeGatewayInterface.php index 59a8613..95d9c3c 100644 --- a/src/Gateways/PagarMe/Interface/PagarMeGatewayInterface.php +++ b/src/Gateways/PagarMe/Interface/PagarMeGatewayInterface.php @@ -7,6 +7,7 @@ use PHPay\PagarMe\Resources\Customer\Customer; use PHPay\PagarMe\Resources\Subscription\Subscription; use PHPay\PagarMe\Resources\WebhookDelivery\WebhookDelivery; +use PHPay\Support\Customer as CustomerData; /** * the Pagar.me gateway offers customers, charges and subscriptions. @@ -29,7 +30,7 @@ interface PagarMeGatewayInterface extends * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer; + public function customer(CustomerData|array $customer = []): Customer; /** * get resource charge from gateway. diff --git a/src/Gateways/PagarMe/PagarMeGateway.php b/src/Gateways/PagarMe/PagarMeGateway.php index 97f611d..503d8f1 100644 --- a/src/Gateways/PagarMe/PagarMeGateway.php +++ b/src/Gateways/PagarMe/PagarMeGateway.php @@ -4,10 +4,12 @@ use GuzzleHttp\Client; use PHPay\PagarMe\Interface\PagarMeGatewayInterface; +use PHPay\PagarMe\Requests\PagarMeCustomerRequest; use PHPay\PagarMe\Resources\Charge\Charge; use PHPay\PagarMe\Resources\Customer\Customer; use PHPay\PagarMe\Resources\Subscription\Subscription; use PHPay\PagarMe\Resources\WebhookDelivery\WebhookDelivery; +use PHPay\Support\Customer as CustomerData; class PagarMeGateway implements PagarMeGatewayInterface { @@ -57,8 +59,12 @@ public function isSandbox(): bool * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer + public function customer(CustomerData|array $customer = []): Customer { + if ($customer instanceof CustomerData) { + $customer = PagarMeCustomerRequest::fromCustomer($customer); + } + return new Customer($this->secretKey, $customer, $this->client); } diff --git a/src/Gateways/PagarMe/Requests/PagarMeCustomerRequest.php b/src/Gateways/PagarMe/Requests/PagarMeCustomerRequest.php index abbfe22..283f719 100644 --- a/src/Gateways/PagarMe/Requests/PagarMeCustomerRequest.php +++ b/src/Gateways/PagarMe/Requests/PagarMeCustomerRequest.php @@ -4,6 +4,7 @@ use PHPay\Exceptions\ValidationException; use PHPay\PagarMe\Enums\CustomerTypeEnum; +use PHPay\Support\Customer; class PagarMeCustomerRequest { @@ -58,4 +59,28 @@ public static function messages(): object 'type' => 'O campo type aceita apenas: individual, company.', ]; } + + /** + * map the library's Customer onto the payload Pagar.me expects. + * + * Pagar.me needs `type`, which Customer derives from the document length. + * + * @param Customer $customer + * @return array + */ + public static function fromCustomer(Customer $customer): array + { + $payload = array_filter([ + 'name' => $customer->name, + 'email' => $customer->email, + 'document' => $customer->document, + ], fn ($value) => $value !== null); + + if ($customer->documentType() !== null) { + $payload['type'] = $customer->isCompany() ? 'company' : 'individual'; + } + + return $payload + $customer->extra(); + } + } diff --git a/src/Gateways/PagarMe/Resources/Charge/Charge.php b/src/Gateways/PagarMe/Resources/Charge/Charge.php index 2682cb9..3b247a2 100644 --- a/src/Gateways/PagarMe/Resources/Charge/Charge.php +++ b/src/Gateways/PagarMe/Resources/Charge/Charge.php @@ -5,10 +5,10 @@ use GuzzleHttp\Client; use PHPay\Exceptions\{ApiException, ValidationException}; use PHPay\PagarMe\Enums\PaymentMethodEnum; -use PHPay\PagarMe\Requests\PagarMeOrderRequest; +use PHPay\PagarMe\Requests\{PagarMeCustomerRequest, PagarMeOrderRequest}; use PHPay\PagarMe\Resources\Charge\Interface\ChargeInterface; use PHPay\PagarMe\Traits\HasPagarMeClient; -use PHPay\Support\Money; +use PHPay\Support\{Customer as CustomerData, Money}; /** * orders and charges of the Pagar.me Core API v5. @@ -87,8 +87,16 @@ public function setCustomerId(string $customerId): ChargeInterface * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface + public function setCustomer(CustomerData|array $customer): ChargeInterface { + if ($customer instanceof CustomerData) { + if ($customer->id !== null) { + return $this->setCustomerId($customer->id); + } + + $customer = PagarMeCustomerRequest::fromCustomer($customer); + } + if (isset($customer['id']) && is_string($customer['id']) && $customer['id'] !== '') { return $this->setCustomerId($customer['id']); } diff --git a/src/Gateways/PagarMe/Resources/Charge/Interface/ChargeInterface.php b/src/Gateways/PagarMe/Resources/Charge/Interface/ChargeInterface.php index 45b3898..a259e30 100644 --- a/src/Gateways/PagarMe/Resources/Charge/Interface/ChargeInterface.php +++ b/src/Gateways/PagarMe/Resources/Charge/Interface/ChargeInterface.php @@ -2,7 +2,7 @@ namespace PHPay\PagarMe\Resources\Charge\Interface; -use PHPay\Support\Money; +use PHPay\Support\{Customer as CustomerData, Money}; interface ChargeInterface { @@ -28,7 +28,7 @@ public function setCustomerId(string $customerId): ChargeInterface; * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface; + public function setCustomer(CustomerData|array $customer): ChargeInterface; /** * set the items of the order diff --git a/src/Gateways/PagarMe/Resources/Subscription/Interface/SubscriptionInterface.php b/src/Gateways/PagarMe/Resources/Subscription/Interface/SubscriptionInterface.php index a9f779e..309d680 100644 --- a/src/Gateways/PagarMe/Resources/Subscription/Interface/SubscriptionInterface.php +++ b/src/Gateways/PagarMe/Resources/Subscription/Interface/SubscriptionInterface.php @@ -2,6 +2,8 @@ namespace PHPay\PagarMe\Resources\Subscription\Interface; +use PHPay\Support\Customer as CustomerData; + interface SubscriptionInterface { /** @@ -26,7 +28,7 @@ public function setCustomerId(string $customerId): SubscriptionInterface; * @param array $customer * @return SubscriptionInterface */ - public function setCustomer(array $customer): SubscriptionInterface; + public function setCustomer(CustomerData|array $customer): SubscriptionInterface; /** * set list filter diff --git a/src/Gateways/PagarMe/Resources/Subscription/Subscription.php b/src/Gateways/PagarMe/Resources/Subscription/Subscription.php index cfd8efa..88c991b 100644 --- a/src/Gateways/PagarMe/Resources/Subscription/Subscription.php +++ b/src/Gateways/PagarMe/Resources/Subscription/Subscription.php @@ -4,9 +4,10 @@ use GuzzleHttp\Client; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\PagarMe\Requests\PagarMeSubscriptionRequest; +use PHPay\PagarMe\Requests\{PagarMeCustomerRequest, PagarMeSubscriptionRequest}; use PHPay\PagarMe\Resources\Subscription\Interface\SubscriptionInterface; use PHPay\PagarMe\Traits\HasPagarMeClient; +use PHPay\Support\Customer as CustomerData; /** * plans and subscriptions of the Pagar.me Core API v5. @@ -83,8 +84,16 @@ public function setCustomerId(string $customerId): SubscriptionInterface * @param array $customer * @return SubscriptionInterface */ - public function setCustomer(array $customer): SubscriptionInterface + public function setCustomer(CustomerData|array $customer): SubscriptionInterface { + if ($customer instanceof CustomerData) { + if ($customer->id !== null) { + return $this->setCustomerId($customer->id); + } + + $customer = PagarMeCustomerRequest::fromCustomer($customer); + } + if (isset($customer['id']) && is_string($customer['id']) && $customer['id'] !== '') { return $this->setCustomerId($customer['id']); } diff --git a/src/Gateways/Woovi/Interface/WooviGatewayInterface.php b/src/Gateways/Woovi/Interface/WooviGatewayInterface.php index 21ea4db..46096a5 100644 --- a/src/Gateways/Woovi/Interface/WooviGatewayInterface.php +++ b/src/Gateways/Woovi/Interface/WooviGatewayInterface.php @@ -3,6 +3,7 @@ namespace PHPay\Woovi\Interface; use PHPay\Contracts\{SupportsCharges, SupportsCustomers, SupportsPixKeys, SupportsSubscriptions, SupportsWebhooks}; +use PHPay\Support\Customer as CustomerData; use PHPay\Woovi\Resources\Charge\Charge; use PHPay\Woovi\Resources\Customer\Customer; use PHPay\Woovi\Resources\Pix\Pix; @@ -29,7 +30,7 @@ interface WooviGatewayInterface extends * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer; + public function customer(CustomerData|array $customer = []): Customer; /** * get resource charge from gateway. diff --git a/src/Gateways/Woovi/Requests/WooviCustomerRequest.php b/src/Gateways/Woovi/Requests/WooviCustomerRequest.php index de617f6..00d0216 100644 --- a/src/Gateways/Woovi/Requests/WooviCustomerRequest.php +++ b/src/Gateways/Woovi/Requests/WooviCustomerRequest.php @@ -3,6 +3,7 @@ namespace PHPay\Woovi\Requests; use PHPay\Exceptions\ValidationException; +use PHPay\Support\Customer; class WooviCustomerRequest { @@ -54,4 +55,21 @@ public static function messages(): object 'identificador' => 'O cliente precisa de ao menos um identificador: email, taxID ou phone.', ]; } + + /** + * map the library's Customer onto the payload Woovi expects. + * + * @param Customer $customer + * @return array + */ + public static function fromCustomer(Customer $customer): array + { + return array_filter([ + 'name' => $customer->name, + 'email' => $customer->email, + 'taxID' => $customer->document, + 'phone' => $customer->phone, + ], fn ($value) => $value !== null) + $customer->extra(); + } + } diff --git a/src/Gateways/Woovi/Resources/Charge/Charge.php b/src/Gateways/Woovi/Resources/Charge/Charge.php index 5644851..202c011 100644 --- a/src/Gateways/Woovi/Resources/Charge/Charge.php +++ b/src/Gateways/Woovi/Resources/Charge/Charge.php @@ -4,8 +4,8 @@ use GuzzleHttp\Client; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\Support\Money; -use PHPay\Woovi\Requests\WooviChargeRequest; +use PHPay\Support\{Customer as CustomerData, Money}; +use PHPay\Woovi\Requests\{WooviChargeRequest, WooviCustomerRequest}; use PHPay\Woovi\Resources\Charge\Interface\ChargeInterface; use PHPay\Woovi\Traits\HasWooviClient; @@ -88,8 +88,12 @@ public function setCorrelationId(string $correlationId): ChargeInterface * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface + public function setCustomer(CustomerData|array $customer): ChargeInterface { + if ($customer instanceof CustomerData) { + $customer = WooviCustomerRequest::fromCustomer($customer); + } + $this->charge['customer'] = $customer; return $this; diff --git a/src/Gateways/Woovi/Resources/Charge/Interface/ChargeInterface.php b/src/Gateways/Woovi/Resources/Charge/Interface/ChargeInterface.php index 7dd67e0..194227c 100644 --- a/src/Gateways/Woovi/Resources/Charge/Interface/ChargeInterface.php +++ b/src/Gateways/Woovi/Resources/Charge/Interface/ChargeInterface.php @@ -2,7 +2,7 @@ namespace PHPay\Woovi\Resources\Charge\Interface; -use PHPay\Support\Money; +use PHPay\Support\{Customer as CustomerData, Money}; interface ChargeInterface { @@ -28,7 +28,7 @@ public function setCorrelationId(string $correlationId): ChargeInterface; * @param array $customer * @return ChargeInterface */ - public function setCustomer(array $customer): ChargeInterface; + public function setCustomer(CustomerData|array $customer): ChargeInterface; /** * set list query params diff --git a/src/Gateways/Woovi/Resources/Subscription/Interface/SubscriptionInterface.php b/src/Gateways/Woovi/Resources/Subscription/Interface/SubscriptionInterface.php index 754ba70..218e268 100644 --- a/src/Gateways/Woovi/Resources/Subscription/Interface/SubscriptionInterface.php +++ b/src/Gateways/Woovi/Resources/Subscription/Interface/SubscriptionInterface.php @@ -2,7 +2,7 @@ namespace PHPay\Woovi\Resources\Subscription\Interface; -use PHPay\Support\Money; +use PHPay\Support\{Customer as CustomerData, Money}; interface SubscriptionInterface { @@ -12,7 +12,7 @@ interface SubscriptionInterface * @param array $customer * @return SubscriptionInterface */ - public function setCustomer(array $customer): SubscriptionInterface; + public function setCustomer(CustomerData|array $customer): SubscriptionInterface; /** * set the day of the month the charge is generated diff --git a/src/Gateways/Woovi/Resources/Subscription/Subscription.php b/src/Gateways/Woovi/Resources/Subscription/Subscription.php index 89cb47d..f60be96 100644 --- a/src/Gateways/Woovi/Resources/Subscription/Subscription.php +++ b/src/Gateways/Woovi/Resources/Subscription/Subscription.php @@ -4,8 +4,8 @@ use GuzzleHttp\Client; use PHPay\Exceptions\{ApiException, ValidationException}; -use PHPay\Support\Money; -use PHPay\Woovi\Requests\WooviSubscriptionRequest; +use PHPay\Support\{Customer as CustomerData, Money}; +use PHPay\Woovi\Requests\{WooviCustomerRequest, WooviSubscriptionRequest}; use PHPay\Woovi\Resources\Subscription\Interface\SubscriptionInterface; use PHPay\Woovi\Traits\HasWooviClient; @@ -51,8 +51,12 @@ public function __construct( * @param array $customer * @return SubscriptionInterface */ - public function setCustomer(array $customer): SubscriptionInterface + public function setCustomer(CustomerData|array $customer): SubscriptionInterface { + if ($customer instanceof CustomerData) { + $customer = WooviCustomerRequest::fromCustomer($customer); + } + $this->subscription['customer'] = $customer; return $this; diff --git a/src/Gateways/Woovi/WooviGateway.php b/src/Gateways/Woovi/WooviGateway.php index 43d6769..85671b7 100644 --- a/src/Gateways/Woovi/WooviGateway.php +++ b/src/Gateways/Woovi/WooviGateway.php @@ -3,7 +3,9 @@ namespace PHPay\Woovi; use GuzzleHttp\Client; +use PHPay\Support\Customer as CustomerData; use PHPay\Woovi\Interface\WooviGatewayInterface; +use PHPay\Woovi\Requests\WooviCustomerRequest; use PHPay\Woovi\Resources\Charge\Charge; use PHPay\Woovi\Resources\Customer\Customer; use PHPay\Woovi\Resources\Pix\Pix; @@ -42,8 +44,12 @@ public function name(): string * @param array $customer * @return Customer */ - public function customer(array $customer = []): Customer + public function customer(CustomerData|array $customer = []): Customer { + if ($customer instanceof CustomerData) { + $customer = WooviCustomerRequest::fromCustomer($customer); + } + return new Customer($this->appId, $customer, $this->sandbox, $this->client); } diff --git a/src/PHPay.php b/src/PHPay.php index 998c322..3ae2e73 100644 --- a/src/PHPay.php +++ b/src/PHPay.php @@ -4,6 +4,7 @@ use PHPay\Contracts\{Capability, GatewayInterface, SupportsCharges, SupportsCustomers, SupportsPixKeys, SupportsSubscriptions, SupportsWebhooks}; use PHPay\Exceptions\NotImplementedException; +use PHPay\Support\Customer as CustomerData; /** * entry point of the library. @@ -78,7 +79,7 @@ public function capabilities(): array * @return object * @throws NotImplementedException */ - public function customer(array $customer = []): object + public function customer(CustomerData|array $customer = []): object { $gateway = $this->gateway; diff --git a/src/Support/Customer.php b/src/Support/Customer.php new file mode 100644 index 0000000..b149704 --- /dev/null +++ b/src/Support/Customer.php @@ -0,0 +1,249 @@ + fields specific to one gateway, merged into its payload + */ + private array $extra; + + /** + * construct + * + * @param string $name + * @param string|null $document CPF or CNPJ; punctuation is stripped + * @param string|null $email + * @param string|null $phone with area code; punctuation is stripped + * @param string|null $id the customer id at the gateway, when it already exists + * @param array $extra + * @throws ValidationException + */ + public function __construct( + public readonly string $name, + public readonly ?string $document = null, + public readonly ?string $email = null, + public readonly ?string $phone = null, + public readonly ?string $id = null, + array $extra = [], + ) { + if (trim($name) === '') { + throw ValidationException::make('PHPay', self::messages()->name); + } + + if ($document !== null && !in_array(strlen($document), [11, 14], true)) { + throw ValidationException::make('PHPay', self::messages()->document); + } + + if ($email !== null && filter_var($email, FILTER_VALIDATE_EMAIL) === false) { + throw ValidationException::make('PHPay', self::messages()->email); + } + + $this->extra = $extra; + } + + /** + * build a Customer, stripping punctuation from document and phone. + * + * use this when the values come from a form: `'123.456.789-01'` and + * `'(11) 94002-8922'` become digits. + * + * @param string $name + * @param string|null $document + * @param string|null $email + * @param string|null $phone + * @param string|null $id + * @param array $extra + * @return self + * @throws ValidationException + */ + public static function make( + string $name, + ?string $document = null, + ?string $email = null, + ?string $phone = null, + ?string $id = null, + array $extra = [], + ): self { + return new self( + $name, + $document === null ? null : self::onlyDigits($document), + $email, + $phone === null ? null : self::onlyDigits($phone), + $id, + $extra + ); + } + + /** + * whether the document is a CPF. + * + * @return bool + */ + public function isIndividual(): bool + { + return $this->document !== null && strlen($this->document) === 11; + } + + /** + * whether the document is a CNPJ. + * + * @return bool + */ + public function isCompany(): bool + { + return $this->document !== null && strlen($this->document) === 14; + } + + /** + * 'CPF', 'CNPJ', or null when there is no document. + * + * @return string|null + */ + public function documentType(): ?string + { + if ($this->isIndividual()) { + return 'CPF'; + } + + return $this->isCompany() ? 'CNPJ' : null; + } + + /** + * the first word of the name — Mercado Pago wants it apart. + * + * @return string + */ + public function firstName(): string + { + $partes = preg_split('/\s+/', trim($this->name)) ?: [$this->name]; + + return $partes[0]; + } + + /** + * everything after the first word, or null for a single-word name. + * + * @return string|null + */ + public function lastName(): ?string + { + $partes = preg_split('/\s+/', trim($this->name)) ?: []; + + if (count($partes) < 2) { + return null; + } + + return implode(' ', array_slice($partes, 1)); + } + + /** + * the phone split the way PagBank wants it. + * + * assumes a Brazilian number: the last 8 or 9 digits are the number, the + * two before them the area code, and the country is 55 unless the number + * already carries it. + * + * @return array{country: string, area: string, number: string}|null + */ + public function phoneParts(): ?array + { + if ($this->phone === null) { + return null; + } + + $digitos = self::onlyDigits($this->phone); + + if (strlen($digitos) > 11 && str_starts_with($digitos, '55')) { + $digitos = substr($digitos, 2); + } + + if (strlen($digitos) < 10) { + return null; + } + + return [ + 'country' => '55', + 'area' => substr($digitos, 0, 2), + 'number' => substr($digitos, 2), + ]; + } + + /** + * fields specific to one gateway. + * + * @return array + */ + public function extra(): array + { + return $this->extra; + } + + /** + * a copy carrying extra fields for one gateway. + * + * @param array $extra + * @return self + */ + public function withExtra(array $extra): self + { + return new self( + $this->name, + $this->document, + $this->email, + $this->phone, + $this->id, + array_merge($this->extra, $extra) + ); + } + + /** + * a copy pointing at an existing customer at the gateway. + * + * @param string $id + * @return self + */ + public function withId(string $id): self + { + return new self($this->name, $this->document, $this->email, $this->phone, $id, $this->extra); + } + + /** + * messages for validation + * + * @return object{name: string, document: string, email: string} + */ + public static function messages(): object + { + return (object) [ + 'name' => 'O nome do cliente é obrigatório e não pode ser vazio.', + 'document' => 'O documento deve ter 11 dígitos (CPF) ou 14 (CNPJ), somente números. Use Customer::make() para limpar a pontuação automaticamente.', + 'email' => 'O e-mail do cliente, quando informado, deve ser válido.', + ]; + } + + /** + * strip everything that is not a digit. + * + * @param string $value + * @return string + */ + private static function onlyDigits(string $value): string + { + return preg_replace('/\D/', '', $value) ?? ''; + } +} diff --git a/tests/Unit/Support/CustomerAcrossGatewaysTest.php b/tests/Unit/Support/CustomerAcrossGatewaysTest.php new file mode 100644 index 0000000..989315b --- /dev/null +++ b/tests/Unit/Support/CustomerAcrossGatewaysTest.php @@ -0,0 +1,102 @@ +toBe('12345678901') + ->and(PagBankCustomerRequest::fromCustomer($cliente)['tax_id'])->toBe('12345678901') + ->and(PagarMeCustomerRequest::fromCustomer($cliente)['document'])->toBe('12345678901') + ->and(AbacatePayCustomerRequest::fromCustomer($cliente)['taxId'])->toBe('12345678901') + ->and(WooviCustomerRequest::fromCustomer($cliente)['taxID'])->toBe('12345678901') + ->and(EfiCustomerRequest::fromCustomer($cliente)['cpf_cnpj'])->toBe('12345678901') + ->and(CieloSaleRequest::fromCustomer($cliente)['Identity'])->toBe('12345678901') + ->and(MercadoPagoCustomerRequest::fromCustomer($cliente)['identification']['number']) + ->toBe('12345678901'); +})->group('support'); + +it('respeita a forma que cada gateway espera, não só o nome do campo', function () { + $cliente = clienteExemplo(); + + /* o pagbank quer o telefone quebrado */ + expect(PagBankCustomerRequest::fromCustomer($cliente)['phones'][0]) + ->toBe(['country' => '55', 'area' => '11', 'number' => '940028922', 'type' => 'MOBILE']); + + /* o pagar.me quer o tipo derivado do documento */ + expect(PagarMeCustomerRequest::fromCustomer($cliente)['type'])->toBe('individual'); + + /* o mercado pago quer o nome separado */ + expect(MercadoPagoCustomerRequest::fromCustomer($cliente)) + ->toMatchArray(['first_name' => 'Mário', 'last_name' => 'Lucas']); + + /* a cielo é a única que usa maiúscula */ + expect(CieloSaleRequest::fromCustomer($cliente)) + ->toMatchArray(['Name' => 'Mário Lucas', 'IdentityType' => 'CPF']); + + /* abacate chama o telefone de cellphone; woovi, de phone */ + expect(AbacatePayCustomerRequest::fromCustomer($cliente)['cellphone'])->toBe('11940028922') + ->and(WooviCustomerRequest::fromCustomer($cliente)['phone'])->toBe('11940028922'); +})->group('support'); + +it('deriva company do CNPJ', function () { + $empresa = Customer::make('Sixtec LTDA', '12.345.678/0001-99'); + + expect(PagarMeCustomerRequest::fromCustomer($empresa)['type'])->toBe('company') + ->and(CieloSaleRequest::fromCustomer($empresa)['IdentityType'])->toBe('CNPJ'); +})->group('support'); + +it('omite o que não foi informado em vez de mandar null', function () { + $minimo = new Customer('Mário Lucas'); + + expect(AsaasCustomerRequest::fromCustomer($minimo))->toBe(['name' => 'Mário Lucas']) + ->and(PagBankCustomerRequest::fromCustomer($minimo))->not->toHaveKey('phones'); +})->group('support'); + +it('carrega campos específicos de um gateway pelo escape hatch', function () { + $cliente = clienteExemplo()->withExtra(['externalReference' => 'cliente-42']); + + expect(AsaasCustomerRequest::fromCustomer($cliente)) + ->toHaveKey('externalReference') + ->and(AsaasCustomerRequest::fromCustomer($cliente)['externalReference'])->toBe('cliente-42'); +})->group('support'); + +it('funciona de ponta a ponta na cobrança', function () { + $history = []; + + (new PHPay\PagBank\Resources\Charge\Charge('token', true, mockClient([jsonResponse([])], $history))) + ->setCustomer(clienteExemplo()) + ->addItem('Item', PHPay\Support\Money::reais(100.50)) + ->setQrCode(PHPay\Support\Money::reais(100.50)) + ->create(); + + $enviado = recordedBody($history)['customer']; + + expect($enviado['tax_id'])->toBe('12345678901') + ->and($enviado['name'])->toBe('Mário Lucas') + ->and($enviado['phones'][0]['area'])->toBe('11'); +})->group('support'); diff --git a/tests/Unit/Support/CustomerTest.php b/tests/Unit/Support/CustomerTest.php new file mode 100644 index 0000000..35b4797 --- /dev/null +++ b/tests/Unit/Support/CustomerTest.php @@ -0,0 +1,69 @@ +isIndividual())->toBeTrue() + ->and($pessoa->documentType())->toBe('CPF') + ->and($empresa->isCompany())->toBeTrue() + ->and($empresa->documentType())->toBe('CNPJ') + ->and($semDoc->documentType())->toBeNull() + ->and($semDoc->isIndividual())->toBeFalse(); +})->group('support'); + +it('limpa pontuação de documento e telefone com make()', function () { + $cliente = Customer::make( + name: 'Mário Lucas', + document: '123.456.789-01', + phone: '(11) 94002-8922', + ); + + expect($cliente->document)->toBe('12345678901') + ->and($cliente->phone)->toBe('11940028922'); +})->group('support'); + +it('separa o nome como o mercado pago quer', function () { + expect((new Customer('Mário Lucas da Silva'))->firstName())->toBe('Mário') + ->and((new Customer('Mário Lucas da Silva'))->lastName())->toBe('Lucas da Silva') + ->and((new Customer('Prince'))->lastName())->toBeNull(); +})->group('support'); + +it('quebra o telefone como o pagbank quer', function () { + expect((new Customer('Mário', phone: '11940028922'))->phoneParts()) + ->toBe(['country' => '55', 'area' => '11', 'number' => '940028922']); + + /* aceita o número já com o código do país */ + expect((new Customer('Mário', phone: '5511940028922'))->phoneParts()) + ->toBe(['country' => '55', 'area' => '11', 'number' => '940028922']); + + expect((new Customer('Mário'))->phoneParts())->toBeNull() + ->and((new Customer('Mário', phone: '123'))->phoneParts())->toBeNull(); +})->group('support'); + +it('recusa documento com tamanho inválido', function () { + expect(fn () => new Customer('Mário', '123')) + ->toThrow(ValidationException::class, '11 dígitos'); +})->group('support'); + +it('recusa nome vazio e e-mail inválido', function () { + expect(fn () => new Customer(' '))->toThrow(ValidationException::class, 'nome do cliente'); + expect(fn () => new Customer('Mário', email: 'nao-e-email')) + ->toThrow(ValidationException::class, 'e-mail'); +})->group('support'); + +it('é imutável: withExtra e withId devolvem cópias', function () { + $original = new Customer('Mário Lucas', '12345678901'); + $comId = $original->withId('cus_1'); + $comExtra = $original->withExtra(['observacao' => 'vip']); + + expect($original->id)->toBeNull() + ->and($comId->id)->toBe('cus_1') + ->and($original->extra())->toBe([]) + ->and($comExtra->extra())->toBe(['observacao' => 'vip']) + ->and($comExtra->name)->toBe('Mário Lucas'); +})->group('support');