Refunds
Return money of a completed payin, in full or in parts. A refund is its own transaction with its own
id, merchant_reference and status. When a refund completes, the payin moves to refunded and its
refunded_amount grows; later refunds of a refunded payin add to refunded_amount.
method_type of the payin |
Refund |
|---|---|
payin_card |
Full or partial |
payin_apple_pay |
Full or partial |
payin_google_pay |
Full or partial |
payin_blik |
501 not_supported_yet |
Use the key of the payin shop that holds the payin.
Flow
- Generate a
merchant_referencefor the refund and save it before the call. - Create the refund:
POST /v2/payins/{payin_id}/refunds. Saveidfrom the response. -
Wait for the webhook
refund.completedorrefund.failed. Verify the signature and act ondata.object— the full refund. -
Fallback: if no webhook has come for a minute, you can start polling the status with
GET /v2/transactions/{transaction_id}until it is final (R20).
POST /v2/payins/{payin_id}/refunds
Request
Headers: Authorization: Bearer <payin shop key>, Content-Type: application/json.
| Field | In | Type | Required | Format and limits |
|---|---|---|---|---|
payin_id |
path | string | yes | id of the payin |
merchant_reference |
body | string | yes | 1–100 letters, digits, _, -. Unique per merchant across payins, refunds, payouts and pay sessions. The idempotency key of this call |
amount |
body | object | no | The whole refundable remainder if omitted |
amount.value |
body | string | yes, in amount |
Major units, at most two decimals, at least 0.01, up to the refundable remainder: "20.00" |
amount.currency |
body | string | yes, in amount |
The payin currency: EUR |
description |
body | string | no | Why you refund, up to 255 characters |
The refundable remainder is the payin amount minus its refunds that are not failed, including
refunds still in progress.
curl -X POST https://payments-api.mrcr.io/v2/payins/3e9c7b5d-0f4a-4b1e-8d6c-8a5f1b4e7da3/refunds \
-H "Authorization: Bearer $PAYIN_SHOP_KEY" \
-H "Content-Type: application/json" \
-d '{ "merchant_reference": "refund-10001", "amount": { "value": "20.00", "currency": "EUR" } }'
Response 201 Created
The Refund object in data. A repeat with the same merchant_reference and body
returns the existing refund in its current status.
| Header | Meaning |
|---|---|
Idempotent-Replayed: true |
The same merchant_reference and body were sent before. This is the existing refund; nothing was created again |
{
"data": {
"id": "7c3a1f9b-4d8e-4f5c-8b0a-2e9d5f8c1be7",
"object": "refund",
"merchant_reference": "refund-10001",
"method_type": "refund",
"payin_id": "3e9c7b5d-0f4a-4b1e-8d6c-8a5f1b4e7da3",
"status": "created",
"status_details": null,
"amount": { "value": "20.00", "currency": "EUR" },
"created_at": "2026-09-29T11:00:00Z",
"updated_at": "2026-09-29T11:00:00Z"
}
}
Errors
No refund is created on any of them.
| HTTP | code |
When | Retry |
|---|---|---|---|
| 400 | validation_failed |
A field is missing or malformed — details[] names it |
No. Fix the request |
| 401 | authentication_failed |
The key is missing, malformed, revoked, or sent to the wrong host | No |
| 403 | method_not_enabled |
A payout shop key | No. Use the payin shop key |
| 404 | resource_not_found |
No such payin in this shop | No |
| 409 | action_not_allowed |
The payin is not completed, nothing is left to refund, or amount is above the remainder |
No |
| 409 | merchant_reference_conflict |
The reference was used with a different body | No. Resend the original body, or use a new reference for a new refund |
| 501 | not_supported_yet |
A BLIK payin | No |
| 429 | rate_limited |
Too many requests | Yes, after Retry-After seconds |
| 500 | internal_error |
Failure on our side | Yes, the identical request |
The Refund object
Returned by the create and by GET /v2/transactions/{transaction_id}, and sent in webhooks as
data.object. It has the common fields of
every transaction, with these values and fields for a refund:
| Field | Type | Always set | Meaning |
|---|---|---|---|
id |
string | yes | UUID |
object |
string | yes | refund |
method_type |
string | yes | refund |
payin_id |
string | yes | The payin the money is returned from |
status |
string | yes | created, processing, completed, failed — see Statuses |
status_details.code |
string | when failed |
provider_declined or internal_error |
Refunds of one payin: GET /v2/transactions?payin_id=… — see Transactions.
Statuses
| Status | What it means | Webhook | What you do | Next |
|---|---|---|---|---|
created |
Accepted and recorded; the amount is reserved from the remainder | — | Wait | processing, failed |
processing |
At the provider | — | Wait | completed, failed |
completed |
Final. Returned to the payer; the payin is refunded and its refunded_amount grows |
refund.completed, and payin.refunded for the payin |
Mark the refund done | — |
failed |
Final. Not returned; the amount is back in the remainder | refund.failed |
Refund again with a new merchant_reference |
— |
Webhooks
type |
When | data.object |
|---|---|---|
refund.completed |
The refund reaches completed |
The Refund object |
refund.failed |
The refund reaches failed |
The Refund object, with status_details |
payin.refunded |
The refund completes | The payin, with refunded_amount |
Headers, signature check and redelivery — Webhooks.
Next: Webhooks