QR code lifecycle

Every state a Scan to Pay code can be in, the transitions between them, and what each state means for your integration.

Every Scan to Pay code has a codeState that the platform tracks across its lifetime. The state determines whether the code can be scanned, modified, or paid. This page documents every state, what triggers each transition, and what the state means for your integration.

The states are independent of transaction state. A code's codeState is about the code itself; a transaction's TxState is about a specific payment attempt. See Transaction states for the transaction-level state machine.


The five states

StateMeaningCan be paid?
AvailableThe code is active and ready to receive payments.
LockedThe code is currently being processed in a payment flow. Locked codes can't be paid by anyone else until the lock releases.Soft lock (one payer at a time)
UsedA use-once code that was successfully paid. Terminal — the code can never be paid again.
BlockedThe merchant has temporarily disabled the code. Scans fail.
DeletedThe code has been permanently removed. Scans fail.✗ (terminal)

State transitions

                  ┌─────────────────────────────┐
                  │                             │
   create  ──►  Available  ─── block ─────►  Blocked
                  │ ▲                           │
                  │ │                           │
            scan  │ │ unlock                    │
                  ▼ │ (timeout / fail)          │ unblock
                Locked                          │
                  │                             │
                  │ payment succeeds            │
                  │ (useOnce: true)             │
                  ▼                             │
                Used (terminal)                 │
                                                │
                  ┌─────────────────────────────┘
                  │
   delete  ──► Deleted (terminal)

Notes on transitions:

  • Available → Locked happens when a customer scans the code and the Scan to Pay app initiates a payment. The lock prevents two customers from scanning the same code simultaneously and racing each other.
  • Locked → Available happens automatically if the payment times out or fails (use-many codes return to Available; use-once codes that fail typically also return so the customer can retry).
  • Locked → Used happens when a use-once code is successfully paid. Terminal.
  • Available ↔ Blocked is controlled by the merchant via the Managing QR codes block / unblock endpoints.
  • Available → Deleted is controlled by the merchant via the delete endpoint. Terminal.

When you'll see each state

Available

The default state after creation. The code can be scanned and paid. For static / re-usable codes (useOnce: false), the code returns to Available after each successful payment so it can be paid again.

Locked

Internal to the platform during a payment in flight. You generally don't act on this state — wait a few seconds and re-query. If a code stays Locked for more than a minute or two, something's wrong; contact support.

If you try to update, block, or delete a code while it's Locked, you'll get a 443 CODE_LOCKED error. Retry once the lock releases.

Used

Only applies to useOnce: true codes. Once a use-once code reaches Used, it's permanently consumed:

  • Scans return an error to the customer's app
  • You cannot update or unblock it back to Available
  • The original code value can never be reused

To accept another payment for the same merchant, create a fresh code.

Blocked

A reversible "off" state. Useful for:

  • End-of-day blocking of static counter codes to prevent off-hours payments
  • Investigating a dispute — block while you reconcile
  • Suspected fraud on a code with unusual scan activity

Unblock returns the code to Available with no other state change. Original merchantReference, amount, and expiry are preserved.

Deleted

Permanent. The code is removed from the system; scans fail with 431 CODE_NOT_FOUND. The original code value cannot be recreated.

Delete is appropriate when:

  • A static code is no longer needed (terminal decommissioned, merchant closed)
  • A printed sticker has been recalled
  • You're cleaning up test codes after sandbox work

For temporary disablement, use Block instead.


Lifecycle for static vs dynamic codes

The same state machine applies to both patterns, but the typical paths differ:

Static QR (useOnce: false):

Available → Locked → Available → Locked → Available → ... (indefinitely)
                                                  → Blocked → Available
                                                  → Deleted

The code spends most of its life in Available and briefly transitions to Locked during each scan. Long-lived; many payments per code.

Dynamic QR (useOnce: true):

Available → Locked → Used (terminal)
       └──────────────────────────► Deleted (if you cancel before scan)

Short-lived; one payment per code, then Used forever.


Querying the current state

Use the Query operation to inspect a code's current state:

curl -X GET "https://qa.scantopay.io/pluto/code/0123456789" \
  -u "$USERNAME:$PASSWORD"

The response includes codeState as one of the values above. See Managing QR codes for the full operation set.


What's next

  • Trigger state transitionsManaging QR codes
  • Errors that surface lifecycle issuesErrors (codes 431, 443, 452, 453, 454, 455, 456)
  • What happens after a code is paid (transaction-level state)Transaction states
  • Recover a stuck code via supportSupport