Card payin

Charge a bank card: card fields or a saved card token. The payer finishes the payment on the payment page, which runs 3DS when the issuer asks for it. A shop can hold card payins and capture or void them later.

Raw card numbers put your servers in PCI DSS scope. Without certification use a wallet, BLIK or the hosted payment form.

Use the key of a payin shop; a payout shop key gets 403 method_not_enabled. Rules shared by every method — Authentication, Idempotency, Errors, Webhooks — apply here and are not repeated.

Flow

  1. Generate merchant_reference and save it with your order before the call.
  2. Create the payin: POST /v2/payins/card. Save id from the response.
  3. Send the payer to next_action.redirect.url — the payment page. It runs 3DS when the issuer asks for it, then sends the payer to return_urls.
  4. If the shop holds card payins, the payin stops at authorized: call capture or void.
  5. Wait for the webhook payin.completed or payin.failed. Verify the signature and act on data.object — the full payin.
  6. 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. Polling instead of webhooks loads the API and gets 429 rate_limited (R20).

Endpoints

Step Endpoint Operation
Create POST /v2/payins/card createCardPayin
Capture a hold POST /v2/payins/{payin_id}/capture capturePayin
Void a hold POST /v2/payins/{payin_id}/void voidPayin
Refund POST /v2/payins/{payin_id}/refunds — common for all payins, see Refunds createRefund
Read one GET /v2/transactions/{transaction_id} — common for all transactions, see Read one transaction retrieveTransaction

The create, capture and void have their own sections below. Reading returns the Payin object.

POST /v2/payins/card

Creates a payin and returns the link to the payment page.

Request

Headers: Authorization: Bearer <payin shop key>, Content-Type: application/json.

Send either card or card_token.

Field Type Required Format and limits
merchant_reference string yes 1–100 characters. Unique per merchant across payins, refunds, payouts and pay sessions. The idempotency key of this call
amount object yes
amount.value string yes Major units, at most two decimals, at least 0.01: "100.50". A string, never a number
amount.currency string yes ISO 4217, the shop currency: EUR
payer object yes The person who pays
payer.first_name string with card_token With card, if first_name or last_name is omitted, both are taken from card.cardholder_name
payer.last_name string with card_token
payer.email string yes Email address
payer.ip_address string yes IPv4 or IPv6 of the payer
payer.address object yes Billing address
payer.address.country string yes ISO 3166-1 alpha-2: US
payer.address.state string for US, CA, AU payers ISO 3166-2 subdivision code without the country prefix, 2–3 letters: CA
payer.address.city string yes Not digits only
payer.address.postal_code string yes
payer.address.line1 string yes Street and house number
payer.address.line2 string no Apartment, suite, floor
card object one of card, card_token
card.number string yes 12–19 digits, no spaces, passes the Luhn check
card.exp_month string yes 1–12, with or without a leading zero: "12"
card.exp_year string yes Two digits: "30"
card.cvc string yes 3–4 digits. Never stored
card.cardholder_name string yes Latin letters and spaces, as printed on the card
card_token string one of card, card_token card.token of an earlier completed payin of this shop
save_card boolean no true — the payin returns card.token when it is completed. Works if the shop allows saving cards
return_urls object no
return_urls.success string no https:// URL. The payment page sends the payer there after completed, or at authorized when the shop holds card payins
return_urls.failure string no https:// URL. The payment page sends the payer there after failed
description string no Up to 255 characters
curl -X POST https://payments-api.mrcr.io/v2/payins/card \
  -H "Authorization: Bearer $PAYIN_SHOP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "merchant_reference": "order-10001",
    "amount": { "value": "100.50", "currency": "EUR" },
    "payer": {
      "email": "jane@example.com", "ip_address": "203.0.113.10",
      "address": { "country": "US", "state": "CA", "city": "San Francisco", "postal_code": "94105", "line1": "1 Market Street" }
    },
    "card": { "number": "4111111111111111", "exp_month": "12", "exp_year": "30", "cvc": "123", "cardholder_name": "JANE DOE" },
    "return_urls": { "success": "https://shop.example.com/pay/success", "failure": "https://shop.example.com/pay/failure" }
  }'

