Refund a credit card transaction
Return money to a customer for a credit card payment, in full or in part.
Use this flow for returns, cancelled orders, duplicate charges, or any case where you owe the customer money back. You can issue several refunds against the same debit, up to the amount that was paid.
Prerequisites
- Getting your API KeyJetpay API Key
- The ID of a paid credit card debit
If you haven't collected a payment yet, see Directly charge a credit cardDirectly charge a credit card or Preauthorize and capturePreauthorize and capture first.
Flow overview
sequenceDiagram
autonumber
participant Platform
participant Jetpay
participant Bank as Customer's Bank
Platform->>Jetpay: GET /debit/{debit_id}/refunds
Jetpay-->>Platform: Refundable amount and existing refunds
Platform->>Jetpay: POST /debit/{debit_id}/refunds
Jetpay-->>Platform: Refund created (status: processing)
Jetpay-->>Platform: Webhook (payments.refunds.created)
Jetpay->>Bank: Submit refund
Jetpay-->>Platform: Webhook (payments.refunds.submitted / failed)Implementation details
Check what can be refunded
Endpoint: GET /debit/{debit_id}/refunds
Look up how much of a debit is still refundable before you issue a refund.
curl https://extapi.demo.jetpay.baselinepayments.com/debit/debit_123/refunds \
-H "Authorization: Bearer <YOUR_API_TOKEN>"Handle the response
{
"transaction_id": "debit_123",
"captured_amount": 100.00,
"submitted_refund_amount": 0.00,
"processing_refund_amount": 0.00,
"refundable_amount": 100.00,
"blocked_refund_amount": 0.00,
"blocked_refund_reason": null,
"refunds": []
}Create a refund
Endpoint: POST /debit/{debit_id}/refunds
Refund part or all of the refundable amount, with a reason.
curl https://extapi.demo.jetpay.baselinepayments.com/debit/debit_123/refunds \
-X POST \
-H "Authorization: Bearer <YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"amount": 25.00,
"reason": "customer_request",
"reason_details": "Item returned in store"
}'Handle the response
If the refund is created, the endpoint returns HTTP 201:
{
"id": "refund_001",
"amount": 25.00,
"reason": "customer_request",
"reason_details": "Item returned in store",
"status": "processing"
}Store the returned id to match it against refund events later.
Checking the debit again now shows the refund in progress:
{
"transaction_id": "debit_123",
"captured_amount": 100.00,
"submitted_refund_amount": 0.00,
"processing_refund_amount": 25.00,
"refundable_amount": 75.00,
"blocked_refund_amount": 0.00,
"blocked_refund_reason": null,
"refunds": [
{
"id": "refund_001",
"amount": 25.00,
"reason": "customer_request",
"reason_details": "Item returned in store",
"status": "processing"
}
]
}Track status updates via events/webhooks
As a refund moves through its lifecycle, an Event is created for each update. The most efficient way to track these updates in your system is to register for webhooks. If you are not yet familiar with our webhooks system, please review the Events and webhooksEvents and webhooks guide.
{
"refundId": "refund_001",
"transactionId": "debit_123",
"status": "submitted",
"amount": "25.00",
"reason": "customer_request",
"reasonDetails": "Item returned in store"
}See the Transactions reference for request fields and error codes, and the Events reference for the refund events.
Handling failures
The refund can't be created. Nothing was refunded, so fix the cause before trying again:
- 409: the debit isn't refundable right now. Either it hasn't been paid yet, or it has been permanently closed to refunds.
- 422: the amount is more than what's left to refund, or the debit wasn't paid by credit card.
The request fails with a 5xx or times out. The refund may still have been created. Check what can be refunded before you retry, and only create a new refund if it isn't in the list.
The refund fails. A refund can come back as failed, either right away in the create response or later through a payments.refunds.failed event. A failed refund releases its amount, so you can create a new one for the same amount. failed is final, and the same refund never changes status again.
A submitted refund is rejected. In rare cases the customer's bank rejects a refund after it was submitted. Jetpay reviews these, and you receive payments.refunds.failed if the refund can't be completed.
Testing
Refunds in the demo environment follow the same lifecycle as production. To test end to end:
- Charge a card in the demo environment using a Directly charge a credit carddirect charge or a preauthorization you capture.
- Create a partial refund, then check that the refundable amount drops by that amount.
- Try to refund more than what's left and confirm you handle the 422.
- Point a webhook at your test server and confirm you record payments.refunds.created.
Best practices
- Check before you refund. Read the refundable amount first so you never ask for more than is available.
- Never blindly retry a create. Refund creation doesn't support an Idempotency-Key, so repeating a request creates a second refund. After any error or timeout, check the refunds list first.
- Store the refund id. It's how you match webhook events to the refund in your system.
- Drive your status from webhooks. Update your records from refund events rather than polling, and be ready for failed at any point.
- Record a reason. Add reason_details so your team and ours can see why the money went back.
FAQ
Can I refund a bank transfer or an in-person terminal payment? No. Refunds are available for credit card debits only.
Can I issue more than one refund on the same payment? Yes. Refund as many times as you need, up to the total amount paid.
Can I refund a preauthorization? You can refund whatever has been captured. To give back an amount you haven't captured yet, release it instead (see Preauthorize and capturePreauthorize and capture).
Does the refund include the surcharge? The refundable amount is what the customer was charged, including any customer surcharge. Refund that full amount to give back everything they paid.
Is my customer notified? Yes. Jetpay emails the customer when a refund is initiated.
How long does a refund take? A refund stays processing while Jetpay prepares and sends it, then moves to submitted once it has been sent to the customer's bank. When the money shows up on the customer's statement depends on their bank.