After the sale

Find a payment when a customer writes in, show its amounts exactly, refund it, export your books.

Find a payment

List payments

payments-list.sh

# One page of payments, newest first, created after an instant (RFC 3339, excluded).curl "https://api.qbitflow.app/v2/payments?createdAfter=2026-10-01T00:00:00Z&limit=10" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a page of Payment ({ items, hasMore, nextCursor? })
const page = await client.payments.list({	createdAfter: new Date(Date.now() - 30 * 24 * 3600 * 1000), // the last 30 days	limit: 10,});for (const payment of page.items) {	// Amounts are decimal strings: the token's (amount, in currency) and USD (amountUsd).	const { uuid, reference, amount, currency, amountUsd } = payment;	console.log(`${uuid} "${reference ?? ''}": ${amount} ${currency.symbol} (${amountUsd} USD)`);}if (page.hasMore) {	// Pass it back, verbatim, as the cursor param for the next page.	console.log(`next cursor: ${page.nextCursor}`);}
page = client.payments.list(refunded=False, limit=20)  # newest firstfor payment in page.items:    # Amounts are decimal strings: the token's (amount, with currency) and USD (amount_usd).    print(        payment.uuid,        payment.reference,        payment.amount,        payment.currency.symbol,        f"({payment.amount_usd} USD)",    )if page.has_more:    print("next page: cursor =", page.next_cursor)  # pass it back, verbatim, as cursor=
since := time.Now().AddDate(0, -1, 0)page, err := client.Payments.List(ctx, &qbitflow.PaymentListParams{	CreatedAfter: &since, // the last month	Limit:        20,})if err != nil {	log.Fatal(err)}for _, p := range page.Items {	// Amounts are decimal strings: the token's (Amount, with Currency) and USD (AmountUSD).	fmt.Printf("%s %q: %s %s (%s USD)\n", p.UUID, p.Reference, p.Amount, p.Currency.Symbol, p.AmountUSD)}if page.HasMore {	// Pass it back, verbatim, as PaymentListParams.Cursor for the next page.	fmt.Println("next cursor:", page.NextCursor)}
$page = $client->payments->list(new \QBitFlow\Params\PaymentListParams(	createdAfter: new \DateTimeImmutable('-30 days'),	limit: 20,));foreach ($page->items as $payment) {	// Amounts are decimal strings: the token's (amount, with its currency) and USD (amountUsd).	echo "{$payment->uuid} " . ($payment->reference ?? '-') . ": {$payment->amount} {$payment->currency->symbol} ({$payment->amountUsd} USD)\n";}if ($page->hasMore) {	$cursor = $page->nextCursor; // pass it back, verbatim, as `cursor` for the next page}

Newest first, a page at a time (pagination), filtered by date (createdAfter, createdBefore), customer, product or refunded.

Get a payment

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 paid
const 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";

By pay@… id or order reference. GET /payments?reference= for one nobody paid answers 200 with an empty page ({ items: [], hasMore: false }); the SDKs' getByReference raises not found.

Show amounts exactly

Display amounts exactly
// Token amounts come both ways, as decimal strings: payment.amount ('4.99') and// payment.amountMinUnits ('4990000', with payment.currency.decimals). Convert between them with// string arithmetic, never floats.const shown = formatAmount('4990000', 6); // '4.99' (USDC has 6 decimals)const minUnits = parseAmount('4.99', 6); // '4990000'console.log(`${shown} USDC = ${minUnits} minimal units`);
# Amounts in a token's smallest unit are decimal strings: never convert them through a float.shown = qbitflow.format_amount("4990000", 6)  # "4.99" (6 decimals, like USDC)min_units = qbitflow.parse_amount("4.99", 6)  # "4990000"print(shown, min_units)
// Exact decimal arithmetic on strings, never floats. USDC has 6 decimals.display, err := qbitflow.FormatAmount("4990000", 6)if err != nil {	log.Fatal(err)}fmt.Println(display, "USDC") // 4.99 USDC minUnits, err := qbitflow.ParseAmount("4.99", 6) // more than 6 decimals is a *ValidationErrorif err != nil {	log.Fatal(err)}fmt.Println(minUnits) // 4990000 // From a payment: payment.Currency.FormatAmount(payment.AmountMinUnits)
// Amounts in a token's smallest unit are decimal strings: convert them exactly, never through floats.$display = \QBitFlow\Support\Amount::format('4990000', 6); // "4.99" (USDC has 6 decimals)$minUnits = \QBitFlow\Support\Amount::parse('4.99', 6);     // "4990000"echo "{$display} USDC = {$minUnits} min units\n";
  • amountUsd: what the customer paid, in USD (price plus checkout lines). amount: the same in the token.
  • amountMinUnits: the same in the token's smallest unit, a string; currency.decimals places the point (amounts).
  • settlement.split: the split as recorded (QBitFlow's fee, what your wallet received). The network fee, settlement.split.networkFee, comes on top of amount. Never recompute it from the rates.

Refund a payment

You send a refund from the dashboard, not from code: open Dashboard/Payments/a payment/Refund, pick the share, and sign with the wallet that was paid. Nothing is recorded until the transfer confirms. Then it's approved, initiatedBy: "merchant", and refund.approved is sent. Your code reads refunds: ?status=pending lists the customers' requests waiting for you, and one refund is read by its id.

List refunds

refunds-list.sh

# The refunds waiting for an answer.curl "https://api.qbitflow.app/v2/refunds?status=pending" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a page of Refund ({ items, hasMore, nextCursor? })
// The active ones: pending, waiting for an answer (no status: every status).const page = await client.refunds.list({ status: [RefundStatus.Pending] });for (const refund of page.items) {	const { uuid, txUuid, initiatedBy, reason, amount, currency } = refund;	console.log(`${uuid} of ${txUuid} (${initiatedBy}): ${amount} ${currency.symbol}, ${reason}`);}
# The ones waiting for an answer (status None: every status).page = client.refunds.list(status=[qbitflow.RefundStatus.PENDING])for refund in page.items:    print(refund.uuid, refund.tx_uuid, refund.initiated_by, refund.reason, refund.amount)
// The active ones: pending, waiting for an answer (Status empty: every status).page, err := client.Refunds.List(ctx, &qbitflow.RefundListParams{	Status: []qbitflow.RefundStatus{qbitflow.RefundStatusPending},})if err != nil {	log.Fatal(err)}for _, r := range page.Items {	fmt.Println(r.UUID, r.TxUUID, r.InitiatedBy, r.Reason, r.Amount, r.Currency.Symbol)}
// The active ones: pending, waiting for an answer (no status: every status).$page = $client->refunds->list(new \QBitFlow\Params\RefundListParams(	status: [\QBitFlow\Enums\RefundStatus::PENDING],));foreach ($page->items as $refund) {	echo "{$refund->uuid} {$refund->txUuid} ({$refund->initiatedBy}): {$refund->amount} {$refund->currency->symbol}, " . ($refund->reason ?? '-') . "\n";}
Read a refund

refunds-get.sh

# One refund, by its id (refund@…): its status, its share of the payment and its amount.curl "https://api.qbitflow.app/v2/refunds/$REFUND_UUID" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a Refund
const refund = await client.refunds.get(refundUuid); // refund@…// pending (a customer's request), approved or rejected.const { uuid, status, refundPercent, amount, currency } = refund;console.log(`${uuid}: ${status}, ${refundPercent}% = ${amount} ${currency.symbol}`);
refund = client.refunds.get(refund_uuid)  # refund@…# pending (a customer's request), approved or rejected.print(refund.uuid, refund.status, f"{refund.refund_percent}%", refund.amount)
refund, err := client.Refunds.Get(ctx, refundUUID) // refund@…if err != nil {	log.Fatal(err)}// pending (a customer's request), approved or rejected.fmt.Println(refund.UUID, refund.Status, refund.RefundPercent+"%", refund.Amount, refund.Currency.Symbol)
$refund = $client->refunds->get($refundUuid); // refund@…// pending (a customer's request), approved or rejected.echo "{$refund->uuid} {$refund->status} {$refund->refundPercent}% {$refund->amount} {$refund->currency->symbol}\n";
  1. Customer wallet → QBitFlow API: Asks for a refund (a refund you start skips this)
  2. QBitFlow API → Your server: refund.requested
  3. Your wallet → QBitFlow API: Approves a share, and signs it
  4. QBitFlow API → Your server: refund.rejected, if you rejected it in the dashboard (no signature)
  5. Your wallet → Customer wallet, on-chain: Sends the share back, on-chain
  6. QBitFlow API → Your server: refund.approved
Blue mark: money, on-chain. The rest moves no funds.
  • The paid wallet needs the share when you prepare the transfer, or it answers 422 insufficient_funds (a token) or 422 insufficient_native_funds (the chain's own coin). Top it up, then send.
  • Refunds cost you the fees. It pays back its share of everything paid, network fee included; QBitFlow keeps its fee (who bears a refund).
  • One refund per payment: a second answers 409 refund_already_exists, with the refund in details.refundUuid. If it's a customer's request, you approve that one instead.

Customers can ask too, from their payment's page (by default for 30 days after paying). You approve or reject them in the dashboard. They arrive as:

refund.requested· the body your endpoint receives

{  "createdAt": "2026-10-01T12:00:00Z",  "data": {    "initiatedBy": "customer",    "reason": "I was charged twice for the same order",    "refundPercent": "100",    "status": "pending",    "txUuid": "pay@01a0f755-f200-7000-8000-000000000001",    "uuid": "refund@01a0ec3b-3ba0-7000-8000-000000000006",    … 11 more fields  },  "id": "evt_ef774d14-7666-58e4-b447-10383579da8e",  "test": false,  "type": "refund.requested",  "version": "v2"}
The contract's example (webhooks.md § Examples)
  1. data.initiatedBy: customer: yours to approve or reject.
  2. data.refundPercent: A customer asks for 100; you set the share when you approve.
  3. data.txUuid: The payment to refund.

Check it

Refund half your test payment from Dashboard/Payments/a payment/Refund, signing with your test wallet. Once it confirms, it's approved in the list and refund.approved shows in Dashboard/Settings/Events.

Export for accounting

Export accounting data

accounting-export.sh

# Every payment, bill, refund and fee between two dates. One request covers a limited window (a# longer one answers 400); format=csv answers CSV.curl "https://api.qbitflow.app/v2/accounting/export?from=2026-01-01&to=2026-03-31&format=json" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: { items: AccountingEvent[], hasMore: false } (format=csv: CSV text)
// Every payment, bill, refund and fee between two dates (both included). Any range: the SDK// requests it in windows of at most 92 days, both included (here 4), and concatenates them.const rows = await client.accounting.exportJsonRange('2026-01-01', '2026-12-31');for (const row of rows.slice(0, 5)) {	console.log(row.type, row.txUuid, row.txTimeUtc, row.grossAmountUsd, row.productName);} // The same as CSV text, its header line once.const csv = await client.accounting.exportCsvRange('2026-01-01', '2026-12-31');await writeFile('qbitflow-2026.csv', csv, { mode: 0o600 });
# The API exports at most 92 days at a time (both included): the *_range methods split any# range into windows, request them in order and join the results (the CSV header once).events = client.accounting.export_json_range("2026-01-01", "2026-12-31")for event in events:    print(event.type, event.tx_uuid, event.tx_time_utc.date(), event.gross_amount_usd) csv_text = client.accounting.export_csv_range("2026-01-01", "2026-12-31")with open("qbitflow-2026.csv", "w", encoding="utf-8") as file:    file.write(csv_text)
// The API exports at most 92 days at a time (both included): the …Range methods split any// range into windows, request them in order and join the results (the CSV header once).events, err := client.Accounting.ExportJSONRange(ctx, "2026-01-01", "2026-12-31")if err != nil {	log.Fatal(err)}for _, e := range events {	fmt.Println(e.Type, e.TxUUID, e.TxTimeUTC.Format(time.DateOnly), e.GrossAmountUSD, e.ProductName)} csv, err := client.Accounting.ExportCSVRange(ctx, "2026-01-01", "2026-12-31")if err != nil {	log.Fatal(err)}if err := os.WriteFile("qbitflow-2026.csv", []byte(csv), 0o600); err != nil {	log.Fatal(err)}
// The API exports at most 92 days at a time (both included): the range helpers split any range// into windows, request them in order and join the results (the CSV header once).$events = $client->accounting->exportJsonRange('2026-01-01', '2026-12-31');foreach ($events as $event) {	echo "{$event->type} {$event->txUuid} {$event->txTimeUtc->format('Y-m-d')} {$event->grossAmountUsd} USD {$event->productName}\n";} $csv = $client->accounting->exportCsvRange('2026-01-01', '2026-12-31'); // one header line, then the rows

Payments, refunds and fees between two dates, JSON or CSV, 92 days per request at most (the SDKs split longer ranges). Amounts are in the coin paid and in USD, and grossAmount is exactly platformFee + organizationFee + netAmount. Network fees have their own columns, with who paid them: never in the net.