API reference

Refunds

  • 2 endpoints

A refund is either requested by the customer (the hosted refund page, signed by the wallet that paid) and approved or rejected by the merchant, or sent by the merchant without a request; either way the merchant signs the transfer from the wallet that was paid, in the dashboard (non-custodial: QBitFlow never holds the money). There is no in-between state on the merchant's side: its own refund is recorded only once its transfer confirmed (approved); before that, nothing is stored and nothing blocks the customer. The SDK surface is GET /refunds and GET /refunds/:uuid (read only).

List refunds#

GET /refunds

A page of the refunds of the request's space and, from the organization's space, of its members by default (includeMembers; userUuid: one member's), newest first, with any status unless filtered (?status=pending for those to answer; repeat it for several). Each names its member (userUuid), whether the organization decides it (held), and, once sent, where its transfer stands (approval). A member needs the members.refunds policy (403 policy_disabled).

Auth
X-API-Key: any keyA member's key needs the members.refunds policy
On-Behalf-Of
Honored: acts in the member's space
Idempotency-Key
Ignored
Success
200
Request

request.sh

curl https://api.qbitflow.app/v2/refunds \  -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";}

Query parameters

  • limitintegerOptional

    Page size: 20 when absent or ≤ 0, at most 100 (a larger one is clamped, not refused)

  • cursorstringOptional

    The previous page's nextCursor, verbatim; omit for the first page

    max=200

  • includeMembersbooleanOptional

    The organization's space only (the team, its keys without On-Behalf-Of): its members' refunds too, each naming its member (userUuid), the ones it decides marked held. Default: true from the organization's space, false from a member's (true there → 400)

  • userUuidstringOptional

    The organization's space only: this member's refunds only (their personal space in the request's mode; another organization's user → 404). includeMembers doesn't apply

  • heldbooleanOptional

    Only the refunds of payments held for a member (true: the ones the organization decides and sends), or only the others (false)

  • statusarray of stringOptional

    Only the refunds in one of these statuses: repeat the parameter for several (?status=approved&status=rejected). Absent: every status

    max=5oneof=pending approved rejected failed

    One ofpendingapprovedrejected

    • failedDeclared, never set: an approval that fails leaves the refund pending, to approve again
  • txUuidstringOptional

    Only the refund of this transaction: a payment (pay@…) or a bill (sub-hist@…), or its bare uuid (either). A transaction that can't be refunded (a subscription, a refund) matches none

    txuuid

Response

A page: { "items": Refund[], "hasMore": …, "nextCursor"?: … }. Pass nextCursor back as cursor for the next page; it is absent when hasMore is false.

27 fields of each Refund
  • uuidstring

    The refund's id (refund@<uuid>)

    A prefixed id (pay@…, sub@…)

  • txUuidstring

    The refunded transaction: a payment (pay@<uuid>) or a subscription's bill (sub-hist@<uuid>)

  • reasonstringOptional

    Why the refund: the customer's reason, or the merchant's when it started the refund (absent when it gave none). Only for the merchant: never in the hosted reads (GET /hosted/transactions/:txUuid/refund, the answer of POST /hosted/refunds)

    Only for the resource's owner

  • statusstring

    pending (a customer's request not answered yet), approved (its transfer confirmed), rejected (the merchant rejected the request). failed is declared but never set: a failed approval leaves the request pending

    One ofpendingapprovedrejected

    • failedDeclared, never set: an approval that fails leaves the refund pending, to approve again
  • createdAtstring

    When the refund was requested (or prepared by the merchant: its intent)

  • merchantMessagestringOptional

    The merchant's message to the customer: why it rejected the request, or a note on a refund it started. Absent when none

  • respondedAtstringOptional

    When the refund was answered: rejected, or its transfer confirmed (approved). Absent while pending

  • testboolean

    Whether this is test data (testnets) or live data

  • userUuidstringOptional

    Member the resource belongs to; omitted for the organization's own resources

    Only for the resource's owner

  • txHashstringOptional

    The refund transfer's transaction, once approved. Absent before

  • initiatedBystring

    Who started the refund: customer (requested by the customer, the merchant answers) or merchant (sent by the merchant without a request: recorded once its transfer confirmed, approved; see POST /refunds/intent)

    One ofcustomermerchant

  • heldbooleanOptional

    Held: the refunded payment was paid to the organization's wallet, which holds the seller's funds (the trust layer): the organization decides the refund and sends it from its wallet; the seller (userUuid) sees it. Once sent, it is a negative line of the seller's held funds. False: the seller (the refund's space) decides it.

    Only for the resource's owner

  • paidstring

    What the customer paid for the refunded transaction: its amount, fees included, and the network fee they paid on top. The base of refundPercent: a full refund sends all of it back. paid and paidMinUnits in the units of currency, paidUsd at the refunded transaction's time (absent when unknown: a request made before it was recorded) In the currency's units, exact (paidMinUnits / 10^decimals), a decimal string

  • paidMinUnitsstring

    In the currency's smallest unit, a string of digits

  • paidUsdstringOptional

    In USD, a decimal string; absent when unknown

  • refundPercentnumber

    The share of what the customer paid that the refund sends back, a percent string (100: all of it). A customer requests 100; the merchant sets it when it approves, or when it starts the refund. Written as refundPercent

  • amountstring

    What the refund sends back: refundPercent of what the customer paid, rounded down to a whole min unit (all of it for a customer's request, the merchant's share for its own refund, and the share sent once approved). amount in the currency's units, amountUsd at the refunded transaction's time (absent when unknown: a request made before it was recorded) In the currency's units, exact (amountMinUnits / 10^decimals), a decimal string

  • amountMinUnitsstring

    In the currency's smallest unit, a string of digits

  • amountUsdstringOptional

    In USD, a decimal string; absent when unknown

  • currencyIdinteger

    The currency paid, and sent back: written as the currency object under currency (GET /currencies/:id)

  • settlementTransferSettlementOptional

    The refund transfer's transaction (its block, what it cost), once approved; absent before

    Only for the resource's owner

    1 field of TransferSettlement
    • transactionSettlementTransactionOptional

      The transfer's transaction. Absent before it was confirmed

      6 fields of SettlementTransaction
      • blockNumberstring

        The block (Ethereum, Base) or slot (Solana) it landed in, a string of digits

      • blockTimestringOptional

        When its block was produced (UTC); when it was recorded if the chain's node didn't give it. Absent only on records made before it was recorded

      • gasUsedintegerOptional

        The gas it consumed (Ethereum, Base). Absent on Solana

      • feeTokenAmount

        What the transaction cost its sender, in the chain's native coin (ETH, SOL; its currency is the payment currency's chain): its gas and, on Base, its L1 data fee. amountUsd at nativePriceUsd

        3 fields of TokenAmount
        • amountstring

          In the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")

        • amountMinUnitsstring

          The on-chain integer, in the token's smallest unit, a string of digits ("12500000")

        • amountUsdstringOptional

          Its value in USD, a decimal string; absent when no price was known

      • l1FeeTokenAmountOptional

        The part of fee that paid Base's L1 data fee, in the native coin. Absent off Base, and on transactions recorded before it was

        3 fields of TokenAmount
        • amountstring

          In the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")

        • amountMinUnitsstring

          The on-chain integer, in the token's smallest unit, a string of digits ("12500000")

        • amountUsdstringOptional

          Its value in USD, a decimal string; absent when no price was known

      • nativePriceUsdstringOptional

        NativePriceUSD is the USD price of the chain's native coin when the transaction was recorded (what its fee cost in USD). Absent when the price wasn't available then

  • chainstringOptional

    The chain it was paid on: ETH (Ethereum), BASE (Base) or SOL (Solana), the testnet's for a test-mode transfer (currency.test)

  • explorerUrlstringOptional

    Its transaction on the chain's block explorer (the testnet's in test mode). Omitted without a txHash

  • approvalApprovalOptional

    Approval is where the refund transfer stands while the request is pending and it was sent (POST /refunds/:uuid/approve, or the merchant's own refund answering it: POST /refunds): being confirmed, or its last attempt failed. Absent before it is sent, and once the refund is answered (respondedAt). Only for the merchant, in its reads (GET /refunds, GET /refunds/:uuid, GET /hosted/transactions/:txUuid/refund); never in webhooks

    Only for the resource's owner

    3 fields of Approval
    • statusstring

      waitingConfirmation: sent, being confirmed on-chain. created: its last attempt failed (lastAttempt): send it again (POST /refunds/:uuid/approve/intent)

      One of:

      • createdWaiting for the customer: nothing sent yet, or the last attempt failed (lastAttempt)
      • waitingConfirmationA transaction was sent: waiting for the network and its record
      • completedConfirmed and recorded. Final
      • expiredThe checkout expired unpaid (at its expiresAt, or expired by its merchant). Final
    • txHashstringOptional

      The transfer being confirmed, once known

    • lastAttemptAttemptOptional

      With created: why the last attempt failed (its code and message)

      5 fields of Attempt
      • statusstring

        failed

        One of:

        • failedThe attempt's transaction wasn't sent, didn't confirm or failed on the network: nothing was paid
      • codestringOptional

        The error code (errors): what to do about it (insufficient_funds, token_not_approved, tx_not_sent…)

      • messagestringOptional

        The reason, for the customer

      • txHashstringOptional

        The attempt's transaction, if it was sent

      • atstring

        When it failed

  • customerUuidstringOptional

    CustomerUUID is the refunded transaction's customer (a refund has none of its own); absent without one. Only for the merchant, in its reads (never in webhooks)

    Only for the resource's owner

  • productNamestringOptional

    What the refunded transaction paid for (its product's name, as on the payment or bill). Only for the merchant, in its reads (never in webhooks)

    Only for the resource's owner

  • customerSummaryOptional

    The refunded transaction's customer, named (even once deleted); absent without one. Only for the merchant, in its reads (never in webhooks)

    Only for the resource's owner

    6 fields of Summary
    • uuidstring

      The customer (GET /customers/:uuid)

    • firstNamestring

      First name

    • lastNamestringOptional

      Last name, when given

    • emailstring

      Email, lowercase

    • referencestringOptional

      The merchant's reference of the customer, when it has one

    • deletedboolean

      The merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404

An item of the response

{  "amount": "10.0042",  "amountMinUnits": "10004200",  "amountUsd": "10.0042",  "chain": "BASE",  "createdAt": "2026-10-01T12:00:00Z",  "currency": {    "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",    "chain": "BASE",    "code": "BASE:USDC",    "decimals": 6,    "id": 8,    "mainCurrencyId": 3,    "name": "USD Coin",    "symbol": "USDC",    "test": false  },  "held": false,  "initiatedBy": "customer",  "paid": "10.0042",  "paidMinUnits": "10004200",  "paidUsd": "10.0042",  "reason": "I was charged twice for the same order",  "refundPercent": "100",  "status": "pending",  "test": false,  "txUuid": "pay@01a0f755-f200-7000-8000-000000000001",  "uuid": "refund@01a0ec3b-3ba0-7000-8000-000000000006"}
From the data of the refund.requested event's example: the same type.
Show all 29 lines

Errors

  • 403policy_disabled

    a member doing what their organization's policy doesn't allow them, reads of what the policy governs included

Get refund#

GET /refunds/:uuid

A refund of the request's space or, from the organization's space, of one of its members (as GET /refunds lists them). Another organization's, another mode's or another member's → 404. A member needs the members.refunds policy (403 policy_disabled).

Auth
X-API-Key: any keyA member's key needs the members.refunds policy
On-Behalf-Of
Honored: acts in the member's space
Idempotency-Key
Ignored
Success
200
Request

request.sh

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

Path parameters

  • uuidstringRequired

    The refund: refund@<uuid>, or its bare uuid

    txuuid

Response

A Refund.

27 fields of Refund
  • uuidstring

    The refund's id (refund@<uuid>)

    A prefixed id (pay@…, sub@…)

  • txUuidstring

    The refunded transaction: a payment (pay@<uuid>) or a subscription's bill (sub-hist@<uuid>)

  • reasonstringOptional

    Why the refund: the customer's reason, or the merchant's when it started the refund (absent when it gave none). Only for the merchant: never in the hosted reads (GET /hosted/transactions/:txUuid/refund, the answer of POST /hosted/refunds)

    Only for the resource's owner

  • statusstring

    pending (a customer's request not answered yet), approved (its transfer confirmed), rejected (the merchant rejected the request). failed is declared but never set: a failed approval leaves the request pending

    One ofpendingapprovedrejected

    • failedDeclared, never set: an approval that fails leaves the refund pending, to approve again
  • createdAtstring

    When the refund was requested (or prepared by the merchant: its intent)

  • merchantMessagestringOptional

    The merchant's message to the customer: why it rejected the request, or a note on a refund it started. Absent when none

  • respondedAtstringOptional

    When the refund was answered: rejected, or its transfer confirmed (approved). Absent while pending

  • testboolean

    Whether this is test data (testnets) or live data

  • userUuidstringOptional

    Member the resource belongs to; omitted for the organization's own resources

    Only for the resource's owner

  • txHashstringOptional

    The refund transfer's transaction, once approved. Absent before

  • initiatedBystring

    Who started the refund: customer (requested by the customer, the merchant answers) or merchant (sent by the merchant without a request: recorded once its transfer confirmed, approved; see POST /refunds/intent)

    One ofcustomermerchant

  • heldbooleanOptional

    Held: the refunded payment was paid to the organization's wallet, which holds the seller's funds (the trust layer): the organization decides the refund and sends it from its wallet; the seller (userUuid) sees it. Once sent, it is a negative line of the seller's held funds. False: the seller (the refund's space) decides it.

    Only for the resource's owner

  • paidstring

    What the customer paid for the refunded transaction: its amount, fees included, and the network fee they paid on top. The base of refundPercent: a full refund sends all of it back. paid and paidMinUnits in the units of currency, paidUsd at the refunded transaction's time (absent when unknown: a request made before it was recorded) In the currency's units, exact (paidMinUnits / 10^decimals), a decimal string

  • paidMinUnitsstring

    In the currency's smallest unit, a string of digits

  • paidUsdstringOptional

    In USD, a decimal string; absent when unknown

  • refundPercentnumber

    The share of what the customer paid that the refund sends back, a percent string (100: all of it). A customer requests 100; the merchant sets it when it approves, or when it starts the refund. Written as refundPercent

  • amountstring

    What the refund sends back: refundPercent of what the customer paid, rounded down to a whole min unit (all of it for a customer's request, the merchant's share for its own refund, and the share sent once approved). amount in the currency's units, amountUsd at the refunded transaction's time (absent when unknown: a request made before it was recorded) In the currency's units, exact (amountMinUnits / 10^decimals), a decimal string

  • amountMinUnitsstring

    In the currency's smallest unit, a string of digits

  • amountUsdstringOptional

    In USD, a decimal string; absent when unknown

  • currencyIdinteger

    The currency paid, and sent back: written as the currency object under currency (GET /currencies/:id)

  • settlementTransferSettlementOptional

    The refund transfer's transaction (its block, what it cost), once approved; absent before

    Only for the resource's owner

    1 field of TransferSettlement
    • transactionSettlementTransactionOptional

      The transfer's transaction. Absent before it was confirmed

      6 fields of SettlementTransaction
      • blockNumberstring

        The block (Ethereum, Base) or slot (Solana) it landed in, a string of digits

      • blockTimestringOptional

        When its block was produced (UTC); when it was recorded if the chain's node didn't give it. Absent only on records made before it was recorded

      • gasUsedintegerOptional

        The gas it consumed (Ethereum, Base). Absent on Solana

      • feeTokenAmount

        What the transaction cost its sender, in the chain's native coin (ETH, SOL; its currency is the payment currency's chain): its gas and, on Base, its L1 data fee. amountUsd at nativePriceUsd

        3 fields of TokenAmount
        • amountstring

          In the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")

        • amountMinUnitsstring

          The on-chain integer, in the token's smallest unit, a string of digits ("12500000")

        • amountUsdstringOptional

          Its value in USD, a decimal string; absent when no price was known

      • l1FeeTokenAmountOptional

        The part of fee that paid Base's L1 data fee, in the native coin. Absent off Base, and on transactions recorded before it was

        3 fields of TokenAmount
        • amountstring

          In the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")

        • amountMinUnitsstring

          The on-chain integer, in the token's smallest unit, a string of digits ("12500000")

        • amountUsdstringOptional

          Its value in USD, a decimal string; absent when no price was known

      • nativePriceUsdstringOptional

        NativePriceUSD is the USD price of the chain's native coin when the transaction was recorded (what its fee cost in USD). Absent when the price wasn't available then

  • chainstringOptional

    The chain it was paid on: ETH (Ethereum), BASE (Base) or SOL (Solana), the testnet's for a test-mode transfer (currency.test)

  • explorerUrlstringOptional

    Its transaction on the chain's block explorer (the testnet's in test mode). Omitted without a txHash

  • approvalApprovalOptional

    Approval is where the refund transfer stands while the request is pending and it was sent (POST /refunds/:uuid/approve, or the merchant's own refund answering it: POST /refunds): being confirmed, or its last attempt failed. Absent before it is sent, and once the refund is answered (respondedAt). Only for the merchant, in its reads (GET /refunds, GET /refunds/:uuid, GET /hosted/transactions/:txUuid/refund); never in webhooks

    Only for the resource's owner

    3 fields of Approval
    • statusstring

      waitingConfirmation: sent, being confirmed on-chain. created: its last attempt failed (lastAttempt): send it again (POST /refunds/:uuid/approve/intent)

      One of:

      • createdWaiting for the customer: nothing sent yet, or the last attempt failed (lastAttempt)
      • waitingConfirmationA transaction was sent: waiting for the network and its record
      • completedConfirmed and recorded. Final
      • expiredThe checkout expired unpaid (at its expiresAt, or expired by its merchant). Final
    • txHashstringOptional

      The transfer being confirmed, once known

    • lastAttemptAttemptOptional

      With created: why the last attempt failed (its code and message)

      5 fields of Attempt
      • statusstring

        failed

        One of:

        • failedThe attempt's transaction wasn't sent, didn't confirm or failed on the network: nothing was paid
      • codestringOptional

        The error code (errors): what to do about it (insufficient_funds, token_not_approved, tx_not_sent…)

      • messagestringOptional

        The reason, for the customer

      • txHashstringOptional

        The attempt's transaction, if it was sent

      • atstring

        When it failed

  • customerUuidstringOptional

    CustomerUUID is the refunded transaction's customer (a refund has none of its own); absent without one. Only for the merchant, in its reads (never in webhooks)

    Only for the resource's owner

  • productNamestringOptional

    What the refunded transaction paid for (its product's name, as on the payment or bill). Only for the merchant, in its reads (never in webhooks)

    Only for the resource's owner

  • customerSummaryOptional

    The refunded transaction's customer, named (even once deleted); absent without one. Only for the merchant, in its reads (never in webhooks)

    Only for the resource's owner

    6 fields of Summary
    • uuidstring

      The customer (GET /customers/:uuid)

    • firstNamestring

      First name

    • lastNamestringOptional

      Last name, when given

    • emailstring

      Email, lowercase

    • referencestringOptional

      The merchant's reference of the customer, when it has one

    • deletedboolean

      The merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404

Errors

  • 403policy_disabled

    a member doing what their organization's policy doesn't allow them, reads of what the policy governs included

Edge cases

The contract's rules for these routes: Authentication & roles · The refund (Refund) · Reading refunds (GET /refunds, GET /refunds/:uuid) · What a refund pays back · Where a refund's transfer stands (approval, chain, explorerUrl) · Conflicts (409) · Webhooks.

Read all 7

Authentication & roles

  • A member's refunds and decisions follow the organization's members.refunds policy (off → 403 policy_disabled, reads included: GET /refunds and GET /refunds/:uuid, with the member's session or their own key); the team always passes, acting in the member's space. A refund the member already holds stays the organization's to manage.
  • GET /refunds lists; a GET never creates.

The refund (Refund)

  • uuid is refund@<uuid>; every :uuid path takes it or the bare uuid. The merchant's own refund's uuid is its transaction's (refund@<the payment's or bill's uuid>: one per transaction). Another kind of id (pay@…) isn't a refund → 404; a value that isn't an id → 400 validation_failed on uuid.
  • txUuid is the refunded transaction, always in lowercase: a one-time payment (pay@…) or one bill of a subscription (sub-hist@…, a Bill's uuid).
  • Statuses: pending (a customer's request not answered yet, or its approval being confirmed), approved (its transfer is confirmed on-chain: refund.approved), rejected (the merchant rejected the customer's request: refund.rejected). A merchant's own refund is only ever approved (initiatedBy: "merchant"): it is recorded once its transfer confirmed. failed is declared but never set: an approval whose transaction fails leaves the request pending, and the merchant can approve it again. Clients with exhaustive switches handle all four.
  • Money (naming): amount / amountMinUnits / amountUsd is what the refund sends back, paid / paidMinUnits / paidUsd what the customer paid (its base), in the refund's currency (the catalog's object: Currencies): amount and paid in the token's units (exact decimal strings), …MinUnits the on-chain integers (strings of digits), …Usd decimal strings at the refunded transaction's time, absent when unknown (a request made before they were recorded), never "0". refundPercent is a percent string ("50").
  • Absent until they have a value (never null): reason (the merchant may give none), merchantMessage, respondedAt (set when the refund is answered: rejected, or its transfer confirmed), txHash and explorerUrl (the refund transfer, once approved), settlement (its transaction only: block, time, what it cost), userUuid (the organization's own refund), approval (below), customer / customerUuid / productName (the refunded transaction's, merchant reads only).
  • chain (ETH, BASE, SOL) is on every refund that names its currency.

Reading refunds (GET /refunds, GET /refunds/:uuid)

  • GET /refunds is one paginated list (pagination: limit 20 by default, at most 100, opaque cursor), newest first on (createdAt, uuid), every status unless filtered:
    • status (repeatable: ?status=approved&status=rejected; any of pending approved rejected failed; an unknown value → 400 validation_failed on status[0], the index of the bad value). ?status=pending is what is waiting for the merchant (the former GET /transaction/refunds/all).
    • txUuid (a prefixed id, any case, or the bare uuid of a payment or bill): the refund of that transaction (0 or 1 item). A subscription (sub@…) or a refund id matches none; a malformed value → 400 on txUuid.
    • held (true: the refunds the organization decides, of payments it held for its members; false: the others).
    • includeMembers (the organization's space only): its members' refunds too, each naming its member (userUuid). Default true from the organization's space (a marketplace decides its held sellers' refunds), false from a member's; true from a member's space → 400 on includeMembers.
    • userUuid (the organization's space only, owners and admins): one member's refunds (a removed member's too); from a member's space → 400 on userUuid; another organization's user → 404. The cursor keeps no filter: pass the same query with it. A cursor the API didn't give → 400 on cursor.
  • GET /refunds/:uuid reads one refund the request may list: its space's, or, from the organization's space, one of its members' in the same mode. Another organization's, another mode's, the organization's own from a member's space, or another member's → 404 (existence is never revealed).
  • Both are completed like every read: approval, customer, customerUuid, productName.

What a refund pays back

  • The base is everything the customer paid for the transaction: its amount, QBitFlow's fee and the organization's fee (a marketplace's commission) included, plus the network fee the customer paid on top (settlement.split.networkFee of the payment or bill). It is paidMinUnits / paid / paidUsd, read from what the transaction's confirmation recorded, never from the chain.
  • A refund sends back refundPercent of that base (greater than 0, at most 100), rounded down to a whole smallest unit (and its USD value rounded down to 6 decimals): never more than the customer paid, and never nothing (a share that rounds to 0 → 400 on refundPercent). At 100 %, the customer gets back exactly what their wallet sent.
  • Who bears the fees: the merchant. QBitFlow keeps its fee and the organization its commission (fee records are not reversed); the refund comes out of the wallet that was paid (the member's, or the organization's when it held the member's funds), which also pays the network fee of the refund's own transfer. A refund of a payment the organization holds for a member is a negative line of the member's held funds (Held funds).
  • A customer's request is for everything (refundPercent: "100"); the merchant chooses the share when approving. A merchant's own refund sends the share its intent prepared. A payment whose split wasn't recorded can't be refunded (500; the team repairs the record), never for 0.

Where a refund's transfer stands (approval, chain, explorerUrl)

  • Between POST /refunds/:uuid/approve (or a merchant's own refund answering the request) and its confirmation, the request stays pending without respondedAt, like a request nobody answered. approval tells them apart (merchant only, API reads, never in webhooks):
    • absent: nothing sent yet (only prepared, or not even that): it is the merchant's to sign;
    • { "status": "waitingConfirmation", "txHash"? }: sent, being confirmed: don't send it again;
    • { "status": "created", "lastAttempt": { "code", "message", "txHash"?, "at" } }: the last attempt failed (code, errors): prepare and send it again. Once answered, respondedAt is set and approval is absent.
  • chain is on every refund; explorerUrl (the refund transfer on the chain's explorer, the testnet's in test mode) once it has a txHash.

Conflicts (409)

  • One refund per transaction, whatever it became (pending, approved or rejected), whoever started it: a second one → 409 refund_already_exists with details.refundUuid (the existing refund, or the merchant's refund being sent). A merchant's intent never sent is no refund. The transaction is matched whatever the case of its uuid.
  • A held payment already released to its member (or closed by a release that sent nothing: Held funds) can't be refunded: its money left the organization's wallet → 409 held_funds_released, at the customer's request, at the merchant's intent, at the approval's intent and at the sends.
  • While a release of the member's held funds is being sent (however long its confirmation takes), the refund of one of its payments can't be prepared nor sent → 409 held_funds_release_in_flight (once the release is confirmed, the payment is released: held_funds_released; once it failed, the refund can be approved).

Webhooks

refund.requested (a customer's request), refund.approved (the transfer confirmed: a request approved, or a merchant's refund sent) and refund.rejected (a request rejected), each with the Refund as its owner reads it, without approval, customer, customerUuid and productName (Webhook events). A merchant's refund is announced once, by refund.approved: there is no refund.requested for it.

The codes named here

  • 400validation_failed

    path/query/body failed binding or validation, or a business rule about one input (a field named in errors)

  • 403policy_disabled

    a member doing what their organization's policy doesn't allow them, reads of what the policy governs included