Refunds
You refund with one call, POST /v1/refunds. You never choose between a void (cancellation) and a refund: the platform picks the cheapest method the card network and the acquiring bank allow, and tells you which one it used.
{
"payment_intent": "pi_01J9ZQ4V8K2M3N5P7R9T1W3Y5A",
"amount": 2500,
"reason": "requested_by_customer",
"metadata": {"ticket": "SUP-4411"}
}
curl -X POST https://pay.example.com/v1/refunds \
-H "Authorization: Bearer sk_sandbox_YOUR_KEY" \
-H "Idempotency-Key: refund-order-1001-1" \
-H "Content-Type: application/json" \
-d '{"payment_intent": "pi_01J9ZQ4V8K2M3N5P7R9T1W3Y5A", "amount": 2500}'
payment_intentis required.amountis in minor units of the base amount of the sale (the price you charged, without any customer-paid installment surcharge); omit it to refund everything that is still refundable.reasonis optional:duplicate,fraudulentorrequested_by_customer.- The refundable amount of a payment is
amount_refundableon the payment: captured, minus refunds already made, minus refunds still in flight. Asking for more is400 amount_exceeds_refundable. - You can refund a payment several times, in parts, until nothing is left.
{
"id": "re_01J9ZQ7B2C4D6F8H1K3M5P7R9T",
"object": "refund",
"amount": 2500,
"amount_returned_to_card": 2500,
"currency": "try",
"payment_intent": "pi_01J9ZQ4V8K2M3N5P7R9T1W3Y5A",
"status": "succeeded",
"method": "partial_void",
"scheduled_for": null,
"reason": "requested_by_customer",
"failure_reason": null,
"metadata": {"ticket": "SUP-4411"},
"created": 1790000600
}
How the platform executes it
Turkish acquirers differ: most only allow a cancellation of the whole amount before their end-of-day cut-off, many refuse a refund until that day's batch is closed, and some can cancel part of a sale on the same day. The platform knows each acquirer's capabilities and chooses. The method field of the refund shows the outcome:
method |
When | What it means for you |
|---|---|---|
void |
The whole captured amount, no earlier refund, same business day before the cut-off, the money not yet released to your balance. | The sale is cancelled. The customer usually sees no charge at all. Nothing is deducted from your balance and no balance check is made. The payment becomes canceled. |
partial_void |
Part of the sale, same business day before the cut-off, on an acquirer that supports partial cancellation. | The sale is reduced. Your balance is not charged. |
refund |
Everything else: later days, or an acquirer without partial cancellation. | A normal refund at the bank. It is checked against your available balance, and the amount is held while it is processed. |
external |
A refund that was made outside the API and recorded by the platform. | Informational. |
Once any partial refund or cancellation exists on a payment, a later full void is no longer possible; the remainder is refunded as a partial_void or refund.
Deferred refunds
Some banks reject a refund for a sale from the same day until their end-of-day batch has closed. In that case the platform accepts your request immediately, holds the amount in your balance and sends it to the bank when it is allowed. The refund then has status: processing and scheduled_for set to the unix time at or after which it is sent. When it goes out the refund moves on (refund.updated, then refund.succeeded), and scheduled_for becomes null. You do nothing; just do not issue the same refund again.
Funds and approval
A refund needs available balance: if it is too low the call fails with 402 insufficient_available_balance (balance_error). Voids and partial voids need no funds. If your account requires refund approval, the refund starts as pending_approval and the method is decided again when it is approved.
Fees
A succeeded refund carries fees: pf_fee_refunded and fee_tax_refunded (the commission given back, only when your account's refund fee policy is refund), installment_surcharge_refunded (returned to the card with the refund), fee_retained_on_void (commission a void kept, when the platform keeps it) and merchant_debit (what your balance loses: amount − commission given back). The payment's fees.refunded sums them and fees.net_after_refunds is what you keep. In GET /v1/balance_transactions a returned commission is a refund_fee_return with a negative fee.
Statuses and events
status is one of pending_approval, processing, succeeded, failed (with failure_reason) or canceled. Subscribe to refund.created, refund.updated, refund.succeeded and refund.failed (see the webhooks guide). List refunds with GET /v1/refunds and filter by payment_intent, status and method.
Checklist
- Use an
Idempotency-Keyper refund, derived from your own refund id. - Reconcile on
refund.succeededandrefund.failed, not on the immediate response alone. - Do not retry a refund that returned
processing; read it withGET /v1/refunds/{id}.