Building a Payment UI
Patterns for building the screen a user sees after an order is created: deposit instructions, countdowns, status tracking, and refunds. Based on the pitfalls integrators hit most often.
The pending-order route
A common pattern is a shareable/reloadable route keyed only by unique_reference (e.g. /order/ABCDEF123456) that renders from GET /orders/{unique_reference}/.
This works, with one important caveat:
Mode flags are not reliably reconstructible from a detail read alone — DeFi detail responses may omit is_defi, and CeFi fixed/floating intent is not echoed back as a flag. If your payment UI branches on mode (fixed-rate countdown, DeFi explorer links, recreate flow), store the mode alongside the reference at creation time instead of inferring it from the GET response.
What the detail response does reliably give you: status, amounts, addresses and extra IDs, payment_window_minutes, fixed_rate_deadline, rate, transaction hashes, and (CeFi only) amount_usd.
Deposit instructions
Render from the create/detail response:
deposit_address— the address the user must pay. Always show copy-to-clipboard and a QR code.deposit_address_extra_id— when non-empty, the memo/tag is mandatory; display it as prominently as the address (funds sent without it can be delayed or lost).deposit_amount+deposit_currency— the exact amount to send. For DeFi floating orders amounts may benull— show "send any amount within limits" UX based on the pair's limits fromGET /rate/?is_defi=true.
Countdown: which deadline to show
Two fields describe the payment deadline:
| Field | Type | Use |
|---|---|---|
fixed_rate_deadline | UTC timestamp or null | When present, this is the authoritative moment the quoted rate stops being honored — count down to it |
payment_window_minutes | integer | Fallback window from created_on when no fixed deadline exists |
Recommended logic:
- If
fixed_rate_deadlineis set → countdown to it (it equalscreated_on + payment_window_minutes). - Else (
fixed_rate_deadline: null) → the order is floating-rate: there is no rate deadline. Show "rate determined when your deposit arrives" instead of a countdown. CeFi floating orders carrypayment_window_minutes: 0; DeFi floating orders may carry a small nominal window (e.g.1) — ignore it and key off the nullfixed_rate_deadline.
The deadline applies to the user's deposit, not to Nexchange's processing time afterwards.
When the countdown hits zero
An expired unpaid order will move to CANCELED (see Order Lifecycle). Offer a recreate flow: fetch a fresh quote for the same pair/amount (refresh-on-submit), create a new order, and route the user to the new reference. Never let the user pay against an expired order's address.
Status tracking
Poll GET /orders/{unique_reference}/ while the order is in a non-terminal status — cadence and stop conditions in Order Tracking, or subscribe to Webhooks.
Map statuses to user-facing stages rather than exposing raw values — a suggested grouping:
| UI stage | Statuses |
|---|---|
| Awaiting payment | INITIAL |
| Payment detected | UNCONFIRMED PAYMENT |
| Processing | PAID, PRE-RELEASE, PENDING KYC |
| Sending to you | RELEASED |
| Done | COMPLETED |
| Expired | CANCELED |
| Refund in progress | INITIATED REFUND, REFUND FAILED |
| Refunded | REFUNDED |
Show withdraw_transaction with an explorer link once populated (RELEASED onward).
"I have paid" and cancellation
- Optional accelerator:
PATCH {"marked_as_paid": true, "deposit_transaction": "<hash>"}after the user confirms sending — speeds up crediting but is never required (chain monitoring detects deposits automatically). - Cancel an unpaid order with
PATCH {"marked_as_paid": false}. - Both are described in Update Order.
Refund UX
- Collect
refund_addressat creation whenever possible (mandatory for DeFi orders whose destination chain is non-EVM — Solana, Tron, Bitcoin). It must be an address on the deposit currency's chain — refunds return where funds came from. For chains that need a memo/tag, also collectrefund_address_extra_id— refund transactions carry it. REFUNDEDis a terminal outcome of a failed swap, not a variant of success: the user got their deposit back instead of the swapped funds. Present it that way ("swap could not be completed — your funds were returned"), with the refund destination shown.REFUND FAILEDis transient — refunds are retried (INITIATED REFUND → REFUND FAILED → INITIATED REFUND → REFUNDED). Keep the user informed rather than surfacing it as a dead end.
Safe retries
Retry semantics differ by mode (201 vs 200):
- DeFi: creation is idempotent — re-sending an identical request returns
200with the existing order. On a client-side timeout, just retry. - CeFi: every create mints a new order. On a timeout, first check whether the order already exists (list recent orders for the pair with
status=INITIAL) before re-creating — otherwise a reload can leave the user with two orders and two deposit addresses.
Next steps
- Order Lifecycle — full status semantics
- Order Tracking — polling cadence
- iframe / Widget — if you'd rather embed a ready-made UI