Overview
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.
Before you refund — check availability
Refundability is a property of the purchase, not a global rule. Read it before offering a refund in your own interface.
GET https://gate.reviopay.com/api/v1/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.
Full refund
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 '{}'Partial refund
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.
Worked example
| 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 refund generates a payment object
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.
Asynchronous behaviour
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.
When a refund fails
A processing failure returns HTTP 400 with error code purchase_refund_error. The response itself does not carry the reason — retrieve it:
GET https://gate.reviopay.com/api/v1/purchases/{id}/Read transaction_data.attempts[], newest element first. The matching attempt’s .error carries the code and description.
Common causes
| 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 |
Refund, release or cancel?
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.
Refunds are not reversible
There is no void or cancel operation for a refund. Practical consequences:
- Confirm before submitting. Put the confirmation step in your own interface — the API will not give you a second chance
- A refund sent in error can only be recovered by charging the customer again, which needs their consent and a fresh authorisation. Their stored token, if you hold one, makes this possible but does not make it automatic
- Guard against double submission. There is no idempotency key, so a retried request is a second refund. Disable the control after the first click and reconcile against
refundable_amountbefore retrying
Chargebacks are not refunds
A 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.
Reconciliation
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.
Checklist before going live with refunds
- Read
refund_availabilitybefore showing a refund control - Read
refundable_amountrather than tracking balances yourself - Send amounts as integers in cents
- Subscribe to
payment.refundedandpurchase.refund_failure - Treat HTTP 200 as accepted, not completed
- Prevent double submission — there is no idempotency key
- Check for
status: chargebackbefore refunding - Use
/release/for holds and/cancel/for unpaid purchases