PHPAY-91: value object Money — eliminar no tipo o erro de centavos vs reais - #92
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.50num gateway de centavos não quebra a integração — cobra R$ 1,00, e você só descobre na conciliação.A solução
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 usarcentavos().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 * 100dá7.000000000000001; há teste para isso.Aritmética mínima:
multiply()eplus(), 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 umsetAmount()explícito, que escreve a chave certa na unidade certa:Compatibilidade
Totalmente aditivo. Todo método aceita
Money|int(ouMoney|int|floatnos 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 noCLAUDE.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).