Payments and their statuses
A payment exists once the chain confirms it. Until then, four statuses say where it stands.
- created → waitingConfirmation: sent
- waitingConfirmation → created: attempt failed
- waitingConfirmation → completed (final): confirmed
- created → expired (final): expires unpaid
The statuses
| Status | Means | What to do |
|---|---|---|
created | waiting for the customer, or the last attempt failed (lastAttempt) | wait |
waitingConfirmation | a transaction was sent | wait: never offer to pay again |
completed | final: confirmed on-chain and recorded | fulfil (payment.completed) |
expired | final: unpaid at expiresAt, or you expired it | release the order (checkout.expired) |
Confirmations
A transaction completes at the network's confirmation depth (one block on test networks; in live,
each chain's own), counting only QBitFlow's contract's events. No route creates a payment: the
chain does, and the payment and payment.completed are recorded together.
A sent transaction that isn't final in time is never a failure: it may still land, so it stays
waitingConfirmation, and a second can't be signed (409 tx_already_sent).
Failed attempts: lastAttempt
A failed attempt sends the checkout back to created with lastAttempt: its code, the message
the customer saw, txHash when something was sent, and at.
Avoid
Cancel the order on lastAttempt
Do
Give up on expired
checkout.expired is the end. Until then, wait.| What failed | Codes |
|---|---|
| Not enough in the wallet | insufficient_funds, insufficient_native_funds (the coin, for gas) |
| Allowance, approval | insufficient_allowance, allowance_expired, token_not_approved |
| The signature | invalid_signature, signature_expired |
| On the chain | tx_failed (reverted), tx_replaced, tx_expired, tx_not_sent |
| QBitFlow's side | network_unavailable (try later), transaction_refused |
Most are caught before signing (QBitFlow checks the balance first). No webhook announces a failure; the dashboard logs each for 25 months (GET /payment-failures).
The payment
Read one with GET /payments/pay@… or the bare uuid). To find it by
your reference, filter the list: GET /payments (reference=) answers 0 or 1 item.
Here is a payment read:
payments-get.sh
# A payment by its uuid (pay@…, the checkout session's id). By your order reference, the list's# ?reference= filter: 0 or 1 item (none until the customer paid).curl https://api.qbitflow.app/v2/payments/$PAYMENT_UUID \ -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a Payment#curl "https://api.qbitflow.app/v2/payments?reference=order-1042" \ -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a page of Payment ({ items, hasMore, nextCursor? }), items empty until the customer paidconst payment = await client. payments. get( paymentUuid); // pay@…, the checkout session's idconsole.log(payment.uuid, payment.reference, payment.amountUsd, 'USD', payment.metadata); // Runs GET /payments?reference= and returns its one payment: a NotFoundError (404,// not_found) until the customer paid.const byReference = await client. payments. getByReference( 'order-1042');console.log(`order-1042 was paid by ${byReference.from}`);payment = client. payments. get( payment_uuid) # pay@…, the id of its checkout sessionprint(payment.reference, payment.amount_usd, "USD", payment.tx_hash, payment.metadata) # Runs GET /payments?reference= and returns its one payment (NotFoundError when none).by_reference = client. payments. get_by_reference( "order-1042") # your order idprint(by_reference.uuid)payment, err := client.Payments.Get(ctx, paymentUUID, nil) // pay@…if err != nil { log.Fatal(err)}fmt.Println(payment.UUID, payment.Reference, payment.AmountUSD, "USD", payment.Metadata) // Runs GET /payments?reference= and returns its one payment.byReference, err := client. Payments. GetByReference( ctx, "order-1042") // your order idif err != nil { log.Fatal(err) // a *qbitflow.NotFoundError (404, not_found) when no payment has this reference}fmt.Println("order-1042 was paid by", byReference.UUID)$payment = $client->payments->get( $paymentUuid); // pay@…echo "{$payment->uuid}: {$payment->amountUsd} USD, tx {$payment->txHash}\n"; // Runs GET /payments?reference= and returns its one payment (a NotFoundException when none).$payment = $client->payments->getByReference( 'order-1042'); // your order idecho "order-1042 was paid by {$payment->uuid}\n";List payments newest first with GET /payments. Subscription bills are in the combined feed (GET /transactions):
transactions-list.sh
# One page of the feed: one-time payments and subscription bills together, newest first, each# with its type. ?type=payment or ?type=bill keeps one kind.curl "https://api.qbitflow.app/v2/transactions?limit=10" \ -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a page of Transaction ({ items, hasMore, nextCursor? })// One feed of one-time payments and subscription bills, newest first; each row reads like its// resource. `type` filters it to one kind (TransactionKind.Payment or TransactionKind.Bill).const feed = await client.transactions.list({ limit: 20 });for (const tx of feed.items) { console.log(tx.type, tx.uuid, tx.amountUsd, 'USD');}# One feed of one-time payments and subscription bills, newest first; each row reads like# its resource. type= filters it to one kind (TransactionKind.PAYMENT or .BILL).feed = client. transactions. list( limit=20)for tx in feed.items: print(tx.type, tx.uuid, tx.amount_usd, "USD")// One feed of one-time payments and subscription bills, newest first; each row reads like// its resource. Type filters it to one kind (TransactionKindPayment or TransactionKindBill).feed, err := client.Transactions.List(ctx, &qbitflow. TransactionListParams{Limit: 20})if err != nil { log.Fatal(err)}for _, tx := range feed.Items { fmt.Println(tx.Type, tx.UUID, tx.AmountUSD, "USD")}// One feed of one-time payments and subscription bills, newest first; each row reads like its// resource. `type` filters it to one kind (TransactionKind::PAYMENT or TransactionKind::BILL).$feed = $client->transactions->list(new \QBitFlow\Params\TransactionListParams( limit: 20));foreach ($feed->items as $transaction) { echo "{$transaction->type} {$transaction->uuid} {$transaction->amountUsd} USD\n";}Here is a payment as payment.completed delivers it:
payment.completed· the body your endpoint receives
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "amount": "10", "amountMinUnits": "10000000", "amountUsd": "10", "confirmedAt": "2026-10-01T11:59:58Z", "explorerUrl": "https://basescan.org/tx/0x1111111111111111111111111111111111111111111111111111111111111111", "metadata": { "crm:customer": "C-5521", "orderId": "order-1042" }, "priceUsd": "10", "reference": "order-1042", "settlement": { "feePercent": "0.99", "split": {…}, "transaction": {…} }, "txHash": "0x1111111111111111111111111111111111111111111111111111111111111111", "uuid": "pay@01a0f755-f200-7000-8000-000000000001", … 17 more fields }, "id": "evt_82c1e15d-c0d9-584f-a65a-fb4099925273", "test": false, "type": "payment.completed", "version": "v2"}data.amount: What the customer paid in the token, exact;amountMinUnitsis the same in its smallest unit andamountUsdits value in USD (amounts).data.confirmedAt: When the chain confirmed it;createdAtis when QBitFlow recorded it.data.explorerUrl: Its transaction on the chain's explorer: link to it rather than building one.data.metadata: Your checkout's own data, returned as you sent it.data.reference: Your order id, from the checkout.data.settlement: How the contract split it, as paid (fees).data.uuid: The checkout's id, now the payment's, for life.
A payment read adds refund and refundable. productName and productDescription copy what was paid for:
product edits never change them.