Response 201 Created

The Payin object in data, in requires_action with the link to the payment page in next_action.redirect.url. A repeat of an earlier call returns the payin in its current status.

Header Meaning
Idempotent-Replayed: true The same merchant_reference and body were sent before. This is the existing payin in its current status; nothing was created again
{
  "data": {
    "id": "0b6f4e2a-7c1d-4e8b-9a3f-5d2c8e1b4a70",
    "object": "payin",
    "merchant_reference": "order-10001",
    "method_type": "payin_card",
    "status": "requires_action",
    "status_details": null,
    "amount": { "value": "100.50", "currency": "EUR" },
    "refunded_amount": { "value": "0.00", "currency": "EUR" },
    "next_action": {
      "type": "redirect",
      "redirect": { "url": "https://pay-in.mrcr.io/order/0b6f4e2a-7c1d-4e8b-9a3f-5d2c8e1b4a70" },
      "expires_at": "2026-09-29T09:10:00Z"
    },
    "capture_method": "automatic",
    "card": { "brand": "visa", "last4": "1111" },
    "created_at": "2026-09-29T09:00:00Z",
    "updated_at": "2026-09-29T09:00:01Z"
  }
}

Errors

No payin is created on any of them.

HTTP code When Retry
400 validation_failed A field is missing or malformed — details[] names it. Also: both card and card_token, or neither; an unknown card_token; the amount has more than two decimals or is not in the shop currency No. Fix the request
401 authentication_failed The key is missing, malformed, revoked, or sent to the wrong host No
403 method_not_enabled Card payins are not enabled for the shop, or the key is of a payout shop No. Use the payin shop key, or ask Mercuryo to enable the method
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 payin
429 rate_limited Too many requests Yes, after Retry-After seconds
500 internal_error Failure on our side Yes, the identical request

After a timeout or a dropped connection the payin may or may not exist. Resend the identical request (R4), never with a new reference.

Payer step: the payment page

The payin is in requires_action with next_action.type: redirect:

  • Send the payer to next_action.redirect.url before next_action.expires_at. Open it as a full page or a new window, not in an iframe.
  • The page runs 3DS when the issuer asks for it.
  • After the final status the page sends the payer to return_urls.success or return_urls.failure; a payin held at authorized sends them to return_urls.success at the hold. Coming back is not a result — act on the webhook.

How to open the page in a browser or an app — Payer actions.

POST /v2/payins/{payin_id}/capture

Charges a held payin, the full amount. Only from authorized.

Request

Headers: Authorization: Bearer <payin shop key>.

Parameter In Type Required Meaning
payin_id path string yes id of the payin
amount body object no Partial capture — in the contract, returns 501 not_supported_yet
curl -X POST https://payments-api.mrcr.io/v2/payins/0b6f4e2a-7c1d-4e8b-9a3f-5d2c8e1b4a70/capture \
  -H "Authorization: Bearer $PAYIN_SHOP_KEY"

Response 200 OK

The Payin object in data, in processing. Then the webhook payin.completed, or payin.capture_failed — the payin is authorized again and you can call capture again.

Safe to repeat: a repeat while the capture is in progress, or after it, returns the payin in its current status.

Errors

HTTP code When Retry
404 resource_not_found No such payin in this shop No
409 action_not_allowed The payin is not authorized, or it is authorized after a failed void No
501 not_supported_yet amount is sent No. Capture the full amount

Also 401, 403, 429, 500 — see Errors.

POST /v2/payins/{payin_id}/void

Releases a held payin. Only from authorized. The payin ends failed with canceled_by_merchant.

Request

Headers: Authorization: Bearer <payin shop key>.

Parameter In Type Required Meaning
payin_id path string yes id of the payin
curl -X POST https://payments-api.mrcr.io/v2/payins/0b6f4e2a-7c1d-4e8b-9a3f-5d2c8e1b4a70/void \
  -H "Authorization: Bearer $PAYIN_SHOP_KEY"

Response 200 OK

