Refund Payment

Refunds a captured payment, in full or in part.

Partial and multiple refunds

amount is the amount to refund in this call, not a running total — you can call this endpoint more than once against the same payment as long as the sum of all refunds issued so far stays below the original captured amount. Check Payment Object's max_refund_amount field (captured amount minus everything already refunded) before issuing another partial refund. The response's refund_amount is the cumulative total refunded so far, not just this call's amount — for example, two separate amount: 10 calls against a 50 AED payment return refund_amount: 10.0 then refund_amount: 20.0.

Fees

Refunding never carries its own fee. The original processing fee charged on the payment is not returned to you — this applies to partial refunds too, so refunding half a payment doesn't refund half the fee.

Timing: this is asynchronous under the hood

A 200 response means the refund was accepted, not necessarily that money has already moved. Internally a refund passes through pending → initiated → success (or failed):

  • pending — accepted but not yet submitted to the gateway, typically because your wallet balance doesn't currently cover the refund amount. It's picked up automatically once funds are available; there's no separate call you need to make to progress it.
  • initiated — submitted to the payment gateway and awaiting the gateway's outcome. The payment.refund_initiated webhook fires here.
  • success / failed — the gateway confirmed the refund or it was rejected. The payment.refunded / payment.refund_failed webhook fires here.

The synchronous response reflects whatever state the refund is in the moment the call returns — don't treat a 200 with refund_status: "pending" or "initiated" as final. Subscribe to payment.refund_initiated, payment.refunded, and payment.refund_failed (see Webhook delivery & retries) or poll Fetch Payment Info to confirm the outcome.

What blocks a refund

Amount- and state-based blocks all return the same shape — 422 with error_code: VALIDATION_ERROR and a dynamic message naming the actual limit, not a fixed string. Verified against sandbox: requesting more than max_refund_amount, refunding a payment that hasn't captured, and refunding an already-fully-refunded payment all return {"messages": ["See errors"], "error_code": "VALIDATION_ERROR", "errors": ["Amount must be less than AED <max_refund_amount>"]} — showing AED 0.00 when nothing is left to refund.

  • Payment not yet captured, already fully refunded, or amount exceeds max_refund_amount — all surface as the VALIDATION_ERROR shape above.
  • Invalid or unknown payment identifier — not a 422 at all: returns {"messages": ["Charge record was not found"], "error_code": "RECORD_NOT_FOUND"}.
  • The payment is older than 6 months — refunds aren't accepted past that window (not independently re-verified against a live sandbox payment old enough to trigger it, but confirmed in source: Charges::Refundable::MAXIMUM_REFUND_DURATION = 6.months).
  • The payment has an open dispute — disputed payments can't be refunded through this endpoint (confirmed in source and on the Fetch Dispute page; not independently re-verified live — no disputed payment was available in this sandbox account to trigger it).
  • A refund is already pending/initiated on the same payment — not independently re-verified live (sandbox refunds resolved synchronously as success in testing, with no pending window to reproduce this against).

Marketplace / split payments

If the payment used payouts_share, refunding it does not automatically claw back the recipient's share — see Marketplace & Split Payments for how to reconcile that yourself.

Path Params
string
required
Defaults to MB-LINK-D8B07FB8C7

Payment ID

Body Params
number
required
≥ 1
Defaults to 10

Amount to refund in this call (not a running total). Must not exceed the payment's max_refund_amount. Only AED transfers supported

Headers
string
Responses

400

Bad Request

Language
URL
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json