Repository navigation
PHPAY-163: Efí — multa, juros e itens da cobrança deixam de ser fixos no código - #164
Merged
Merged
Conversation
Three problems in the same resource, two of them about money. All three were confirmed against a mocked client before anything was changed. Fine and interest were hardcoded. getConfigurations() fell back to ['fine' => 200, 'interest' => 33] with no setter, so every billet this library created carried 2% of fine and 0.033% of interest per day, chosen by nobody. Efi's own documentation says the two fields use DIFFERENT scales: fine is percent x 100 and interest is percent x 1000. setConfigurations() now exists, and the validator refuses a decimal by repeating both scales rather than letting a wrong value through. The default stays. Removing it would be a behaviour change in a minor: a billet from an integration that never configured anything would stop carrying charges the merchant may be counting on. What changed is that it is visible and overridable, and ['fine' => 0, 'interest' => 0] bills without it. An empty array is refused, because it does not say "no charges" and Efi does not document whether the object can be omitted at all. A billet could only have one item. setItems() now takes the list, and with explicit items description and value stop being required: they belong to the single-item shortcut, not to the gateway, whose payload has no charge-level amount. create() twice on the same resource failed, and the message blamed the caller for a field they had set: getItems() unset description and value after building the item. The unsets served nothing, since the payload is assembled from the items and from expire_at. They are gone, with a regression test. Unknown `configurations` keys pass through, unlike the gateway config array. There the array is the library's own surface and a stray key is a typo; here it is Efi's payload, and refusing a field that works would break whoever sends one. Out of scope, with the reason recorded: converting a percentage into Efi's unit, because the scale of the monthly form of interest is not documented; and `discount`, which is a sibling of banking_billet and not part of configurations.
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 #163.
O
CLAUDE.mdregistrava que oChargeda Efí tinha$itemse$configurationsem setter. Fui conferir e eram três problemas, dois deles com dinheiro.
Confirmei os três contra um client mockado antes de mexer em nada.
1. Multa e juros eram fixos no código
Toda cobrança da Efí saía com isso. E o que significa não estava em lugar
nenhum — fui à documentação da Efí:
fine200interest33As duas escalas são diferentes, e é a própria Efí que documenta assim:
"neste caso, 200 equivale a 2%" e "33 equivale a 0,033%". É a armadilha que
o
Moneyexiste para fechar — errar não quebra a integração, cobra errado. Porisso os dois são inteiros, e o validador recusa decimal repetindo as duas
escalas na mensagem: 2,5% de multa é
250.O default fica, e é decisão
Tirar seria mudança de comportamento numa minor: boleto de integração que
nunca configurou nada pararia de carregar encargo que o lojista pode estar
contando. Ele continua, mas agora é visível e sobrescrevível.
Sem encargo é
['fine' => 0, 'interest' => 0]. Array vazio é recusado — nãodiz "sem encargo", e a Efí não documenta se o objeto
configurationspodeser omitido, então não vou shippar um caminho cujo comportamento no servidor não
confirmei. A mensagem aponta a saída que existe.
2. Boleto só podia ter um item
O payload da Efí não tem valor no nível da cobrança — tem
items. OsetAmount()com adescriptioné o atalho que monta um item, e era o únicocaminho. Agora:
Com itens explícitos,
descriptionevaluedeixam de ser exigidos: elespertencem ao atalho, não ao gateway, e exigi-los junto seria pedir campo que não
vai a lugar nenhum.
3.
create()duas vezes mentia para quem chamouO mais afiado. O
getItems()mutava o estado:E a segunda chamada falhava com "O campo description é obrigatório" — culpando
quem chamou por um campo que ele preencheu. Os
unset()não serviam para nada: opayload é montado a partir dos itens e de
expire_at. Saíram, com teste deregressão.
Uma assimetria de propósito
Chave de
configurationsque esta versão não modela passa adiante, aocontrário do array de configuração do gateway, que recusa chave desconhecida. Lá
o array é superfície da biblioteca e chave estranha é typo; aqui é payload da
Efí, e recusar um campo que funciona quebraria quem o manda. Está comentado no
validador.
Duas coisas que vale dizer
Adicionei dois métodos ao
ChargeInterfaceda Efí. Isso é tecnicamentequebra para quem implementa a interface — o mesmo cuidado que manteve o
SupportsWebhookSignaturedevolvendoobjectno #146. A diferença: aquela éinterface de capacidade, que gateway de terceiro implementa; esta é interface
de Resource interna do gateway, e
EfiGatewayInterface::charge()devolve aclasse concreta, então nada em prática depende de ela ser fechada. Segui o padrão
do repositório, em que todo setter de Resource está na interface dele.
Corrijo uma coisa que eu disse de passagem: cheguei a achar que a criação de
boleto da Efí estava sem cobertura. Está coberta — forma do payload e unidade do
valor, no
EfiGatewayTest. O que não tinha teste eraconfigurations, itemmúltiplo e reuso do Resource, que é como o bug de mutação sobreviveu.
Fora de escopo, com o motivo
setFine(2.0)): a escala da forma de objeto deinterest(['value' => 330, 'type' => 'monthly']) não está documentadapela Efí. Por analogia pareceria ×1000, mas meia conversão segura é pior que
nenhuma — então a lib aceita a forma de objeto e não converte nada.
discount: não vive emconfigurations, é irmão debanking_billet.Verificação
composer test: lint, 770 testes (+27), PHPStan nível 9, 113 links. Smokecom 97 verificações.
Os 27 cobrem o default que sai, a troca, o boleto sem encargo, o array vazio
recusado, decimal recusado nas duas escalas, a forma de objeto de
intereste otype, odays_to_write_off, campo desconhecido passando, item múltiplo, itensdispensando o atalho, a validação de item em centavos — e o
create()duasvezes.