Refunds
POST /v1/refunds refunds a succeeded payment intent — in full or in part. Refund IDs use the vpr_test_* / vpr_live_* prefix.
Required key: secret key (vp_sk_*). Available in SDK 0.11.x.
Create a refund
Omit amount to refund the full remaining balance (the server computes captured − previously refunded). Pass an amount below the remaining balance for a partial refund.
curl https://checkout.vonpay.com/v1/refunds \
-H "Authorization: Bearer vp_sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "payment_intent": "vpi_test_abc123", "amount": 500 }'
const refund = await client.refunds.create({
paymentIntent: "vpi_test_abc123",
amount: 500, // omit for a full refund
});
Request
| Field | Type | Required | Description |
|---|---|---|---|
payment_intent | string | — | ID of the payment intent to refund (vpi_test_* / vpi_live_*). Optional, but you must supply exactly one parent reference — either payment_intent or transaction. |
amount | integer | — | Minor units; positive (≥ 1). Omit to refund the full remaining balance; pass a value below the remaining balance for a partial refund. |
currency | string | — | 3-letter alphabetic ISO 4217 currency code. |
reason | string | — | One of duplicate, fraudulent, requested_by_customer, expired_uncaptured_charge. Mirrored back on the refund record. |
metadata | object | — | Object with string keys and arbitrary JSON values. |
Response — Refund
{
"id": "vpr_test_JL3xPcFktvsF10Ib",
"payment_intent": "vpi_test_abc123",
"amount": 500,
"currency": "USD",
"status": "succeeded",
"reason": null
}
| Field | Type | Description |
|---|---|---|
id | string | Refund record ID (vpr_test_* / vpr_live_*). |
payment_intent | string | The payment intent this refund applies to. |
amount | integer | Refunded amount, in minor units. |
currency | string | ISO 4217 (uppercase on response). |
status | string | requested, succeeded, failed, or canceled. |
reason | string | null | The reason supplied on create, or null. |
Status
A refund record is created in requested and reaches a terminal succeeded or failed; canceled is a rare terminal state.
requested ──▶ succeeded
└─▶ failed
Full vs. partial
- Full — omit
amount. Refundscaptured − previously refunded. - Partial — pass an
amountbelow the remaining refundable balance. The full-vs-partial outcome is determined by the amount relative to the remaining balance, not by whetheramountis present. - Multiple partial refunds against the same intent are allowed up to the remaining refundable balance. Each issues a separate refund record and fires its own
charge.refundedevent.
Constraints
| Condition | Result |
|---|---|
amount exceeds the remaining refundable balance | 422 refund_amount_exceeds_remaining. The error envelope carries remaining_refundable. |
The source intent is not in a refundable state (status is not succeeded) | 422 refund_intent_not_refundable. The error envelope carries payment_intent and current_status. |
Void-after-capture
To reverse a captured intent, check the void_after_capture capability. When it is rerouted_to_refund, a void on a captured intent is handled through the refund pathway — call refunds.create rather than paymentIntents.void.
Reconciliation
Each refund fires a charge.refunded webhook carrying refund_id, amount (this refund), is_partial, and original_charge_amount. To get the cumulative refunded total for a charge, sum amount across all charge.refunded events for the same transaction_id.
Related
- Payment Intents — the intent a refund applies to
- Capabilities —
partial_refund,void_after_capture - Webhook Events —
charge.refunded