The Payin object in data, in processing. Then the webhook payin.failed with canceled_by_merchant, or payin.void_failed — the payin is authorized again and you can call void again.

Safe to repeat: a repeat while the void is in progress, or after it, returns the payin in its current status.

Errors

HTTP code When Retry
404 resource_not_found No such payin in this shop No
409 action_not_allowed The payin is not authorized, or it is authorized after a failed capture No

Also 401, 403, 429, 500 — see Errors.

The Payin object

Returned by the create, capture, void and 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 card payin:

Field Type Always set Meaning
id string yes UUID. Use it in capture, void, refunds and GET /v2/transactions/{transaction_id}
object string yes payin
method_type string yes payin_card
status string yes created, processing, requires_action, authorized, completed, failed, refunded — see Statuses
status_details.code string when failed See Decline reasons
refunded_amount object yes Sum of completed refunds; above zero in refunded
refunded_amount.value string yes Major units: "20.00"
refunded_amount.currency string yes ISO 4217: EUR
next_action object or null yes Set in requires_action, null in every other status
next_action.type string in requires_action redirect
next_action.redirect.url string in requires_action The payment page
next_action.expires_at date-time no When the provider stops waiting for the payer; after it the payin ends failed
capture_method string yes automatic — charged at once. manual — the shop holds card payins at authorized
card object no The card
card.brand string no Card scheme: visa, mastercard
card.last4 string no Last four digits
card.token string no Set on a completed payin created with save_card: true. Pass it as card_token

Statuses

Status What it means The money Webhook What you do Next
created Accepted and recorded — — Wait requires_action, failed
requires_action Waiting for the payer on the payment page — — Send the payer to next_action.redirect.url processing, failed
processing Checks run; the payin, a capture or a void is at the provider — — Wait authorized, completed, failed
authorized Held on the card. Also after a failed capture or void Held payin.authorized, payin.capture_failed, payin.void_failed Call capture or void processing
completed Final. Charged Charged payin.completed Fulfil the order refunded
refunded Final. Charged, then refunded in part or in full. refunded_amount says how much Refunded in part or in full payin.refunded on every completed refund Reflect the refund in your order —
failed Final. Not charged, or the hold is released. status_details.code says why Released payin.failed Act on the decline reason —
  • Only completed, failed and refunded are outcomes. A final status never changes, with one exception: a completed refund moves the payin from completed to refunded.
  • requires_action comes in the create response, so it needs no webhook.
  • An unknown status is not final — keep waiting (R13).

Decline reasons

status_details.code on a failed card payin. Every decline is final: a new attempt is a new payin with a new merchant_reference.

Code When What you do
card_not_supported The card's BIN is unknown or not accepted Ask the payer for another card
method_unavailable No route for this card right now Try again later with a new payin
risk_rejected Rejected by antifraud Do not retry automatically
provider_declined The provider or the issuer declined, including a failed 3DS Ask the payer to try again or use another card
canceled_by_merchant You voided the hold —
internal_error Failure on our side; nothing was charged Try again with a new payin. Quote id to support if it repeats

An unknown code: handle it like provider_declined.

Webhooks

type When data.object
payin.authorized The payin is held: authorized The Payin object
payin.capture_failed The provider rejected a capture; the payin is authorized again The Payin object
payin.void_failed The provider rejected a void; the payin is authorized again The Payin object
payin.completed The payin reaches completed The Payin object
payin.refunded A refund of the payin completes — the first one moves it to refunded The Payin object, with refunded_amount
payin.failed The payin reaches failed The Payin object, with status_details
refund.completed, refund.failed A refund of this payin reaches a final status The Refund object — see Refunds

Headers, signature check and redelivery — Webhooks.

In the sandbox

The cents of amount.value select the outcome.

Cents Example Outcome
any other 100.00 completed; authorized when the shop holds card payins
.51 100.51 failed — provider_declined
.52 100.52 failed — risk_rejected
.54 100.54 failed — card_not_supported
.55 100.55 failed — method_unavailable
.56 100.56 failed — internal_error
.61 100.61 processing for 60 seconds, then completed

Sandbox keys and fixtures — Sandbox.

Next: Apple Pay