Table of contents

Advanced refund management

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 /purchases/{id}/
FieldMeaning
refund_availabilityWhat kind of refund this purchase permits
refundable_amountHow much is still available to refund

refund_availability values

ValueMeaning
allFull and partial refunds both permitted
full_onlyThe whole amount, or nothing
partial_onlyPartial refunds only
pis_allFull and partial, via payment initiation
pis_partialPartial only, via payment initiation
noneNot 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

StepRequestrefundable_amount after
Purchase paid149900
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.

SignalMeaning
status: pending_refundAccepted, still processing at the acquirer
payment.refunded webhookCompleted. Carries the Payment generated by the refund
purchase.refund_failure webhookFailed after acceptance
status: refundedFully 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 /purchases/{id}/

Read transaction_data.attempts[], newest element first. The matching attempt's .error carries the code and description.

Common causes

CauseWhat to do
Amount exceeds refundable_amountRead the current value and resubmit within it
Partial refund on a full_only purchaseRefund the full amount instead
refund_availability: noneHandle off-platform; contact support
Purchase not in a refundable stateAn authorisation on hold is released, not refunded
Acquirer or issuer rejectionRetry 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 statusOperationEffect
created — never paidPOST /purchases/{id}/cancel/Cancels the purchase
hold — authorised, not capturedPOST /purchases/{id}/release/Releases the reserved funds. No refund involved
paid — capturedPOST /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_amount before 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.

EventMeaning
payment.charged_backA chargeback has been raised against the payment
payment.chargeback_reversedThe 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

  1. Read refund_availability before showing a refund control
  2. Read refundable_amount rather than tracking balances yourself
  3. Send amounts as integers in cents
  4. Subscribe to payment.refunded and purchase.refund_failure
  5. Treat HTTP 200 as accepted, not completed
  6. Prevent double submission — there is no idempotency key
  7. Check for status: chargeback before refunding
  8. Use /release/ for holds and /cancel/ for unpaid purchases