Refunds
Refund a captured payment in full or in part, read the refund's own status, and know what a dispute does to refunds.
POST /v1/payments/{id}/refundRefunds a captured payment. A refund may be full or partial, and several partial refunds are allowed on the same payment as long as their sum does not exceed the captured amount. Accounts limited to refunds and voids may call this route.
To cancel a payment that was not captured yet, void it instead.
Eligibility
- The payment has a successful capture: it is
CAPTURED,PARTIALLY_REFUNDED, orDISPUTEDwith a dispute that does not block (see Disputes). Anything else answers404. - No other refund of the same payment is in progress.
amount, when given, is at least1and no more than the remaining refundable balance. Omit it to refund the whole remaining balance.- Your daily refund limit, if one is configured, is not exceeded. See Account limits.
Request
Idempotency-Key: honoured. The 409 for a disputed payment is not stored under the key.
| Field | Type | Required | Rules |
|---|---|---|---|
amount | integer | no | Cents to refund, at least 1 and at most the remaining refundable balance. Omit for a full refund of the remaining balance. |
reason | string | no | Free text recorded against the refund, at most 255 characters. It is returned on the payment's history. Never put card numbers, security codes or other sensitive data in it. |
# Full refund of the remaining balance
curl -X POST https://api-payments-staging.highwirepayments.com/v1/payments/01928e3e-7b6c-7c8e-9b3a-5b9f8b9b9b9b/refund \
-u "YOUR_API_KEY:YOUR_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0193c30a-1f2e-7b6c-8d9a-0e1f2a3b4c5d" \
-d '{}'
# Partial refund with a reason
curl -X POST https://api-payments-staging.highwirepayments.com/v1/payments/01928e3e-7b6c-7c8e-9b3a-5b9f8b9b9b9b/refund \
-u "YOUR_API_KEY:YOUR_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0193c30b-7a8b-7c9d-8e0f-1a2b3c4d5e6f" \
-d '{
"amount": 500,
"reason": "customer request"
}'An empty body ({}) is a full refund; the Content-Type header is still required.
Response
201 Created:
{
"refundId": "01928e55-3c4d-7c8e-8b1a-fedcba987654",
"paymentId": "01928e3e-7b6c-7c8e-9b3a-5b9f8b9b9b9b",
"status": "SUCCEEDED",
"amount": 500,
"remainingRefundableAmount": 6700,
"cardNumber": "555555xxxxxxxx4444"
}| Field | Type | Notes |
|---|---|---|
refundId | string | The refund's own id. It appears in the payment's transactions with object: "refund". |
paymentId | string | The payment that was refunded. |
status | enum | The refund's status, not the payment's. SUCCEEDED: the money is on its way back. PENDING: the processor accepted the refund and is still settling it; it resolves to SUCCEEDED, FAILED or CANCELED asynchronously, visible on the payment's history. |
amount | integer | Cents refunded by this call. |
remainingRefundableAmount | integer | Cents still available for further refunds, computed as if this refund succeeds. |
cardNumber | string or null | Masked card number of the payment. null for wallet payments, which carry no card number. |
The payment's own status moves only when a refund succeeds: to PARTIALLY_REFUNDED, or to
REFUNDED once the refunded total reaches the captured amount. A DISPUTED payment keeps that
status; the refund is recorded on its history. See Payment lifecycle.
| Status | Body | When |
|---|---|---|
201 | The response above. | Refunded, or accepted and settling (PENDING). |
400 | Validation error | amount is not a positive integer, or reason is too long. |
400 | Plain error | A refund is already in progress for this payment, Payment has already been fully refunded, or amount exceeds remaining refundable balance (6700). |
400 | Refund decline | The processor refused the refund, or Amount exceeds daily refund limit. |
404 | Plain error | Payment not found or not refundable: <id>: unknown, another account's, or a payment without a successful capture (AUTHORIZED, DECLINED, VOIDED, PENDING). A malformed id answers 404 too. |
409 | Reason-coded conflict | reason: "PAYMENT_DISPUTED", see below; or (without reason) an Idempotency-Key in flight. |
422 | Plain error | The key was reused with a different body. |
401 | Plain error | Invalid credentials, or an inactive account. |
500 | Plain error | The refund was accepted but could not be finalized yet: do not retry; poll the payment. |
Disputes
A cardholder can dispute a payment with their bank. The payment becomes DISPUTED, permanently.
While the dispute still holds the funds, a refund would pay the cardholder twice, so it is refused:
{
"statusCode": 409,
"message": "Payment has an open or lost dispute and cannot be refunded.",
"reason": "PAYMENT_DISPUTED"
}- A chargeback, or a later stage, that is open or was lost blocks refunds.
- An inquiry (the bank asking for information, no funds moved) does not block, and neither does a dispute that was won or closed.
- The
409is not stored under theIdempotency-Key: the same key can be sent again once the dispute is won.