The refund is against the payment, not the invoice
An invoice can have several charge attempts and only one took the money. That is the one you refund.confirmed payment can be refunded, and where the money comes from
depends on your collection model. Under BYOP the refund runs on the provider
account you connected: it is your money leaving your account. Under Infi
Managed the charge was received on our structure, so the refund leaves from
there and is deducted from your payout — including future payouts, if the amount
was already paid out.
Full or partial: the amount decides the access
Omittingamount refunds everything. And here is the rule that matters:
The logic: a full refund undoes the sale, so the capability the sale created has
to die with it. A partial refund does not undo the sale — R100 guide is goodwill, not a cancellation — and cutting the file there would
punish exactly the customer you just tried to please.
An amount above the total is treated as full, not refused.
When you want the opposite
revokeAccess: false is the policy of plenty of info-products: fighting costs
more than the file. Do not send the field if you do not mean to override —
the derivation above is the right behaviour in almost every case.
What the buyer sees afterwards
Their old link answers410 Gone, with error_code and message in the
body:
revokedAt and stays in the list:
Reading what you refunded
status becomes refunded only once the whole amount is back. A partial
refund leaves the payment confirmed; what says how much came back is
refundedAmount.
"5" and
"5.00" are the same amount. Do not compare the raw text to decide how much
came back.
refunds() returns the individual records, with amount, date and identifier.
The reason field is optional in the response.
The invoice stays paid
On purpose. The invoice records that it was paid, because it was — and
accounting does not erase a fact, it posts the opposite of it. The refund is its
own record, with its own reversing entry in the ledger.
Practical consequence: if you sum paid invoices for your sales report, a
refunded sale counts in full. Subtract refundedAmount from the payments.
Webhook
A refund emitspayment.refunded, with accessRevoked saying whether the
download fell:
accessRevoked is there because your own system almost always has access of its
own to cut — a subscription, a feature flag, a Discord role. Subscribe in
webhooks.
A chargeback is the same mechanic, without you
When the buyer disputes at their bank, the provider sendsPAYMENT_REFUNDED or
the chargeback event and the same path runs: the payment becomes
charged_back, a reversing entry is posted, and access falls — the network takes
the full amount, so the amount-based derivation revokes. You do not have to do
anything, and you cannot prevent it.
The emitted event is payment.chargeback.
Refunding twice is safe. The reversal path only acts on a
confirmed payment. A repeated provider
webhook, or a retry of your own, does not post to the ledger again and does not
rewrite when access fell — and the revocation date is preserved, because that is
what a dispute turns on.