Google Pay payin
Charge the card behind a Google Pay token (ECv2). Some cards ask for 3DS: the payer passes it on the payment page.
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
-
Get
paymentMethodData.tokenizationData.tokenfrom the Google Pay API. Generatemerchant_referenceand save it with your order. - Create the payin:
POST /v2/payins/google-paywith the token in Base64. Saveid. -
If the webhook
payin.requires_actioncomes, send the payer tonext_action.redirect.url— the payment page. It runs 3DS, then sends the payer toreturn_urls. -
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 |
|---|---|---|
| Create | POST /v2/payins/google-pay |
createGooglePayPayin |
| 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 has its own section below. Reading returns the Payin object.
POST /v2/payins/google-pay
Creates a payin from the Google 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: "75.00". 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 |
google_pay |
object | yes | |
google_pay.payment_token |
string | yes | Base64 of paymentMethodData.tokenizationData.token. Protocol ECv2 |
return_urls |
object | yes | |
return_urls.success |
string | yes | https:// URL. The payer is sent there after completed |
return_urls.failure |
string | yes | https:// URL. The payer is sent there after failed |
curl -X POST https://payments-api.mrcr.io/v2/payins/google-pay \
-H "Authorization: Bearer $PAYIN_SHOP_KEY" \
-H "Content-Type: application/json" \
-d '{
"merchant_reference": "order-30001",
"amount": { "value": "75.00", "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" }
},
"google_pay": { "payment_token": "eyJzaWduYXR1cmUiOiLigKYiLCJpbnRlcm1lZGlh…" },
"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 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": "4f0d8c6e-1a5b-4c2f-9e7d-9b6a2c5f8eb4",
"object": "payin",
"merchant_reference": "order-30001",
"method_type": "payin_google_pay",
"status": "created",
"status_details": null,
"amount": { "value": "75.00", "currency": "EUR" },
"refunded_amount": { "value": "0.00", "currency": "EUR" },
"next_action": null,
"created_at": "2026-09-29T09:20:00Z",
"updated_at": "2026-09-29T09:20: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 |
Google 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.
Payer step: 3DS
When the card asks for 3DS, the payin goes to requires_action and the webhook
payin.requires_action comes with next_action.type: redirect:
-
Send the payer to
next_action.redirect.urlbeforenext_action.expires_at. Open it as a full page or a new window, not in an iframe. -
After the final status the payer is sent to
return_urls.successorreturn_urls.failure. Coming back is not a result — act on the webhook.
How to open the page in a browser or an app — Payer actions.
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 a Google 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_google_pay |
status |
string | yes | created, processing, requires_action, 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; it runs 3DS |
next_action.expires_at |
date-time | no | When the provider stops waiting for the payer; after it the payin ends failed |
google_pay |
object | no | Set after the token is decrypted |
google_pay.brand |
string | no | Card scheme: visa, mastercard |
google_pay.last4 |
string | no | Last four digits of the 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 | requires_action, completed, failed |
requires_action |
The payer has to pass 3DS | — | payin.requires_action |
Send the payer to next_action.redirect.url |
processing, 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 Google 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 Google 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, including a failed 3DS | 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.requires_action |
The card asks for 3DS | The Payin object, with next_action |
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
google_pay.payment_token: test_google_pay_token_visa, or a real token from Google Pay in its TEST
environment. The cents of amount.value select the outcome:
| Cents | Example | Outcome |
|---|---|---|
| any other | 75.00 |
completed |
.51 |
75.51 |
failed — provider_declined |
.52 |
75.52 |
failed — risk_rejected |
.54 |
75.54 |
failed — card_not_supported |
.55 |
75.55 |
failed — method_unavailable |
.56 |
75.56 |
failed — internal_error |
.57 |
75.57 |
requires_action, then completed after the payer step |
.58 |
75.58 |
requires_action, then failed — provider_declined |
.61 |
75.61 |
processing for 60 seconds, then completed |
Sandbox keys and fixtures — Sandbox.
Next: BLIK