DEV Community

Peter Panda
Peter Panda

Posted on

Implementing a Price-Lock Window for Crypto Checkout Quotes

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"
}
Enter fullscreen mode Exit fullscreen mode

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_payment while the quote is valid
  • expired when the window passes with no payment detected
  • detected when a transaction is seen but not yet confirmed
  • paid after the gateway reports enough confirmations
  • underpaid or overpaid when 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,))
Enter fullscreen mode Exit fullscreen mode

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)