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
| State | Meaning | Can be paid? |
|---|---|---|
Available | The code is active and ready to receive payments. | ✓ |
Locked | The 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) |
Used | A use-once code that was successfully paid. Terminal — the code can never be paid again. | ✗ |
Blocked | The merchant has temporarily disabled the code. Scans fail. | ✗ |
Deleted | The 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 → Lockedhappens 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 → Availablehappens automatically if the payment times out or fails (use-many codes return toAvailable; use-once codes that fail typically also return so the customer can retry).Locked → Usedhappens when a use-once code is successfully paid. Terminal.Available ↔ Blockedis controlled by the merchant via the Managing QR codes block / unblock endpoints.Available → Deletedis controlled by the merchant via the delete endpoint. Terminal.
When you'll see each state
Available
AvailableThe 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
LockedInternal 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
UsedOnly 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
codevalue can never be reused
To accept another payment for the same merchant, create a fresh code.
Blocked
BlockedA 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
DeletedPermanent. 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 transitions → Managing QR codes
- Errors that surface lifecycle issues → Errors (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 support → Support
Updated 4 days ago
