Skip to main content

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: send approval_token when 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_status on GET /orders/{id} is served or completed). Other sales types and the replacement order of an exchange are not affected.

Request​

  • What tenders must 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: tenders must add up to delta_amount. When the merchant turned off "Terima pembayaran selisih", the call answers 422 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 answer 422 UNPROCESSABLE, and so does a total other than refund_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: tenders must 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.
  • A tender names its payment method by payment_method_id: a cash or custom method from GET /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 with POST /orders. Only the app that owns that order source can use it.
  • GET /payment-methods does not list the rail. Its id is the payment_method_id on the order's tender (GET /orders/{id}), but only when the order was paid on that rail (an order sent without tenders; an order sent with its own tenders has 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_FAILED on the field with code NOT_ALLOWED.
  • When expected_payment_fee differs from the fee the server prices, the answer is 409 CART_REPRICED: quote again.

Behaviour​

  • When the replacement costs less, refund_amount on the record is the difference paid back to the customer (variance_amount when 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_token is not used up.

Retries and idempotency​

  • After a timeout or a network error, retry with the same Idempotency-Key and 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 after 409 DATA_CONFLICT (see the next bullet).
  • On 409 DATA_CONFLICT the 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 read GET /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_FULFILLED is not stored: once the kitchen has served or completed the order, retry with the same key and the same body.

Errors​

StatusCodeWhen
400VALIDATION_FAILEDThe 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).
400IDEMPOTENCY_KEY_REQUIREDIdempotency-Key is missing.
400USER_ID_REQUIREDX-User-ID is missing.
403PLAN_FEATURE_REQUIREDThe 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.
403APPROVAL_REQUIREDThe outlet needs an approval and no token was sent.
403APPROVER_NOT_ELIGIBLEThe approver is not allowed.
403APPROVAL_TOKEN_INVALIDThe token is bad; see data.reason.
403PERMISSION_DENIEDThe acting user may not do this.
403OUTLET_FORBIDDENThe outlet is not allowed.
404NOT_FOUNDUnknown original order, or an order of another outlet.
409SHIFT_NOT_OPENThe acting user has no open shift at the outlet.
409CART_REPRICEDexpected_payment_fee differs from the fee the server prices; quote again.
409INSUFFICIENT_PETTY_CASHA cash refund of a price difference is larger than the shift's petty cash.
409IDEMPOTENCY_KEY_REUSEDThe key was used with another body.
409IDEMPOTENCY_IN_PROGRESSThe first request with this key is still running.
409DATA_CONFLICTSee Retries and idempotency.
409DINE_IN_ROUND_UNPAIDThe Dine In round's bill is not paid yet.
409SETTLEMENT_SALE_NOT_REVERSIBLEThe order is the payment record of a whole table (dine-in or online settlement).
409KITCHEN_NOT_FULFILLEDThe kitchen ticket is not served or completed, or there is no ticket; see data.kitchen_status (null when there is no ticket).
422UNPROCESSABLEA business rule failed; read message.
422POST_SALE_DISABLEDThe merchant turned this operation off in the post-sale policy.
422POST_SALE_WINDOW_EXPIREDThe order is older than the policy allows; data.max_days is the limit in days.
422INVALID_USERThe acting user is not valid.
422PARTNER_REFUND_ORIGINAL_TENDERA partner order: a cash tender is above the cash the order was paid with.
422PRICE_AMOUNT_OVERFLOWA replacement line's quantity, or the replacement total with its tax and fees, is too large to price. Nothing is recorded.

Request​

Responses​

The new exchange, with the same body as GET /exchanges/{exchange_id}.