Repository navigation
PHPAY-173: feat: bill by items, where the gateway has no amount - #176
Merged
Merged
Conversation
Three of the ten have no amount on the charge at all: the PagBank and the Pagar.me build it from items, the AbacatePay from products. Item is the one shape for a line that is spelled name/unit_amount, then description/amount, then name/price. A scalar intent becomes a single line, which is what the Efi charge already did for billets, and an itemised intent on the seven that take a scalar sends the total and drops the line detail. That second direction is a limit of those APIs, so it is written down rather than silent - the amount never changes, because it is the sum and it is authoritative. The AbacatePay refuses a product under one real before any HTTP instead of rounding up, since rounding charges the wrong value. Its externalId comes from the item's reference and, without one, is derived from the name: drawing a random id would give the same product a new identity on every charge. It is also the only one of the ten that needs a return and a completion URL, because its charge is a checkout link - those belong to the application, so they arrive through withExtra() and are refused when missing.
This was referenced Oct 26, 2026
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.
Fase 2 de #173. Agora o
ChargeIntentcobre Pix em sete dos dez.O que entra
PHPay\Support\ItemChargeIntent::comItens()ChargeIntent::itemsOrOne()fromIntent()+supportedMethods()+createCharge()Por que
Itemprecisava existirTrês dos dez não têm valor na cobrança. O PagBank e o Pagar.me somam
items, a AbacatePay somaproducts— e cada um escreve a linha diferente:name,quantity,unit_amountdescription,quantity,amountexternalId,name,quantity,price(mínimo 100)As duas conversões, e o que custa cada uma
getItems()da Efí já fazia isso no boleto, e agora está escrito num lugar em vez de trêsA segunda é limite da API deles, e está documentada em vez de acontecer
calada. O dinheiro nunca se perde:
amounté a soma e é o que vai — há testeprovando que um intent por itens na Woovi manda
14980e nenhumitems.Três decisões que o desenho não previa
1. A AbacatePay exige duas URLs. É a única dos dez cuja cobrança é um
link de checkout, então precisa de
returnUrlecompletionUrl. Elas são daaplicação, não da descrição da cobrança, então vêm por
withExtra()— e achamada recusa quando faltam, com 0 requisições, em vez de inventar uma.
2. O
externalIdé derivado do nome, não sorteado. É o id do produto noseu sistema. Quando o
Itemtrazreference, vai ele; sem ele,sha1donome truncado — sortear daria ao mesmo produto um id novo a cada cobrança, e há
teste provando que duas cobranças iguais produzem o mesmo id.
3. O mínimo de R$ 1,00 da AbacatePay é recusado, nunca arredondado.
Arredondar cobraria o valor errado, que é o erro que o
Moneyexiste paraimpedir. Recusa antes de qualquer HTTP.
Um teste meu da fase 1 quebrou, e estava certo quebrar
ChargeIntentAcrossGatewaysTestusava o PagBank como exemplo de gatewayque não declara a capacidade. Depois desta fase ele declara, então troquei o
exemplo pela Rede — que segue fora porque só faz crédito.
Dois erros do PHPStan que valem registro
@throws ValidationException|ApiExceptionsem import, dentro denamespace PHPay\AbacatePay, resolveu paraPHPay\AbacatePay\ValidationException. É a mesma armadilha de resolução queo
CLAUDE.mddescreve paraPHPay\PHPay.Item::sum()declarava@param array<int, self>e checavainstanceof—PHPStan apontou que a checagem seria sempre verdadeira. O
@parampassou aser
array<mixed>, que é o que de fato chega: a função é a fronteira onde oarray de quem chama vira item, e a checagem é o que torna a promessa
verdadeira. É a forma que os
*Request::validate()já usam.Portão
createCharge()e três exercitam a recusa
Falta a fase 3
boleto(),cartao()e um VOCard, para Cielo, Rede e o boleto da Efí — e épor ela que a Efí entra, roteando
PIXparapixCharge()por dentro. A Redenunca entra em Pix: ela só faz crédito.