Refund workflows #

qpayd records refund requests and links them to the original invoice. The actual refund payment is sent from the wallet or Lightning node that controls the money.

For on-chain stores, qpayd normally has a watch-only descriptor. For Lightning, invoice creation should use limited credentials. Keep full spending credentials in the wallet, node, or private sweep deployment.

Read refund state for an invoice:

curl -sS https://pay.example.com/v1/stores/main/invoices/$INVOICE_ID/refund-summary \
  -H "Authorization: Bearer $QPAYD_MAIN_API_TOKEN"

Create an invoice-scoped refund record:

curl -sS https://pay.example.com/v1/stores/main/invoices/$INVOICE_ID/refunds \
  -H "Authorization: Bearer $QPAYD_MAIN_API_TOKEN" \
  -H "Idempotency-Key: refund_order_123" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_sats": 2000,
    "destination": "bc1q...",
    "reason": "overpayment"
  }'

Finalize it after the refund payment is sent:

curl -sS -X POST https://pay.example.com/v1/stores/main/refunds/$REFUND_ID/finalize \
  -H "Authorization: Bearer $QPAYD_MAIN_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "tx_id": "...", "payment_proof": "..." }'

Mark it failed if the operator or refund executor cannot complete the payment:

curl -sS -X POST https://pay.example.com/v1/stores/main/refunds/$REFUND_ID/fail \
  -H "Authorization: Bearer $QPAYD_MAIN_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "failure_reason": "expired lightning invoice" }'

Cancel a pending refund:

curl -sS -X POST https://pay.example.com/v1/stores/main/refunds/$REFUND_ID/cancel \
  -H "Authorization: Bearer $QPAYD_MAIN_API_TOKEN"

Pending and finalized refunds count against the invoice refundable balance. Canceled and failed refunds do not. Refund responses include optional destination_type, idempotency_key, payment_proof, and failure_reason fields.