How refunds behave on the card API: what can be refunded, how much, how partial refunds accumulate, and what happens when one fails.
One rule shapes everything below: a refund cannot be voided. Once submitted it runs to completion. There is no reversal operation, so the decision to refund is final at the point you make the call.
Refundability is a property of the purchase, not a global rule. Read it before offering a refund in your own interface.
GET /purchases/{id}/ | Field | Meaning |
|---|---|
refund_availability | What kind of refund this purchase permits |
refundable_amount | How much is still available to refund |
refund_availability values| Value | Meaning |
|---|---|
all | Full and partial refunds both permitted |
full_only | The whole amount, or nothing |
partial_only | Partial refunds only |
pis_all | Full and partial, via payment initiation |
pis_partial | Partial only, via payment initiation |
none | Not refundable through the API |
Do not assume all. Refundability varies by payment method, and a purchase paid by a method that cannot be refunded programmatically will return none — in which case the refund has to be handled off-platform.
Omit amount and the entire remaining balance is refunded.
curl -X POST https://gate.reviopay.com/api/v1/purchases/$PURCHASE_ID/refund/ \
-H "Authorization: Bearer $PRECIUM_API_KEY" \
-H "Content-Type: application/json" \
-d '{}' Send amount as an integer in cents.
curl -X POST https://gate.reviopay.com/api/v1/purchases/$PURCHASE_ID/refund/ \
-H "Authorization: Bearer $PRECIUM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "amount": 50000 }' Partial refunds may be repeated until the original total is exhausted. Each one reduces refundable_amount; when it reaches zero the purchase is fully refunded and further attempts fail.
| Step | Request | refundable_amount after |
|---|---|---|
| Purchase paid | — | 149900 |
| Refund one item | { "amount": 49900 } | 100000 |
| Refund shipping | { "amount": 10000 } | 90000 |
| Refund the balance | {} | 0 |
Always read refundable_amount back rather than tracking it yourself. Chargebacks and reversals can change it without your involvement.
A successful refund returns a Payment object, not a modified purchase. Store its identifier against your own order record — it is what reconciliation reports and dispute correspondence will reference.
Refunds are not always immediate. If the acquirer takes too long you receive HTTP 200 with the purchase in status: pending_refund, together with a purchase.pending_refund webhook.
| Signal | Meaning |
|---|---|
status: pending_refund | Accepted, still processing at the acquirer |
payment.refunded webhook | Completed. Carries the Payment generated by the refund |
purchase.refund_failure webhook | Failed after acceptance |
status: refunded | Fully refunded |
Subscribe to payment.refunded and treat it as the completion signal. A 200 on the refund call means accepted, not settled.
A processing failure returns HTTP 400 with error code purchase_refund_error. The response itself does not carry the reason — retrieve it:
GET /purchases/{id}/ Read transaction_data.attempts[], newest element first. The matching attempt's .error carries the code and description.
| Cause | What to do |
|---|---|
Amount exceeds refundable_amount | Read the current value and resubmit within it |
Partial refund on a full_only purchase | Refund the full amount instead |
refund_availability: none | Handle off-platform; contact support |
| Purchase not in a refundable state | An authorisation on hold is released, not refunded |
| Acquirer or issuer rejection | Retry later; if it persists, contact support with the purchase id |
Three different operations, frequently confused. The purchase's current status decides which applies.
| Purchase status | Operation | Effect |
|---|---|---|
created — never paid | POST /purchases/{id}/cancel/ | Cancels the purchase |
hold — authorised, not captured | POST /purchases/{id}/release/ | Releases the reserved funds. No refund involved |
paid — captured | POST /purchases/{id}/refund/ | Returns funds to the cardholder |
Releasing a hold is materially better for the customer than capturing and refunding: the funds are never taken, so nothing has to travel back. Where you know a charge will not proceed, release rather than capture-then-refund.
There is no void or cancel operation for a refund. Practical consequences:
refundable_amount before retryingA chargeback is initiated by the cardholder through their bank, not by you. It arrives as an event and moves the purchase to status: chargeback.
| Event | Meaning |
|---|---|
payment.charged_back | A chargeback has been raised against the payment |
payment.chargeback_reversed | The chargeback was reversed in your favour |
Do not refund a purchase that has been charged back — you would return the funds twice. Check the status first. Dispute handling is a scheme process, not an API one; contact support on receiving a chargeback event.
Refunds appear in reconciliation reports as separate entries referencing the original purchase. Match on the Payment identifier returned by the refund rather than on the purchase alone, since a purchase may carry several partial refunds. Formats are in Reconciliation.
refund_availability before showing a refund controlrefundable_amount rather than tracking balances yourselfpayment.refunded and purchase.refund_failurestatus: chargeback before refunding/release/ for holds and /cancel/ for unpaid purchases