Skip to content

Currency conversion API

Base paths through the gateway: /api/pay/v1 (Payment), /api/cart/v1 (Cart), /api/order/v1 (Order). A bearer is needed except for the policy read. Amounts are minor units of the currency named beside them, at its ISO 4217 exponent (JPY has none, KWD has three).

The cart and order routes answer the buyer: the cart’s owner, or the order’s buyer, acting as that profile (platform operators aside). Anyone else gets 403 (Cart_Shopping_VL_Authorization_RJ for a cart), and a cart is created only for the profile the caller acts as.

Vocabulary: the settlement currency is the seller’s — the cart / order currency the seller is paid in; the presentment currency is what the buyer’s card is charged in. A quote converts a settlement amount into a presentment amount at a rate the payment provider honours for a fixed window, plus the platform’s currency-conversion add-on (default 3 %) which the platform keeps. The seller always receives the full settlement amount.

Whether the option is offered and the add-on to disclose.

{ "data": { "isEnabled": true, "markupBps": 300, "defaultLockDuration": "hour" } }

Body { "currencyCode": "CAD" }. Records the buyer’s presentment currency on the cart and locks a rate for the cart’s estimate total. Answer: the cart detail with an fxLock block:

FieldMeaning
presentmentCurrencyCodeThe buyer’s currency
fxQuoteIdThe Payment BC quote backing the cart (null while none — e.g. an empty cart)
lockedRateBuyer-facing rate, presentment per one settlement unit, add-on included (1 USD = 1.41 CAD)
markupBpsThe platform add-on, basis points
settlementTotalEstimateMinor / markupEstimateMinor / presentmentTotalEstimateMinorThe estimate total, the add-on on it, and what the buyer’s card would be charged
expiresAtUtc, lockDurationWhen the lock lapses; none / five_minutes / hour / day
isUsableFalse once the lock lapsed (2-minute grace) or the total outgrew the quote — lock again

Passing the cart’s own currency (or null) clears the preference and the lock. A usable lock for the same currency is kept (each provider lock carries a premium). 503 Cart_Currency_UP_Lock_FL = no rate could be locked (the provider does not quote the pair, or the option is switched off); the cart is unchanged and the buyer can still pay in the seller’s currency.

Checkout (POST /cart/v1/carts/{id}/checkout) re-locks a lapsed quote for the refreshed total and carries the lock onto the order (CartCheckedOut.v1 → fxLock).

Same body and answer shape (OrderDetailDto.fxLock, amounts for the order’s outstanding total — settlementAmountMinor / markupMinor / presentmentAmountMinor). The order is the invoice the buyer pays: this is where a lapsed lock is refreshed, or a currency chosen for an order that was placed without one (an appointment invoice, for instance). Only while the order awaits payment (409 Order_OrderCurrency_UP_NotPayable_RJ otherwise); 503 Order_OrderCurrency_UP_Lock_FL = no rate. GET /order/v1/orders/{id} and …/by-source-cart/{cartId} return the same fxLock block.

Paying under the lock: POST /pay/v1/checkout

Section titled “Paying under the lock: POST /pay/v1/checkout”

Send the order’s settlement amount and currency as usual, plus fxQuoteId from the order’s fxLock:

{
"accountId": "…", "subjectProfileId": "…", "orderId": "…",
"amountInBaseMinor": 10000, "baseCurrencyCode": "USD",
"fxQuoteId": "7f1e5a0c-…", "idempotencyKey": "order-pay-…"
}

The Payment BC opens the provider intent for presentmentAmountMinor in the presentment currency with the provider’s quote attached, so the rate is honoured; the ledger and the order keep the seller’s amount. The intent’s originalAmount / originalCurrencyCode are the presentment figures. Refusals, all 409: Payment_FxQuote_UP_Repriced_RJ (see below), Payment_FxQuote_VL_Mismatch_RJ (the quote is for another currency or account), Payment_FxQuote_VL_Cap_RJ (the amount outgrew the quote — refresh), Payment_FxQuote_UP_Expired_RJ (only when a fresh quote could not be taken either).

The quote you send is an estimate (2026-09-22)

Section titled “The quote you send is an estimate (2026-09-22)”

A quote that is not an active honoured lock — it lapsed, it was indicative (lockDuration: "none"), or the provider refuses it at charge time — does not fail the checkout. The Payment BC takes a fresh quote right before opening the provider intent and compares its presentment amount with the estimate your quote implied:

Drift from the estimateWhat happens
≤ the platform’s refresh tolerance (admin-set, default 1 %)The intent opens on the fresh quote. The answer carries an fx block — fxQuoteId, presentmentAmountMinor, refreshedFlag: true, refreshedFromQuoteId, estimatedPresentmentAmountMinor, driftBps — so you can show the buyer the amount actually charged.
> the tolerance409 Payment_FxQuote_UP_Repriced_RJ. Nothing is charged. Re-lock (PUT …/currency) to read the new amount and let the buyer confirm before opening the checkout again.

GET /pay/v1/fx-policy (anonymous) returns refreshToleranceBps alongside markupBps and defaultLockDuration, so a storefront can tell the buyer up front how far the rate may move without a second confirmation.

The PaymentIntentSucceeded.v1 event carries the quote the charge actually rode — fxQuoteId, fxLockedRate, fxPspExchangeRate, fxMarkupBps, fxLockDuration plus fxRefreshedFromQuoteId and fxEstimatedPresentmentMinor — and the Order BC re-aligns the invoice’s fxLock with it.

POST /pay/v1/fx-quotes (settlementCurrencyCode, presentmentCurrencyCode, settlementAmountMinor, optional lockDuration, subjectKind CART | ORDER | BOOKING | INVOICE | OTHER, subjectId) answers an FxQuoteDto with both rate directions (providerRate / providerExchangeRate), the add-on and the amounts; GET /pay/v1/fx-quotes/{id}?amount= re-prices it; GET /pay/v1/fx-quotes/{id}/convert?amount= returns just the buyer-facing amount. 503 Payment_FxQuote_AD_Unavailable_FL = the provider cannot quote the pair right now.

markup = ceil(settlement × bps / 10000); presentment = ceil((settlement + markup) / providerExchangeRate) — both rounded up in minor units so the seller never nets less than the price. Refunds and disputes are converted back by the provider at the current rate, not the locked one; neither the provider’s FX fee nor the add-on is returned.