Refunds and credit notes
Being rolled out. The interface described here is the one of the next release: it may still change, and may not be enabled on your tenant yet.
What it is for
Refund a customer, fully or partially. The refund goes through the provider that collected the payment (Stripe or PayPal), from your account, and the platform issues the credit note that corrects the original invoice.
Prerequisites
- A token with the
refundsscope. - The payment identifier (
pay_…): in thepayment.succeededwebhook, in the paid cart (payment_id), or withGET /v1/payments?customer_id=….
1. Refund
curl -s -X POST https://billing-api.dev.stackngo.fr/v1/refunds \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: refund-order-10042-1" \
-d '{ "payment_id": "pay_01J9ZQ8T…", "amount": "18.00", "reason": "Support de cours non livré" }'
| Field | Role |
|---|---|
payment_id | the payment to refund |
amount | amount to refund, tax included; without amount, full refund of what remains refundable |
reason | reason (500 characters at most), shown on the credit note |
Idempotency-Key is required: a refund sent again with the same key is not executed twice.
What you get
{
"id": "ref_01J9ZT4F5G6H7J8K9L0M",
"payment_id": "pay_01J9ZQ8T…",
"status": "pending",
"amount": "18.00",
"currency": "EUR",
"reason": "Support de cours non livré",
"created_at": "2026-10-10T09:00:00Z"
}
When the provider confirms, you receive refund.succeeded; the refund then carries
credit_note_id, the credit note issued. The payment becomes partially_refunded or
refunded, and amount_refunded adds up the refunds.
The credit note reads like an invoice:
curl -s "https://billing-api.dev.stackngo.fr/v1/invoices?customer_id=cus_01J9ZR1A2B3C4D5E6F7G&kind=credit_note" \
-H "Authorization: Bearer $TOKEN"
It has kind: "credit_note", its own number and corrects_invoice_id, the invoice it
corrects. Its Factur-X PDF is downloaded like an invoice.
What you can expect
- One credit note per successful refund, numbered in its own series, without gaps.
- The original invoice is never changed: the credit note corrects it.
- Several partial refunds are possible, up to the payment amount.
- The time for the money to reach your customer's account is that of Stripe or PayPal.
- Refunds appear in the
refundedcolumn of the payments report.
If it does not work
| What you see | Cause | Fix |
|---|---|---|
422 on amount | amount above what remains refundable, or not decimal | check amount and amount_refunded of the payment |
409 | payment already fully refunded, or same Idempotency-Key with a different body | read the payment again; one key per refund |
refund.failed | the provider declined (insufficient balance on your account, payment too old…) | see the details in your Stripe or PayPal dashboard, then retry with a new key |
403 | refunds scope missing | add it to the application in the BillingAccount |
See also Payments troubleshooting.