# Refund a sale Building with an AI agent? Start at https://docs.payme.io/agents.md - Sandbox: `POST https://sandbox.payme.io/api/refund-sale` - Production: `POST https://live.payme.io/api/refund-sale` ## Credentials - Request body field `payme_client_key` (optional): Partners: your partner key. You can send it in the `PayMe-Partner-Key` header instead. A seller leaves it out. - Request body field `seller_payme_id` (optional): The seller's MPL. Required unless you send `seller_id` with `payme_client_key`. You can send it in the `PayMe-Merchant-Key` header instead. ## Overview Refunds a paid sale, in full or in part. The money goes back to the payment method the buyer paid with. On an `authorized` sale the same call voids the hold instead; see [Void an authorization](/payments/post-sale-actions/void-sale). ### Credentials Name the seller: send `seller_payme_id`, or `seller_id` with your partner key (`payme_client_key`). A seller needs no partner key. A partner key on its own fails with error `1`. The sale must belong to the seller you name; otherwise the call returns HTTP 403, error `303`. ### Rules - The sale's status must be `completed` or `partial-refund`. Any other status, including `chargeback`, returns error `305` with the current status in `status_additional_info`. While a chargeback is open the dispute returns the money, so no refund is possible. - Leave out `sale_refund_amount` to refund everything that is left. Never send `0`. - You can refund several times, up to the sale price. Each partial refund, including the last one, must be at least the account's minimum sale value (500 by default). - After a partial refund, `sale_status` stays `partial-refund` even when nothing is left. Read `sale_refund_buffer` for the amount that remains. - If the seller's balance doesn't cover the refund, PayMe takes the shortfall from the seller's card on file and returns `refunded_from_creditcard: true`. - A refund can't be undone. - Sales paid by POS, eCheck or a Verifone prepaid card aren't refunded with this call (error `4`). - Send `verify_only: true` to check a refund without making it. ### Callback When the refund succeeds, PayMe sends a `refund` callback (`notify_type: refund`) to the sale's `sale_callback_url` and to your partner's sale callback URL. A void sends the same callback. See [Callbacks](/payments/getting-started/callbacks). With the invoicing service active on the account, a refund issues a credit document; the original document is not cancelled. ### Language of errors Errors about the refund itself (wrong status, amount, nothing left to refund) come back in the language the sale was created with, whatever you send in `language`. ## Parameters This endpoint takes no path, query or header parameters. ## Request body Content type: `application/json` | Field | Type | Required | Description | | --- | --- | --- | --- | | `payme_client_key` | string | no | Partners: your partner key. You can send it in the `PayMe-Partner-Key` header instead. A seller leaves it out. | | `seller_payme_id` | string | no | The seller's MPL. Required unless you send `seller_id` with `payme_client_key`. You can send it in the `PayMe-Merchant-Key` header instead. | | `seller_id` | string | no | Partners: your own ID for the seller (the `seller_id` you set when you created it). Send it with `payme_client_key` instead of `seller_payme_id`. | | `payme_sale_id` | string | yes | The sale to refund: PayMe's sale ID, or your own `transaction_id` for that sale. | | `sale_refund_amount` | integer | no | Amount to refund, in minor units (`1000` = 10.00). Leave it out to refund everything that is left. Never send `0`. A partial amount must be at least the account's minimum sale value (500 by default). | | `verify_only` | boolean | no | `true` to check the call without making it: PayMe validates the request and returns the amounts, but changes nothing and sends no callback. Default `false`. | | `language` | string | no | Language of error messages: `en`, or `he` (the default). `es`, `ru` and `ar` are accepted, but messages then come back mostly in English. Any other value returns error `51`. | ### Request example ```json { "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "payme_sale_id": "SALEDEMO-XXXXXXXX-XXXXXXXX-XXXXXXXX", "sale_refund_amount": 1000, "language": "en" } ``` ## Code samples ### curl ```bash curl -X POST 'https://sandbox.payme.io/api/refund-sale' \ -H 'Content-Type: application/json' \ --data-raw '{ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "payme_sale_id": "SALEDEMO-XXXXXXXX-XXXXXXXX-XXXXXXXX", "sale_refund_amount": 1000, "language": "en" }' ``` ### Node.js ```javascript const response = await fetch("https://sandbox.payme.io/api/refund-sale", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "payme_sale_id": "SALEDEMO-XXXXXXXX-XXXXXXXX-XXXXXXXX", "sale_refund_amount": 1000, "language": "en" }), }); console.log(await response.text()); ``` ### Python ```python import requests response = requests.post( "https://sandbox.payme.io/api/refund-sale", json={ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "payme_sale_id": "SALEDEMO-XXXXXXXX-XXXXXXXX-XXXXXXXX", "sale_refund_amount": 1000, "language": "en", }, ) print(response.text) ``` ### PHP ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => $body, ]); $response = curl_exec($ch); curl_close($ch); echo $response; ``` ## Responses ### 200 — The refund was made (or, with `verify_only`, would be accepted). (success) | Field | Type | Description | | --- | --- | --- | | `status_code` | integer | `0`: success. | | `status_error_code` | integer | `0` on success. | | `payme_status` | string | `success`. | | `refunded_from_creditcard` | boolean | `true` when the seller's balance didn't cover the refund and PayMe took the shortfall from the seller's card on file. | | `sale_invoice_number` | string | Number of the credit document, when the invoicing service is active on the account; otherwise `null`. | | `sale_invoice_url` | string | Link to the credit document, when the invoicing service is active on the account; otherwise `null`. | | `sale_refund_buffer` | integer | Amount still left to refund, in minor units. | | `sale_status` | string | The sale's status after the refund: `refunded`, or `partial-refund` after a partial refund. | | `payme_transaction_total` | integer | Amount refunded to the buyer, in minor units. | | `payme_transaction_total_aft_deduction` | integer | Amount taken from the seller for this refund, after fees, in minor units. | | `payme_transaction_id` | string | PayMe's ID of the refund transaction. `null` when `verify_only` is `true`. | | `payme_transaction_card_brand` | string | Card brand. `null` when `verify_only` is `true`. | | `payme_transaction_auth_number` | string | Authorization number from the card issuer. `null` when `verify_only` is `true`. | | `payme_transaction_voucher` | string | Voucher number. `null` when `verify_only` is `true`. | | `payme_signature` | string | PayMe's signature for the transaction. `null` when `verify_only` is `true`. | | `payme_transaction_acquirer` | string | The acquirer, or `null`. | | `payme_transaction_arn` | string | Acquirer reference (EMV UID) when the acquirer returns one; usually `null`. Not returned when `verify_only` is `true`. | | `session` | string | PayMe's ID for this request. Quote it when you contact PayMe. | ```json { "status_code": 0, "status_error_code": 0, "payme_status": "success", "refunded_from_creditcard": false, "sale_invoice_number": null, "sale_invoice_url": null, "sale_refund_buffer": 9000, "sale_status": "partial-refund", "payme_transaction_total": 1000, "payme_transaction_total_aft_deduction": 1000, "payme_transaction_id": "TRANDEMO-XXXXXXXX-XXXXXXXX-XXXXXXXX", "payme_transaction_card_brand": "Mastercard", "payme_transaction_auth_number": "72****4", "payme_transaction_voucher": "24****9", "payme_signature": "b71e**************ca5a", "payme_transaction_acquirer": null, "payme_transaction_arn": null, "session": "Ab12Cd34" } ``` ### 400 — Error `1`: no seller named (a partner key on its own). Error `352`: `sale_refund_amount` isn't a positive number. Error `357`: this sale type can't be refunded, for example a customer deposit. Error `20`: the seller doesn't belong to the partner key sent. (error) | Field | Type | Description | | --- | --- | --- | | `status_code` | integer | `1`: the request failed. | | `status_error_code` | integer | PayMe's error code. See [Errors](/payments/getting-started/errors). | | `status_error_details` | string | The error message, in the language you asked for. | | `status_additional_info` | string | Extra detail: usually the field at fault, the sale's current status, or the masked card number. | | `session` | string | PayMe's ID for this request. Quote it when you contact PayMe. | ```json { "status_code": 1, "status_error_details": "Required parameter is missing", "status_additional_info": "seller_payme_id", "status_error_code": 1, "session": "Ab12Cd34" } ``` ### 403 — Error `303`: the sale belongs to another partner or another seller. (error) | Field | Type | Description | | --- | --- | --- | | `status_code` | integer | `1`: the request failed. | | `status_error_code` | integer | PayMe's error code. See [Errors](/payments/getting-started/errors). | | `status_error_details` | string | The error message, in the language you asked for. | | `status_additional_info` | string | Extra detail: usually the field at fault, the sale's current status, or the masked card number. | | `session` | string | PayMe's ID for this request. Quote it when you contact PayMe. | ```json { "status_code": 1, "status_error_details": "Could not access sale", "status_additional_info": "", "status_error_code": 303, "session": "Ab12Cd34" } ``` ### 429 — Error `998`: another call for this sale is still running. Wait, then check the sale before retrying. (error) | Field | Type | Description | | --- | --- | --- | | `status_code` | integer | `1`: the request failed. | | `status_error_code` | integer | PayMe's error code. See [Errors](/payments/getting-started/errors). | | `status_error_details` | string | The error message, in the language you asked for. | | `status_additional_info` | string | Extra detail: usually the field at fault, the sale's current status, or the masked card number. | | `session` | string | PayMe's ID for this request. Quote it when you contact PayMe. | ```json { "status_code": 1, "status_error_details": "Operation is not permitted", "status_additional_info": "Duplicated action detected, please retry later", "status_error_code": 998, "session": "Ab12Cd34" } ``` ### 500 — Request errors. `1`: a required field is missing. `200`: unknown partner key. `251`: unknown seller. `350`: no sale has this ID. `305`: the sale isn't `completed` or `partial-refund` (its status is in `status_additional_info`). `309`: already fully refunded. `352`: the amount is outside the account's limits. `503`: more than what is left to refund. `517`: more than the sale price. `4`: POS, eCheck or Verifone prepaid sale. (error) | Field | Type | Description | | --- | --- | --- | | `status_code` | integer | `1`: the request failed. | | `status_error_code` | integer | PayMe's error code. See [Errors](/payments/getting-started/errors). | | `status_error_details` | string | The error message, in the language you asked for. | | `status_additional_info` | string | Extra detail: usually the field at fault, the sale's current status, or the masked card number. | | `session` | string | PayMe's ID for this request. Quote it when you contact PayMe. | ```json { "status_code": 1, "status_error_details": "Cannot perform action due to an incorrect status", "status_additional_info": "chargeback", "status_error_code": 305, "session": "Ab12Cd34" } ```