DEV Community

Peter Panda
Peter Panda

Posted on

Idempotent Order Handling for Crypto Checkout: Avoiding Double Fulfilment

Card payments trained developers to expect one clean "succeeded" event. Crypto checkout is messier: a payment can be detected, confirmed, under-paid, over-paid, or arrive after the invoice expired. If fulfilment logic is not idempotent, you will eventually ship twice or credit a game account twice. Here is a state model that has worked for merchants we support.

Model the invoice as a state machine

created -> pending (tx seen) -> confirmed -> fulfilled
                \-> underpaid -> (top-up) -> confirmed
                \-> expired -> late_payment -> manual_review
confirmed -> refunded (manual)
Enter fullscreen mode Exit fullscreen mode

Every webhook or polling result moves the invoice through allowed transitions only. Disallowed transitions are logged and ignored.

Use a unique constraint, not just an if-statement

CREATE TABLE fulfilments (
  invoice_id TEXT PRIMARY KEY,
  fulfilled_at TIMESTAMP NOT NULL
);
Enter fullscreen mode Exit fullscreen mode

Fulfilment code inserts a row first. If the insert fails because the row exists, stop. This survives concurrent webhook deliveries in a way that "check then act" code does not.

def fulfil(invoice_id):
    try:
        db.execute("INSERT INTO fulfilments VALUES (?, now())", [invoice_id])
    except UniqueViolation:
        return "already fulfilled"
    deliver_goods(invoice_id)
Enter fullscreen mode Exit fullscreen mode

Decide confirmation thresholds per value

Low-value digital goods can be released at the first confirmation the gateway reports as final. High-value orders can wait for more. Put the thresholds in config, not code.

Late payments need a human

A customer pays after the invoice expired, perhaps because the exchange took time. The amount may no longer match the quoted rate. Route these to manual review with a clear support script rather than auto-fulfilling or ignoring.

Under-payments: tolerance and top-up

Set a tolerance (for example 0.5 percent) inside which the order is accepted. Outside it, offer a top-up link for the difference or a refund. Never fulfil partially paid orders silently.

Refunds are new transactions

A crypto refund is a separate outbound payment, usually to an address the customer confirms. Record it as its own entry linked to the original invoice, with its own transaction hash.

Reconciliation hooks

Store the invoice ID, transaction hash, network, amount received and fulfilment time together. At month end, finance can match gateway settlement reports against fulfilled orders in one query.

We go deeper into refund policy and reconciliation in an operations guide to crypto refunds and reconciliation. The gateway our Hong Kong team works with serves overseas merchants only, but the state model applies to any provider.

Published by PandaWM, an overseas marketing team based in Hong Kong.

Top comments (0)