When a customer pays in crypto, the merchant shows an amount in a coin that is priced against a fiat currency. Prices move, so the quote cannot stay valid forever. Most gateways handle this with a price-lock window: the quoted amount is honoured for a fixed number of minutes, then expires. If you are integrating a gateway into your own checkout, you still need to reflect that window correctly in your order logic and UI.
The data you need per quote
{
"order_id": "A-10293",
"fiat_amount": "49.00",
"fiat_currency": "USD",
"crypto_amount": "49.12",
"crypto_currency": "USDT",
"network": "TRC20",
"quote_created_at": "2026-10-30T02:10:00Z",
"quote_expires_at": "2026-10-30T02:25:00Z"
}
Store the expiry from the gateway response. Do not compute it yourself; the gateway is the source of truth.
States to model
A simple state machine avoids most bugs:
-
awaiting_paymentwhile the quote is valid -
expiredwhen the window passes with no payment detected -
detectedwhen a transaction is seen but not yet confirmed -
paidafter the gateway reports enough confirmations -
underpaidoroverpaidwhen the amount differs from the quote
Transitions should only be triggered by gateway webhooks or by your own scheduled expiry job, never by the browser.
The expiry job
from datetime import datetime, timezone
def expire_stale_quotes(db):
now = datetime.now(timezone.utc)
rows = db.query(
"SELECT order_id FROM quotes WHERE state='awaiting_payment' AND quote_expires_at < %s",
(now,))
for (order_id,) in rows:
db.execute("UPDATE quotes SET state='expired' WHERE order_id=%s AND state='awaiting_payment'",
(order_id,))
The AND state='awaiting_payment' guard matters. If a webhook marks the order as detected a second before the job runs, the job must not overwrite it.
Late payments
Customers sometimes pay after expiry, especially when network fees are high or they copy the address and come back later. Decide the policy in advance: accept and fulfil if the amount still covers the current price, or refund minus network fees. Write the policy on the checkout page so support does not improvise.
UI details
- Show a countdown, but compute it from the server's expiry time, not from page load.
- When the quote expires, replace the address and QR code with a "Get a new quote" button. Leaving an expired address visible invites late payments.
- Display the network clearly. Sending USDT on the wrong network is the most common customer mistake.
Testing
Use the gateway's sandbox to simulate payments just before and just after expiry, partial payments and duplicate webhooks. These edge cases cause most support tickets in production.
If you are still choosing a provider, our a crypto payment gateway guide covers settlement options, fees and what to check before integrating.
Published by PandaWM, an overseas marketing team based in Hong Kong.
Top comments (0)