Create a refund
POST/refunds
Gives money back on a paid order without taking goods back. A new refund answers 201; a retry with the same Idempotency-Key answers 201 with the same record.
Before you call
- The company needs a plan with the refund order list.
- With
X-User-ID, the acting user needs the refund permission and an open shift at the outlet. - Without
X-User-ID(MPOS-2389): you may refund your own partner order with no acting user and no open shift. The call then runs as your order source, the same actor asPOST /orderswithout a user. All of these must hold:- the order was created by your application's order source with
POST /orders; - every
refund_tendersrow names your source's partner rail: thepayment_method_idof the order's tender on that rail. Never cash, never another payment method; - the outlet needs no approval for refunds, and you send no
approval_token.
- the order was created by your application's order source with
- Anything else without
X-User-IDanswers400 USER_ID_REQUIRED. Exchanges always needX-User-IDand an open shift. - Approval works as on
POST /returns: sendapproval_tokenwhen the outlet needs an approval (this needsX-User-ID). - An order of a kitchen sales type (
dine_in,takeaway,pickup,delivery,qr_ordering) can be refunded only once its kitchen ticket is served or completed (kitchen_statusonGET /orders/{id}isservedorcompleted). Other sales types and the replacement order of an exchange are not affected.
Request
refund_amountis the money to give back, in whole units ofcurrency. It cannot be more than what is left to refund.refund_tenders: each row names an eligible tender of the order bypayment_method_id(fromeligible_refund_tendersofPOST /returns/quote) or the cash method.- More than one tender is allowed only for a full refund of an untouched order, and the amounts must add up to the refund.
Behaviour
- On a Terms Payment order the list holds the tenders of its settlement payments in place of the Terms Payment line.
- A group with
refundable: false(for example a Mekari Pay QRIS payment, orreason: legacy_tender) is refused: refund it in POS, or to another eligible tender. - A partner order (one created with
POST /orders) is refunded to the original tender, never in cash. - A cash refund larger than the shift's petty cash records nothing.
- A Dine In table bill is several orders, one per round: return, refund or exchange one round at a time. Reverse the rounds a table settlement paid instead of the settlement itself.
- A Dine In round paid through a table settlement lists its share of the settlement's tenders in
eligible_refund_tenders. - The kitchen check runs before the approval, the petty cash and the tenders, so an
approval_tokenis not used up.
Retries and idempotency
- After a timeout or a network error, retry with the same
Idempotency-Keyand the same body: the stored answer is replayed, or the write runs once. - An error from this call is stored under the
Idempotency-Key: after you fix the cause, send a new key. But never send a new key after409 DATA_CONFLICT(see the next bullet). - On
409 DATA_CONFLICTthe same request is already recorded or still being committed, or the acting user's shift was closed while it ran. - Do not send the same request again with a new
Idempotency-Key: a new key can create a second refund. First readGET /refunds?order_id=...for the order, wait a short while, and if the refund is still absent and you still cannot tell, ask the merchant to check the back office before you create again. 409 KITCHEN_NOT_FULFILLEDis not stored: once the kitchen has served or completed the order, retry with the same key and the same body.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | The body is not valid. |
| 400 | IDEMPOTENCY_KEY_REQUIRED | Idempotency-Key is missing. |
| 400 | USER_ID_REQUIRED | X-User-ID is missing, and the call is not your own partner order refunded to your rail with no approval (see Before you call). |
| 403 | PLAN_FEATURE_REQUIRED | The plan has no refund order list. data.reason is not_in_plan or disabled (the merchant can turn it on in Settings > Features), and data.feature is the feature key. A missing Public API add-on answers the same code without data. |
| 403 | APPROVAL_REQUIRED | The outlet needs an approval and no token was sent. |
| 403 | APPROVER_NOT_ELIGIBLE | The approver is not allowed. |
| 403 | APPROVAL_TOKEN_INVALID | The token is bad; see data.reason. |
| 403 | PERMISSION_DENIED | The acting user may not do this. |
| 403 | OUTLET_FORBIDDEN | The outlet is not allowed. |
| 404 | NOT_FOUND | Unknown original order, or an order of another outlet. |
| 409 | SHIFT_NOT_OPEN | You sent X-User-ID and the user has no open shift at the outlet. |
| 409 | INSUFFICIENT_PETTY_CASH | A cash refund is larger than the shift's petty cash. |
| 409 | IDEMPOTENCY_KEY_REUSED | The key was used with another body. |
| 409 | IDEMPOTENCY_IN_PROGRESS | The first request with this key is still running. |
| 409 | DATA_CONFLICT | See Retries and idempotency. |
| 409 | DINE_IN_ROUND_UNPAID | The Dine In round's bill is not paid yet. |
| 409 | SETTLEMENT_SALE_NOT_REVERSIBLE | The order is the payment record of a whole table (dine-in or online settlement). |
| 409 | KITCHEN_NOT_FULFILLED | The kitchen ticket is not served or completed, or there is no ticket; see data.kitchen_status (null when there is no ticket). |
| 422 | UNPROCESSABLE | refund_amount is more than what is left to refund. |
| 422 | POST_SALE_DISABLED | The merchant turned this operation off in the post-sale policy. |
| 422 | POST_SALE_WINDOW_EXPIRED | The order is older than the policy allows; data.max_days is the limit in days. |
| 422 | INVALID_USER | The acting user is not valid. |
| 422 | REFUND_TENDER_NOT_ELIGIBLE | A tender id is not eligible, or its group has refundable: false; the field is in data.fields. |
| 422 | PARTNER_REFUND_ORIGINAL_TENDER | A partner order: a cash row is above the cash the order was paid with. |
| 422 | REFUND_TENDER_INVALID_AMOUNT | A tender amount is not above 0. |
| 422 | REFUND_TENDER_DUPLICATE | The same tender appears twice. |
| 422 | REFUND_MULTI_TENDER_REQUIRES_FULL | More than one tender, but not a full refund of an untouched order. |
| 422 | REFUND_TENDER_SUM_MISMATCH | The tender amounts do not add up to the refund. |
| 422 | REFUND_TENDER_EXCEEDS_REMAINING | A Dine In round asks more of one non-cash tender than it can still take. |
Request
Responses
- 201
- 400
- 401
- 403
- 404
- 409
- 422
The new refund, with the same body as GET /refunds/{refund_id}.
Error envelope
Error envelope
Error envelope
Error envelope
Error envelope
Error envelope