Create an exchange
POST/exchanges
Takes lines back and sells replacement lines in one step. A new exchange answers 201; a retry with the same Idempotency-Key answers 201 with the same record.
Before you call
- The company needs a plan with exchanges.
- The acting user needs an open shift at the outlet.
- Approval works as on
POST /returns: sendapproval_tokenwhen the outlet needs an approval. - An order of a kitchen sales type (
dine_in,takeaway,pickup,delivery,qr_ordering) can be returned, refunded or exchanged 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
- What
tendersmust hold depends on the difference (delta_amount, the replacement total minus the return total) and on the merchant's exchange settings (the "Tukar barang" page in Pengaturan pesanan of the back office: "Terima pembayaran selisih" and "Refund selisih negatif tukar barang"):- The replacement costs more:
tendersmust add up todelta_amount. When the merchant turned off "Terima pembayaran selisih", the call answers422 UNPROCESSABLE. - The replacement costs less and the merchant refunds the difference ("Refund selisih negatif tukar barang" is on): send exactly one tender, equal to
refund_amount. Two tenders answer422 UNPROCESSABLE, and so does a total other thanrefund_amount("tender total must equal the refund amount"). QRIS is never accepted. - The replacement costs less and the merchant keeps the difference ("Refund selisih negatif tukar barang" is off), or the totals are equal:
tendersmust total 0, so send none. - On a partner order the refunded difference never goes back in cash above the cash the order was paid with: a Cash tender above that answers
422 PARTNER_REFUND_ORIGINAL_TENDER.
- The replacement costs more:
- A tender names its payment method by
payment_method_id: acashorcustommethod fromGET /payment-methods, the order's Terms Payment method, or the order source's own partner rail. - The own partner rail is the
Paid on <display_name>method of the order source that sent the order withPOST /orders. Only the app that owns that order source can use it. GET /payment-methodsdoes not list the rail. Its id is thepayment_method_idon the order's tender (GET /orders/{id}), but only when the order was paid on that rail (an order sent withouttenders; an order sent with its owntendershas no rail tender).- The rail of another order source, the order's rail named by another app, a rail on an order no source sent, and any other type (QRIS included) answer
400 VALIDATION_FAILEDon the field with codeNOT_ALLOWED. - When
expected_payment_feediffers from the fee the server prices, the answer is409 CART_REPRICED: quote again.
Behaviour
- When the replacement costs less,
refund_amounton the record is the difference paid back to the customer (variance_amountwhen the merchant kept it). - A cash refund of a price difference larger than the shift's petty cash records nothing.
- On a partner order (one created with
POST /orders) the difference is paid back to the original tender. - 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.
- 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 exchange. First readGET /exchanges?order_id=...for the order, wait a short while, and if the exchange 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. A tender that is not allowed has code NOT_ALLOWED on the field. A returned line that is not part of the order has a field error on returned_lines[i].order_line_id with code NOT_FOUND (INVALID_FORMAT when the order_line_id is not a UUID). |
| 400 | IDEMPOTENCY_KEY_REQUIRED | Idempotency-Key is missing. |
| 400 | USER_ID_REQUIRED | X-User-ID is missing. |
| 403 | PLAN_FEATURE_REQUIRED | The plan has no exchanges. 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 | The acting user has no open shift at the outlet. |
| 409 | CART_REPRICED | expected_payment_fee differs from the fee the server prices; quote again. |
| 409 | INSUFFICIENT_PETTY_CASH | A cash refund of a price difference 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 | A business rule failed; read message. |
| 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 | PARTNER_REFUND_ORIGINAL_TENDER | A partner order: a cash tender is above the cash the order was paid with. |
| 422 | PRICE_AMOUNT_OVERFLOW | A replacement line's quantity, or the replacement total with its tax and fees, is too large to price. Nothing is recorded. |
Request
Responses
- 201
- 400
- 401
- 403
- 404
- 409
- 422
The new exchange, with the same body as GET /exchanges/{exchange_id}.
Error envelope
Error envelope
Error envelope
Error envelope
Error envelope
Error envelope