How refunds work
- Create a refund with
POST /v1/checkout/{checkout_id}/refund, giving areason, anidempotency_keyand, for a partial refund, anamount. - We debit your wallet in the checkout’s currency and send the money back to the payer.
- Track it with the
checkout.refundedandcheckout.refund_failedwebhooks, or fetch it withGET /v1/refunds/{refund_id}.
Which payments can be refunded
How the money goes back depends on how the payer paid, which is the checkout’s payment method (the sameid values as the payment methods offered).
How much you can refund
The refundable amount of a checkout is what you were credited for the payment, minus any refunds already made (refunds that failed do not count). What you were credited is the net amount, after Platnova’s processing fee. The processing fee is not returned when you refund. For example, if a payer paid NGN 10,000 and you were credited NGN 9,850 after a NGN 150 fee, you can refund up to NGN 9,850 in total. You can refund a checkout more than once, in parts, until the refundable amount reaches zero. Leaveamount out to refund everything that is left.
Funding and fees
Refunds are paid from your available wallet balance in the checkout’s currency. If the balance does not cover the refund, the request is rejected with422 insufficient_balance and nothing is debited.
A payment that is still waiting in a pending settlement is not in your wallet yet. Until it settles, refunding it draws on whatever else is in your balance.
- Card, Apple Pay and Google Pay refunds: your wallet is debited the refund
amount. - Bank-transfer refunds (
bank_transfer,bank_account,ussd,opay): these go out through the same payout system as Send payment, so the normal payout fee and, where it applies, the stamp duty levy are charged on top of the refund. The payer receives exactlyamount, and your wallet is debitedamountplus those charges. Bank-transfer refunds need payouts to be enabled on your business; if they are not, the request is rejected with422 payouts_not_enabled.
Idempotency
Every refund request needs anidempotency_key: a string you choose, up to 128 characters, unique across your business. Use something tied to the refund in your own system, such as order-1042-refund-1.
- Sending the same key for the same checkout and amount again returns the original refund with
200and does not refund twice. If a request times out or you are not sure it arrived, retry it with the same key. - Sending the same key with a different checkout or amount is rejected with
409 idempotency_key_reused. - A repeated key returns the original refund whatever its status, including
failed. To try again after a failed refund, send a new key. - A request refused with an error (see Errors) records nothing, so you can fix the cause and retry with the same key.
- If the first request with a key is still being processed, a repeat gets
409 refund_in_progress. Retry shortly.
Statuses
succeeded and failed are final. A refund moves pending → processing → succeeded or failed; a refund the provider refuses straight away can be failed in the response to the create request itself, so check status there as well as listening for webhooks.
Authentication
Send your API key in theX-API-KEY header, as with the rest of the API. A request made with a dashboard session instead of an API key needs a team member who has the permission to send payments, and a completed two-factor check.
Approval for refunds to another account
A bank-transfer refund back to the account the customer paid from goes out straight away. A refund to adestination you choose is a payout to an account of your choosing, so your business’s payout approval rules apply, exactly as for sending a payment:
- With an API key, it goes out straight away.
- A team member who can approve payments sends it straight away.
- A team member who cannot approve payments must name an
approver. The refund answers202withawaiting_approval: trueandstatus: "pending", and the approver is asked to approve the transfer. If they decline, the refund becomesfailedand the money is returned to your wallet. Without anapprover, the request is refused with422 approver_required.
Create a refund
POST /v1/checkout/{checkout_id}/refund
Request fields
- amount: Optional. How much to refund, in the checkout’s currency. Leave it out to refund the full refundable amount.
- reason: Required, up to 255 characters. Why you are refunding. It is for your own records and systems and is not shown to the payer.
- idempotency_key: Required, up to 128 characters. See Idempotency.
- destination: Optional. An NGN bank account to send a bank-transfer refund to:
account_numberandbank_code. Used only forbank_transfer,bank_account,ussdandopaypayments, and only when we do not have the account the payment came from. If we need it and it is missing, the request is rejected with422 refund_destination_required. It is ignored for card refunds, and ignored when we hold the account the payment came from: the money goes back where it came from. A destination is checked with the customer’s bank before anything is sent. - approver: Optional. The ID of a teammate who can approve payments. Needed only when a team member who cannot approve payments refunds to a
destination(see Approval).
Example request
Example response
The Refund object
List a checkout’s refunds
GET /v1/checkout/{checkout_id}/refunds returns every refund made on a checkout, newest first, including failed ones.
Retrieve a refund
GET /v1/refunds/{refund_id} returns a single Refund object, or 404 refund_not_found if there is no such refund on your business.
Errors
A refund request that cannot be made returnsstatus: false with a machine-readable code in both error[0] and data.code. Branch on the code, not on message, which is written for people and may change.
None of these errors debits your wallet.
Webhooks
Two webhooks tell you how a refund ended. Subscribe to them in your webhook settings like any other event.checkout.refunded
checkout.refund_failed
refund_id to match the webhook to the refund you created, and to ignore a repeated delivery of the same event.
