Skip to content

PHPAY-93: value object Customer — uma forma só para os nove gateways - #94

Merged
mariolucasdev merged 1 commit into
developfrom
feat/phpay-93
Sep 21, 2026
Merged

mariolucasdev merged 1 commit into
developfrom
feat/phpay-93

Conversation

@mariolucasdev

Copy link
Copy Markdown
Collaborator

Closes #93

Fecha a segunda metade da crítica que originou o #91.

O problema, medido

O mesmo campo tinha seis grafias:

Asaas PagBank Pagar.me AbacatePay Woovi Efí Cielo
cpfCnpj tax_id document taxId taxID cpf_cnpj Identity

O README prometia que "trocar de gateway é trocar a linha do construtor", e isso era falso para qualquer coisa envolvendo cliente.

A solução

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

Há um teste que mapeia o mesmo cliente para as oito grafias numa expectativa só.

O VO absorve derivações que estavam espalhadas

$cliente->isIndividual();   // o Pagar.me precisa como type: 'individual'
$cliente->documentType();   // a Cielo quer em IdentityType
$cliente->firstName();      // o Mercado Pago quer nome e sobrenome separados
$cliente->phoneParts();     // o PagBank quer {country, area, number}

Antes, quem integrava tinha que saber dessas diferenças. Agora elas moram num lugar só, com teste.

⚠️ Sobre o risco que eu levantei — e que não se confirmou

Ao propor isso, eu disse:

O escape hatch (extra) vai ser exercitado de verdade — e se ele acabar carregando a maior parte do payload em algum gateway, o VO não está valendo a pena ali. Isso precisa ser avaliado na implementação, não assumido.

Medi, com um script que cruza os campos obrigatórios de cada gateway contra o que o mapper produz:

Asaas        TODOS cobertos pelo VO
PagBank      TODOS cobertos pelo VO
Pagar.me     TODOS cobertos pelo VO
AbacatePay   TODOS cobertos pelo VO
Woovi        TODOS cobertos pelo VO
Efí          TODOS cobertos pelo VO
Cielo        TODOS cobertos pelo VO
MercadoPago  TODOS cobertos pelo VO

O withExtra() só carrega opcionais — endereço, data de nascimento, referência externa. A previsão pessimista não se confirmou.

Onde o mapeamento mora

Na classe *CustomerRequest de cada gateway, em fromCustomer(Customer): array. Ela já detinha o conhecimento do schema daquele gateway para validar — agora validação e mapeamento ficam no mesmo lugar, em vez de espalhar a tradução pelos recursos.

Compatibilidade

Total. Customer|array em todo setCustomer(), setPayer() e customer(), inclusive no contrato SupportsCustomers. Quem passa array continua funcionando.

Efí, Cielo e Rede não ganharam customer() — eles não implementam SupportsCustomers, e o modelo de capacidades já dizia isso.

Verificação

275 testes (759 asserções), PHPStan nível 9 limpo, Pint limpo.

Fecha a segunda metade da crítica que originou o Money: a biblioteca
normalizava os nomes dos métodos entre gateways, mas deixava os payloads crus.

O mesmo campo tinha seis grafias — cpfCnpj no Asaas, tax_id no PagBank,
document no Pagar.me, taxId no AbacatePay, taxID no Woovi, cpf_cnpj no Efí. O
README prometia que trocar de gateway era trocar a linha do construtor, e isso
era falso para qualquer coisa envolvendo cliente.

Agora um Customer escrito uma vez vira o formato de cada um. O mapeamento mora
na classe *CustomerRequest de cada gateway, em fromCustomer(), porque ela já
detém o conhecimento do schema daquele gateway — validação e mapeamento ficam
no mesmo lugar.

O VO também absorve derivações que antes estavam espalhadas ou ausentes:
isIndividual() e documentType() para o type do Pagar.me e o IdentityType da
Cielo; firstName() e lastName() para o Mercado Pago; phoneParts() para o
formato estruturado do PagBank. Antes disso, quem integrava tinha que saber
dessas diferenças.

SOBRE O RISCO QUE EU TINHA LEVANTADO. Ao propor isso eu disse que o escape
hatch podia acabar carregando a maior parte do payload em algum gateway, e que
isso precisava ser medido e não assumido. Medi: o value object cobre TODOS os
campos obrigatórios de cliente dos oito gateways que têm cliente. O
withExtra() só carrega opcionais — endereço, data de nascimento, referência
externa. A previsão não se confirmou.

Compatibilidade total: Customer|array em todo setCustomer(), setPayer() e
customer(), inclusive no contrato SupportsCustomers. Quem passa array continua
funcionando.

Efí, Cielo e Rede não ganharam customer() porque não implementam
SupportsCustomers — o modelo de capacidades já dizia isso.

275 testes no total.
@mariolucasdev
mariolucasdev merged commit 6c9277f into develop Sep 21, 2026
6 checks passed
@mariolucasdev
mariolucasdev deleted the feat/phpay-93 branch September 21, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Value object Customer: uma forma só para o cliente nos nove gateways

1 participant