Refund payment (payment reversal)
This endpoint enables you to initiate a full or partial refund for a specific payment. It allows you to process payment reversals efficiently, ensuring that refunds are handled seamlessly.
Request
Method: POST
URL: https://api.sandbox.bobpay.co.za/v2/payments/reversal
Request body parameters
| Key | Description | Type | Required |
|---|---|---|---|
| id | ID of the payment record to refund. | integer | Conditional* |
| custom_payment_id | Your custom identifier for the payment to refund. | string | Conditional* |
| payment_method_id | Payment method record ID, used together with payment_method. |
integer | Conditional* |
| payment_method | The payment method of the payment, used together with payment_method_id. |
string | Conditional* |
| reversal_amount | The amount to refund, for partial refunds. Must be greater than zero and may not exceed the amount still outstanding on the payment. Defaults to the full payment amount. | float | No |
| reverse_directly_to_bank | If true, the funds are reversed directly to the payer's bank or card instead of their Bob Pay account balance. Only supported for card, Apple Pay, Google Pay, Scan to Pay and Capitec Pay payments. Defaults to true for those payment methods and to false for all others. Set it to false on a second or later refund of the same payment (see "Partial refunds" below). |
boolean | No |
* Note: Identify the payment using one of id, custom_payment_id, or payment_method_id together with payment_method.
Example:
{
"id": 72052
}
Partial refunds
Partial refunds are supported. Send reversal_amount with the amount you want to refund — that is the only additional field required. Identify the payment exactly as you would for a full refund. Leave reversal_amount out to refund the full amount.
{
"id": 72052,
"reversal_amount": 50.00
}
Rules
reversal_amountmust be greater than zero and may not exceed the original payment amount, or the amount still outstanding if the payment is alreadypartially_refunded.- The payment must have a status of
successorpartially_refunded. A payment that is alreadyrefundedcannot be refunded again. - After the first partial refund, the payment and its payment intent move to
partially_refunded. - Once the partial refunds together add up to the full payment amount, the payment and its payment intent move to
refunded. - Your account balance must cover the refund amount plus the reversal fee.
- Test payments and account top-up payments cannot be refunded.
Second and later refunds
Only one refund per payment can be sent back to the payer's bank or card. On any refund after the first, set reverse_directly_to_bank to false so that the refund is credited to the payer's Bob Pay account balance.
{
"id": 72052,
"reversal_amount": 50.00,
"reverse_directly_to_bank": false
}
If you leave reverse_directly_to_bank out, it defaults to true for card, Apple Pay, Google Pay and Capitec Pay payments, and the request fails with cannot reverse a <payment method> payment more than once.
Scan to Pay is the exception: it accepts more than one refund to the payer's bank, but only within 72 hours of the payment. After 72 hours the refund is credited to the payer's Bob Pay account balance instead. Check reversed_directly_to_bank in the response to confirm where the funds were sent.
Refunds that complete asynchronously
For Scan to Pay and Capitec Pay refunds to bank, the refund is confirmed by the bank after the request returns. The payment moves to refund_pending first, and then to its final refunded status once the bank confirms the refund. Capitec Pay accepts only one refund per payment.
Response
Status code: 200 OK Content-Type: application/json
Response fields
| Key | Description | Type |
|---|---|---|
| payment_method | Object with details about the reversed payment. | object |
| payment_method.id | Unique payment record ID. | integer |
| payment_method.payment_intent_id | Payment intent ID linked to this payment. | integer |
| payment_method.payment_intent_reference | Short payment intent reference. | string |
| payment_method.account_id | Originating account ID. | integer |
| payment_method.account | Object with originating account details. | object |
| payment_method.recipient_account_id | Recipient account ID. | integer |
| payment_method.recipient_account | Object with recipient account details. | object |
| payment_method.amount | Amount refunded. | float |
| payment_method.status | Status of the refund (should be "refunded"). | string |
| payment_method.transaction_date | Transaction date and time. | string (date) |
| payment_method.type | Type of transaction (e.g., "payment"). | string |
| payment_method.time_created | Time created. | string (date) |
| payment_method.time_modified | Time last modified. | string (date) |
| payment_method.custom_payment_id | Custom payment identifier. | string |
| payment_method.jurisdiction | Payment jurisdiction. | string |
| payment_method.card_association | Card association (e.g., VISA). | string |
| payment_method.is_debit_card | True if debit card used. | boolean |
| reversed_directly_to_bank | True if funds were reversed directly to bank. | boolean |
Note:
- The fields of the
payment_methodobject depend on the payment method being refunded. The example above shows a card refund; fields such asjurisdiction,card_association, andis_debit_cardare card-specific. - The response also contains deprecated
PaymentMethodandReversedDirectlyToBankkeys (duplicates ofpayment_methodandreversed_directly_to_bank). Ignore these; they will be removed in a future version.
Common errors
All of the errors below are returned with status code 400.
| Message | Cause |
|---|---|
| id or custom_payment_id or payment_method_id and payment_method is required | The request does not identify a payment. |
| reversal_amount must be greater than 0 | reversal_amount was zero or negative. |
| Reversal amount cannot be greater than the payment amount | reversal_amount is more than the original payment. |
| Reversal amount cannot be greater than the remaining payment amount | The payment is partially_refunded and reversal_amount is more than the amount still outstanding. |
| Payment has already been refunded | The payment is already fully refunded. |
| Only successful payments or partially refunded payments can be reversed | The payment status is not success or partially_refunded. |
cannot reverse a <payment method> payment more than once |
A second refund was requested with reverse_directly_to_bank set to (or defaulting to) true. Send false instead. |
| Only card, scan to pay, and PayShap RTP payments can be reversed to bank | reverse_directly_to_bank was set to true for a payment method that cannot refund to bank. |
| Unable to process the refund due to insufficient account balance | Your balance does not cover the refund amount plus the reversal fee. |
| Test payments cannot be reversed | The payment belongs to a test payment intent. |
| Account topup payments cannot be refunded | The payment was an account top-up. |
| the specified payment does not belong to this account | The payment was not received by your account. |