Apple Pay payin
Charge the card behind an Apple Pay token on the web. You validate the merchant session, the payer confirms in Apple Pay, you send the token.
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
-
On
onvalidatemerchant, sendvalidationURLand your domain toPOST /v2/payins/apple-pay/sessions. Passdata.apple_pay.sessiontocompleteMerchantValidationunchanged. - On
onpaymentauthorized, generatemerchant_referenceand save it with your order. - Create the payin:
POST /v2/payins/apple-paywithtoken.paymentDatain Base64. Saveid. -
Wait for the webhook
payin.completedorpayin.failed. Verify the signature and act ondata.object— the full payin. -
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 gets429 rate_limited(R20).
Endpoints
| Step | Endpoint | Operation |
|---|---|---|
| Merchant validation | POST /v2/payins/apple-pay/sessions |
createApplePaySession |
| Create | POST /v2/payins/apple-pay |
createApplePayPayin |
| 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 session and the create have their own sections below. Reading returns the Payin object.
POST /v2/payins/apple-pay/sessions
Gets an Apple Pay merchant session. Creates no transaction; safe to repeat.
Request
Headers: Authorization: Bearer <payin shop key>, Content-Type: application/json.
| Field | Type | Required | Format and limits |
|---|---|---|---|
apple_pay |
object | yes | |
apple_pay.validation_url |
string | yes | validationURL from onvalidatemerchant: https://apple-pay-gateway.apple.com/… |
apple_pay.domain |
string | yes | The domain the payment runs on, registered for the shop: shop.example.com |
curl -X POST https://payments-api.mrcr.io/v2/payins/apple-pay/sessions \
-H "Authorization: Bearer $PAYIN_SHOP_KEY" \
-H "Content-Type: application/json" \
-d '{ "apple_pay": { "validation_url": "https://apple-pay-gateway.apple.com/paymentservices/startSession", "domain": "shop.example.com" } }'
Response 200 OK
| Field | Type | Always set | Meaning |
|---|---|---|---|
data.apple_pay.session |
object | yes | The Apple Pay merchant session. Pass it to completeMerchantValidation unchanged |
Errors
| HTTP | code |
When | Retry |
|---|---|---|---|
| 400 | validation_failed |
validation_url is not an Apple Pay gateway URL, or domain is not registered for the shop |
No |
| 403 | method_not_enabled |
Apple Pay is 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 |
Also 401, 429, 500 — see Errors.
POST /v2/payins/apple-pay
Creates a payin from the Apple Pay token.
Request
Headers: Authorization: Bearer <payin shop key>, Content-Type: application/json.
| Field | Type | Required | Format and limits |
|---|---|---|---|
merchant_reference |
string | yes | 1–100 letters, digits, _, -. 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: "59.90". 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 | yes | Letters, digits, spaces, ., ', - |
payer.last_name |
string | yes | Letters, digits, spaces, ., ', - |
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 |
apple_pay |
object | yes | |
apple_pay.payment_token |
string | yes | Base64 of the token.paymentData JSON from onpaymentauthorized |
curl -X POST https://payments-api.mrcr.io/v2/payins/apple-pay \
-H "Authorization: Bearer $PAYIN_SHOP_KEY" \
-H "Content-Type: application/json" \
-d '{
"merchant_reference": "order-20001",
"amount": { "value": "59.90", "currency": "EUR" },
"payer": {
"first_name": "Jane", "last_name": "Doe", "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" }
},
"apple_pay": { "payment_token": "eyJ2ZXJzaW9uIjoiRUNfdjEiLCJkYXRhIjoi…" }
}'
Response 201 Created
The Payin object in data, in created. Any status is possible,
including a final one when the call repeats an earlier one.
| 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": "3e9c7b5d-0f4a-4b1e-8d6c-8a5f1b4e7da3",
"object": "payin",
"merchant_reference": "order-20001",
"method_type": "payin_apple_pay",
"status": "created",
"status_details": null,
"amount": { "value": "59.90", "currency": "EUR" },
"refunded_amount": { "value": "0.00", "currency": "EUR" },
"next_action": null,
"created_at": "2026-09-29T09:10:00Z",
"updated_at": "2026-09-29T09:10:00Z"
}
}
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: 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 |
Apple Pay is 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.
The Payin 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 an Apple Pay payin:
| Field | Type | Always set | Meaning |
|---|---|---|---|
id |
string | yes | UUID. Use it in refunds and GET /v2/transactions/{transaction_id} |
object |
string | yes | payin |
method_type |
string | yes | payin_apple_pay |
status |
string | yes | created, processing, 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 |
apple_pay |
object | no | Set after the token is decrypted |
apple_pay.brand |
string | no | Card scheme: visa, mastercard |
apple_pay.last4 |
string | no | Last four digits of the device card number |
Statuses
| Status | What it means | The money | Webhook | What you do | Next |
|---|---|---|---|---|---|
created |
Accepted and recorded | — | — | Wait | processing, failed |
processing |
The token is decrypted, checks run, the payin is at the provider | — | — | Wait | completed, failed |
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. status_details.code says why |
— | payin.failed |
Act on the decline reason | — |
-
Only
completed,failedandrefundedare outcomes. A final status never changes, with one exception: a completed refund moves the payin fromcompletedtorefunded. - An unknown status is not final — keep waiting (R13).
Decline reasons
status_details.code on a failed Apple Pay payin. Every decline is final: a new attempt is a new
payin with a new merchant_reference.
| Code | When | What you do |
|---|---|---|
invalid_payment_token |
The token could not be decrypted | Ask the payer to pay again in Apple Pay |
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 | Ask the payer to try again or use another card |
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.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
apple_pay.payment_token: test_apple_pay_token_visa, or a real token from Apple Pay in its sandbox
environment. The session endpoint returns a stub session. The cents of amount.value select the
outcome:
| Cents | Example | Outcome |
|---|---|---|
| any other | 59.00 |
completed |
.51 |
59.51 |
failed — provider_declined |
.52 |
59.52 |
failed — risk_rejected |
.54 |
59.54 |
failed — card_not_supported |
.55 |
59.55 |
failed — method_unavailable |
.56 |
59.56 |
failed — internal_error |
.61 |
59.61 |
processing for 60 seconds, then completed |
Sandbox keys and fixtures — Sandbox.
Next: Google Pay