Skip to content

PHPAY-91: value object Money — eliminar no tipo o erro de centavos vs reais - #92

Merged
mariolucasdev merged 2 commits into
developfrom
feat/phpay-91
Sep 21, 2026
Merged

mariolucasdev merged 2 commits into
developfrom
feat/phpay-91

Conversation

@mariolucasdev

Copy link
Copy Markdown
Collaborator

Closes #91

Primeiro passo da crítica que motivou isso: a biblioteca normalizava os nomes dos métodos entre gateways, mas deixava os payloads crus. O caso mais caro disso era a unidade monetária.

O problema

O README documentava numa tabela que 7 dos 9 gateways usam centavos inteiros e 2 usam reais decimais, e os validadores recusavam decimal onde não cabia.

Isso é remendo em runtime para um problema de tipo. E é o pior erro possível numa biblioteca de pagamentos: mandar 100.50 num gateway de centavos não quebra a integração — cobra R$ 1,00, e você só descobre na conciliação.

A solução

use PHPay\Support\Money;

$valor = Money::reais(100.50);     // ou Money::centavos(10050)

$asaas->charge()->setAmount($valor);          // vira 100.50
$pagbank->charge()->addItem('Item', $valor);  // vira 10050

O mesmo objeto, a unidade certa em cada gateway. Há um teste que assevera exatamente isso numa expectativa só, cobrindo os nove.

Decisões

Recusa mais de duas casas decimais. Money::reais(10/3) lança exceção em vez de arredondar. Arredondamento silencioso é como nascem erros de um centavo na conciliação — a mensagem diz para arredondar explicitamente ou usar centavos().

Aceita string em notação brasileira. '100,50' e '1.234,56' funcionam, que é o que um campo de formulário entrega.

Sobrevive à imprecisão de float. 0.07 * 100 dá 7.000000000000001; há teste para isso.

Aritmética mínima: multiply() e plus(), porque os gateways que montam total a partir de produtos precisam.

Onde o value object sozinho não bastava

Sete gateways recebem o valor como parâmetro — esses só mudaram de assinatura.

Mas Asaas, Mercado Pago e Efí guardam o valor dentro do array de payload (value, transaction_amount). Para esses criei um setAmount() explícito, que escreve a chave certa na unidade certa:

$phpay->charge()
    ->setCharge(['billingType' => 'BOLETO', 'dueDate' => '...'])
    ->setAmount(Money::reais(100.00))
    ->create();

Compatibilidade

Totalmente aditivo. Todo método aceita Money|int (ou Money|int|float nos de reais), e número cru continua sendo lido na unidade que aquele gateway sempre esperou. Há teste cobrindo esse caminho.

Cabe numa v2.1.0 junto com o Woovi.

Dentro da biblioteca

A normalização passa por Money::asReais() / Money::asCentavos(), nunca pela leitura direta do parâmetro — assim um gateway novo não tem como esquecer a conversão. Está anotado no CLAUDE.md.

Verificação

262 testes (704 asserções), PHPStan nível 9 limpo, Pint limpo. Os exemplos de sete gateways foram convertidos para Money.

Próximo

O value object de Customer, que resolve a outra metade da crítica: cinco grafias diferentes para CPF/CNPJ (cpfCnpj, tax_id, document, taxId, taxID).

Elimina no tipo o erro de centavos vs reais: cada gateway pergunta ao objeto a
unidade que precisa, e fica impossível errar.

Recusa mais de duas casas decimais em vez de arredondar calado — arredondamento
silencioso é como nascem erros de um centavo na conciliação. Aceita string em
notação brasileira, que é o que um campo de formulário costuma entregar.
Propaga o Money por toda a superfície que recebe valor, mantendo
compatibilidade: todo método aceita Money|int, ou Money|int|float nos gateways
que usam reais, e número cru continua sendo lido na unidade que aquele gateway
sempre esperou.

Onde o valor chega como parâmetro, a assinatura mudou direto — PagBank,
Pagar.me, Cielo, Rede, AbacatePay e Woovi. Onde ele vive dentro do array de
payload, ganha um setAmount() explícito: Asaas e Mercado Pago em reais, Efí em
centavos. Era o caso que o value object sozinho não resolvia.

Dentro dos gateways, a normalização passa por Money::asReais() ou
Money::asCentavos(), nunca pela leitura direta do parâmetro. Assim um gateway
novo não tem como esquecer a conversão.

O teste que justifica tudo isso é o cruzado: o MESMO Money::reais(100.50) vira
100.50 no Asaas e no Mercado Pago, e 10050 nos outros sete, numa asserção só.
Antes, essa informação vivia numa tabela do README e em validadores que
recusavam decimal — remendo em runtime para um problema de tipo.

O README passa a liderar pelo Money e rebaixa a tabela de unidades a uma nota
sobre passar número cru, deixando claro que o value object a torna
desnecessária. Os exemplos de sete gateways foram convertidos.

262 testes no total.
@mariolucasdev
mariolucasdev merged commit fe2412b into develop Sep 21, 2026
6 checks passed
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 Money: eliminar no tipo o erro de centavos vs reais

1 participant