Skip to main content
A refund returns money from a payment that succeeded: a re_test_… / re_live_… object your server creates with a secret key. It runs through the same engine as a refund made in the dashboard, so it shows up there, on the receipt, and on the payment’s amount_refunded.
1

Refund the payment

Response
payment is a pay_… of yours whose status is succeeded. A bank debit still processing is 400 payment_not_refundable until it settles. A card refund sent before the processor has settled the charge can be refused: the create answers 402 refund_failed (in sandbox, a refund right after the capture came back with failure_code: card_declined), and that refund stays failed. Refund again later, with a new Idempotency-Key. amount is optional, in the smallest unit of the currency, so 2500 is $25.00; leave it out to refund everything that has not been refunded yet. currency is the payment’s. reason is optional, up to 200 characters, and is shown on the refund in the dashboard and on the receipt.
2

Read it back

A refused refund stays readable at the same id: the create answered 402, and this is where you see why.
An Idempotency-Key is required, as on POST /payments. For 24 hours, the same key with the same body answers the same refund, byte for byte, with Idempotent-Replayed: true; the same key with any other body (a different payment, amount or reason) is 400 idempotency_key_reused, and so is a value you already used on another endpoint, such as the charge you are now refunding. After that the refund itself still holds the key: sent again by the same API key with the same payment and amount, it answers that refund as it is now, and with another payment or amount it is 400 idempotency_key_reused. Use one fresh key per refund attempt. Refunds made in the dashboard on a payment made through this API are re_… objects too: they retrieve, list and emit refund.succeeded like any other.

Status

status is Payra’s own lifecycle. It is never a string from the processor.

Why a refund failed

failure is set when status is failed, and null otherwise. It carries a code from the same fixed vocabulary as a payment failure and a message written by Payra; the processor’s own wording is never relayed.

Refusals

A refusal is an answer, not an outage: the create responds 402 refund_failed and leaves the refund behind for you to read.
code is refund_failed for every refusal, so failure_code is what you branch on; it is the refund’s failure.code, from the vocabulary above. When Payra cannot tell whether the processor received the refund (the request timed out, or the connection failed), the create answers 201 with status: "pending" instead, and a later webhook says how it ended.

The remaining balance

Each payment carries amount_refunded: how much of its amount has gone back so far, counting refunds that succeeded wherever they were made, the API or the dashboard. A refund’s amount may not exceed amount − amount_refunded (400 refund_amount_exceeds_balance; the message says how much is left). Partial refunds can be repeated until the payment is fully refunded. A pending refund does not reduce the balance yet, but it blocks another refund of the same payment until it succeeds or fails: one at a time, 409 refund_in_flight for the second. Send it again with a new Idempotency-Key once the first has ended; the same key replays the 409. The payment’s own status stays succeeded after a refund. To know whether money went back, read amount_refunded, list the payment’s refunds, or listen to refund.succeeded.

Receipts

The customer is emailed a refund receipt once the refund is succeeded, when the payment belongs to a customer with an email address whose email notifications are on, and the workspace’s notifications are not paused. A payment with no customer gets none. reason is shown on the receipt and in the dashboard. When you omit it, they read “Refund requested via API” while the refund object answers reason: null.

Listing refunds

GET /refunds answers the refunds of payments made through this API, including those made in the dashboard, newest first, a page at a time. It takes limit and starting_after, and works the same as every other list; see pagination for walking the pages. payment=pay_… narrows it to one payment’s refunds; an unknown payment answers an empty list.
Response (abbreviated)
The list only ever holds your own workspace’s refunds, in the environment of the key you sent.

Errors