Google Pay™ Integration
Overview
This guide walks you through accepting Google Pay™ on your website or Android app with NetValve. You build the Google paymentDataRequest yourself, point it at NetValve as the gateway, and send the encrypted token Google returns to NetValve on a normal /sale request. NetValve decrypts the token server-side and routes the transaction to the acquirer.
For the operating model behind Google Pay at NetValve — including how NetValve's gateway registration with Google removes the need for you to manage any keys or certificates — read the Digital Wallets overview.
How NetValve handles certificates
NetValve is registered with Google as the Google Pay gateway netvalve. That means you do not need to:
- Obtain a Google Pay gateway certificate.
- Generate or rotate a public/private key pair for tokenization.
- Share any cryptographic material with NetValve.
- Ask NetValve for a Google merchant ID, merchant name, or origin — these come from your own Google Pay & Wallet Console.
You configure your front-end to use NetValve as the gateway, send the resulting encrypted token to NetValve in the Sale request, and NetValve decrypts it server-side with its registered processor key before routing to the acquirer.
Before you start
You need:
- A NetValve
siteIdornetvalveMidIdfor the environment you are integrating against (sandbox or production). This is the value you already send on/sale. - A Google Pay & Wallet Console account. It is free, and it is where you get your Google merchant ID and register your production domain. NetValve is not involved in this step.
Google's own reference material for each platform:
| Platform | Developer docs | Integration checklist | Brand guidelines |
|---|---|---|---|
| Web | Google Pay Web API | Web integration checklist | Web brand guidelines |
| Android | Google Pay Android API | Android integration checklist | Android brand guidelines |
Step 1 — Build the paymentDataRequest
The paymentDataRequest is the object you pass to Google's PaymentsClient.loadPaymentData(). You build it entirely on your side; there is no NetValve call to fetch it. Below is a complete, working example for the web:
const paymentDataRequest = {
apiVersion: 2,
apiVersionMinor: 0,
merchantInfo: {
// Your Google merchant ID from the Google Pay & Wallet Console (starts with BCR2).
// Ignored in TEST, required in PRODUCTION. NetValve does not issue this value.
merchantId: "BCR2DN4T...",
// Brand name shown on the Google Pay payment sheet.
merchantName: "Example Store"
},
allowedPaymentMethods: [
{
type: "CARD",
parameters: {
allowedAuthMethods: ["PAN_ONLY", "CRYPTOGRAM_3DS"],
allowedCardNetworks: ["AMEX", "DISCOVER", "MASTERCARD", "VISA"]
},
tokenizationSpecification: {
type: "PAYMENT_GATEWAY",
parameters: {
gateway: "netvalve",
// The siteId or netvalveMidId you will send on /sale. Must be identical.
gatewayMerchantId: "<your siteId or netvalveMidId>"
}
}
}
],
transactionInfo: {
// Your country of registration, ISO 3166-1 alpha-2.
countryCode: "US",
currencyCode: "USD",
totalPriceStatus: "FINAL",
totalPrice: "999.00"
}
};
const paymentsClient = new google.payments.api.PaymentsClient({
environment: "TEST" // "PRODUCTION" when live
});
paymentsClient.loadPaymentData(paymentDataRequest).then((paymentData) => {
// Google returns the encrypted token as a JSON string.
const googlePayToken = JSON.parse(paymentData.paymentMethodData.tokenizationData.token);
// Send googlePayToken to your backend, then to NetValve as googlePaySSL (Step 2).
});
Field reference
| Field | Value | Where it comes from |
|---|---|---|
environment (on PaymentsClient) | TEST in sandbox, PRODUCTION in production | You |
merchantInfo.merchantId | Your Google merchant ID (BCR2…). Ignored in TEST, required in PRODUCTION. | Google Pay & Wallet Console — not issued by NetValve |
merchantInfo.merchantName | Brand name shown on the payment sheet | You |
transactionInfo.countryCode | Your country of registration (ISO 3166-1 alpha-2) | You |
transactionInfo.currencyCode | ISO 4217 currency of the sale — same value as currency on /sale | You |
transactionInfo.totalPrice | Amount as a string with two decimals — same value as amount on /sale | You |
transactionInfo.totalPriceStatus | FINAL | Fixed |
allowedAuthMethods | ["PAN_ONLY", "CRYPTOGRAM_3DS"] | Fixed — both are supported by NetValve |
allowedCardNetworks | ["AMEX", "DISCOVER", "MASTERCARD", "VISA"] | Fixed — the networks NetValve accepts through Google Pay |
tokenizationSpecification.parameters.gateway | netvalve (lowercase) | Fixed |
tokenizationSpecification.parameters.gatewayMerchantId | Your NetValve siteId or netvalveMidId | NetValve — the same value you send on /sale |
gatewayMerchantId must match the /sale requestGoogle encrypts the token for the gatewayMerchantId you supply. NetValve then decrypts it against the siteId or netvalveMidId on the /sale request. If the two are not identical the sale fails with GTW_2096. Do not use your Google merchant ID, your NetValve client ID, or any other identifier here.
The same structure applies on Android. Pass the identical tokenizationSpecification, allowedAuthMethods, allowedCardNetworks and transactionInfo values to PaymentsClient.loadPaymentData() in your app; only the request-building code differs. See Google's Android tutorial.
Step 2 — Send the token to NetValve
Take the parsed token from paymentMethodData.tokenizationData.token and submit it as googlePaySSL on a standard Sale request, with paymentType: WALLET and walletType: GOOGLE_PAY. Everything else on the request is the same as a card sale.
{
"amount":999.00,
"cardHolderName":"John Doe",
"clientOrderId":{{clientOrderId}},
"currency":"USD",
"customerAddress":"123 Main St",
"customerCity":"New York",
"customerCountryCode":"US",
"customerEmail":"docs@netvalve.com",
"customerIp":"203.0.113.1",
"customerName":"John",
"customerLastName":"Doe",
"customerPhone":"+12025551234",
"customerState":"NY",
"customerZipCode":"10001",
"netvalveMidId":"{{netvalveMidId}}",
"paymentType":"WALLET",
"walletType":"GOOGLE_PAY",
"googlePaySSL":{
"signature":"MEUCIQDk+/hOm5zJmWrnmrG+ds+N5L28gMeE0drnDyjyvCnX5wIgAloe3rwAPu7tMwawefcGYO4G6m/+ilQo1DqnphVIJ/s=",
"intermediateSigningKey":{
"signedKey":"{\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0NSQX6HDOtKu3B2TI9HJV3IjiRQdebbCcDR9lVkx51TeIZ/J8lGDFcpqZTkNED1g5QXgTqq5ALpG+d43cQRaQA\\u003d\\u003d\",\"keyExpiration\":\"1715897257675\"}",
"signatures":[
"MEYCIQCUGkX7SGY6WG94ZHuKfyQpHG6o2KbL1gYmB8aZPRebmwIhAMH61R36CiPbPhY3oohvt6pw3ZQJkYDWFDB6SHpFABTA"
]
},
"protocolVersion":"ECv2",
"signedMessage":"{\"encryptedMessage\":\"Syk7ZTw7KzXS6rEisGPZlHTEgpIlH5Z5HJktPKcYHrPWcVh+d4l1MroajG5NQPEOgYxlZV9A4XXq2ZdM/2H/wge9aSCDzY9iY8m2JCMK1SoZVrnD5+dy66iG93VFuAdUjf+ZiddsWDRR1cLyyz/IudsNKaIKKzJNFHk+pCFktklMhvSSaGoDJbKxMqEDuipk6E+R2sAEkBBuKD8zdlzn1xheLnv+MMULUqe7XJTqcCiAJajqMBTFpnyQX3gxd28QwFKu2FX2mYsviR7VsxPnETlSvnabHpcxkLv8EL0mqkGM5hicmjfjEbEKeb2ZXkULBTxipfeHf5eSXo+MyCoo8W5Q+8SvwE9x22KaxJy0A3vPAfgQOK568y+riM+yfP7xt5lWCrCr0rKhepeT+JDEZHdT3gUMlMLdmtzlyT7r6ddtftMkTYzq5BneBFHqYl7uryRh9lFsjSrDjoZQjUGzASPQX+s3ty593Y/1t88fXCNV2xf3PHkpMFq9ZaTKwv1XyHs0uXgawVr2+DVnXcqxfteM5GvpxN42EDlz\",\"ephemeralPublicKey\":\"BOmikxGNXx91S5MftOqKFwqbpiGUr8HfN0zAry5iN4T9LtwtklyG7vS1BsU2YJFaggu5GmuLhA9vFgfvKIPnNUI\\u003d\",\"tag\":\"kJSe9C2RkVCpGt5WCzIZgYnBbywF+pc5LdlUzo43NZw\\u003d\"}"
}
}
netvalveMidId(orsiteId) must be the same value you used asgatewayMerchantIdin Step 1.amountandcurrencyshould matchtransactionInfo.totalPriceandtransactionInfo.currencyCode.- A Google Pay token is single-use. Request a new one for every sale attempt.
If you decrypt the Google Pay token on your own backend, you can skip googlePaySSL and send the decrypted card-network fields directly in a tokenizedDTO object instead. See Submitting a decrypted payload.
Example response
A successful sale returns responseCode: GTW_1000:
{
"traceID": "b126345b-6199-4635-9bf2-f9aadbdd4d58",
"responseTimestamp": "2024-11-08T12:57:41.771+00:00",
"transactionID": 18749,
"responseCode": "GTW_1000",
"responseMessage": "Transaction Approved/ Request Successful.",
"responseCodeType": "APPROVED",
"paymentMethod": "GOOGLE_PAY",
"cardNumber": "411111******1111",
"cardType": "VISA",
"bankTransactionId": "431312750166",
"authCode": "TAS213",
"midId": 2,
"netvalveMidId": "289e253d-f955-4e29-a2c7-bb1805883ee0"
}
Step 3 — Going live
NetValve needs nothing from you to go live with Google Pay. All production setup happens in your own Google Pay & Wallet Console:
- Register your production domain in the Google Pay & Wallet Console and get it approved. Google will not show the payment sheet in
PRODUCTIONon an unregistered origin. - Complete Google's integration checklist for your platform (web / Android) and request production access from the console.
- Switch the request to production: set
environment: "PRODUCTION"onPaymentsClient, and setmerchantInfo.merchantIdto yourBCR2…merchant ID. It is required in production. - Use your production NetValve identifiers:
gatewayMerchantIdand the/salesiteId/netvalveMidIdmust both be the production values, and/salemust go to the production API base URL. Sandbox and production are separate NetValve environments with separate MIDs. - Do not send line items in production. Leave
transactionInfo.displayItemsandtotalPriceLabelout of the request. Line items in a production request triggerOR_BIBED_06on Google's payment sheet.
Google Pay must also be enabled on your NetValve MID. If it is not yet enabled, ask your NetValve representative — this is the only NetValve-side setting involved.
Errors
| Code | Where you see it | Meaning | Fix |
|---|---|---|---|
GTW_2096 | NetValve /sale response | NetValve could not decrypt the Google Pay token. Almost always a gatewayMerchantId mismatch: the token was encrypted for a different siteId / netvalveMidId than the one on the /sale request. Also returned for a reused, expired, or malformed token. | Make gatewayMerchantId identical to the /sale siteId / netvalveMidId. Check gateway is lowercase netvalve. Request a fresh token for each attempt and send the parsed JSON object, not the raw string. |
OR_BIBED_06 | Google's payment sheet, before any NetValve call | Google rejected the request because of a merchant-profile or line-item problem. Common causes: displayItems present in a PRODUCTION request, merchantInfo.merchantId missing or not matching your console, or the origin is not registered/approved in the Google Pay & Wallet Console. | Remove displayItems and totalPriceLabel. Verify merchantId and your registered domain in the Google Pay & Wallet Console. This error is on the Google side; NetValve cannot see or change it. |
Standard NetValve response codes (declines, validation errors, routing) apply to Google Pay sales the same way they do to card sales. See Error codes.
Submitting a decrypted payload (tokenizedDTO)
The googlePaySSL flow above hands NetValve the encrypted Google Pay token and lets NetValve decrypt it server-side. If you'd rather decrypt the token yourself and pass NetValve the resulting card-network data, send a tokenizedDTO object in place of googlePaySSL. Everything else about the Sale request is identical — same endpoint, same paymentType: WALLET, same walletType: GOOGLE_PAY.
This is useful when you already decrypt the Google Pay token on your backend (for example because you hold your own gateway/tokenization keys) and want a single integration path across processors.
Send either googlePaySSL or tokenizedDTO — not both. If tokenizedDTO is present, NetValve treats the wallet payload as already decrypted and does not attempt to decrypt anything.
Two authentication methods
Unlike Apple Pay, Google Pay decrypts into one of two authentication methods, and the fields you send differ between them. Set authMethod accordingly:
authMethod | What cardNumber holds | cryptogram / eciIndicator |
|---|---|---|
CRYPTOGRAM_3DS | A network token (DPAN) | Required — the online cryptogram and ECI from the decrypted token. |
PAN_ONLY | The real card number (FPAN) | Omit — there is no cryptogram or ECI for this method. |
Sending the correct authMethod matters: some acquirers route PAN_ONLY transactions differently, and a PAN_ONLY payload that carries a cryptogram (or vice versa) may be rejected.
Where the fields come from
After you decrypt the Google Pay token, the paymentMethodDetails object looks like this (Google's fields shown for reference):
{
"paymentMethodDetails": {
"authMethod": "CRYPTOGRAM_3DS",
"pan": "4831961250220480",
"expirationMonth": 12,
"expirationYear": 2028,
"cryptogram": "Az2Uq7EABOvUpHEqLyFuMAACAAA=",
"eciIndicator": "05"
}
}
Map those into tokenizedDTO as follows:
tokenizedDTO field | Source in the decrypted token | Notes |
|---|---|---|
cardNumber | pan | Network token (DPAN) for CRYPTOGRAM_3DS; the real card number (FPAN) for PAN_ONLY. |
cardExpiryMonth | expirationMonth | Already a discrete month (MM) — no parsing needed, unlike Apple's combined date. |
cardExpiryYear | expirationYear | Already YYYY. |
cryptogram | cryptogram | CRYPTOGRAM_3DS only. Passed to the acquirer as the online cryptogram (AAV/TAVV) for Visa and Mastercard. Omit for PAN_ONLY. |
eciIndicator | eciIndicator | CRYPTOGRAM_3DS only. Omit for PAN_ONLY. |
authMethod | authMethod | CRYPTOGRAM_3DS or PAN_ONLY. |
Example — CRYPTOGRAM_3DS
{
"amount": 11.00,
"clientOrderId": {{clientOrderId}},
"currency": "USD",
"customerName": "John",
"customerLastName": "Doe",
"customerEmail": "docs@netvalve.com",
"netvalveMidId": "{{netvalveMidId}}",
"paymentType": "WALLET",
"walletType": "GOOGLE_PAY",
"tokenizedDTO": {
"cardExpiryMonth": "12",
"cardExpiryYear": "2028",
"cardNumber": "4831961250220480",
"authMethod": "CRYPTOGRAM_3DS",
"cryptogram": "Az2Uq7EABOvUpHEqLyFuMAACAAA=",
"eciIndicator": "05"
}
}
Example — PAN_ONLY
No cryptogram or ECI, and cardNumber is the real card number:
{
"amount": 11.00,
"clientOrderId": {{clientOrderId}},
"currency": "USD",
"customerName": "John",
"customerLastName": "Doe",
"customerEmail": "docs@netvalve.com",
"netvalveMidId": "{{netvalveMidId}}",
"paymentType": "WALLET",
"walletType": "GOOGLE_PAY",
"tokenizedDTO": {
"cardExpiryMonth": "12",
"cardExpiryYear": "2028",
"cardNumber": "4012000098765439",
"authMethod": "PAN_ONLY"
}
}
The same tokenizedDTO shape is used for Apple Pay and Samsung Pay — only walletType (and, for Apple, the always-present cryptogram) changes. See the Apple Pay guide and the Digital Wallets overview.
Card Parameters
Google Pay™ transactions do not require a billing address. Therefore, no billing address parameters need to be provided with a card.
Supported Card Authentication Methods
Google Pay™ supports the following card authentication methods:
- PAN_ONLY;
- CRYPTOGRAM_3DS.
Supported Card Networks
Google Pay™ supports transactions with cards from the following networks:
- AMEX;
- DISCOVER;
- MASTERCARD;
- VISA.
Enable Google Pay on Hosted Payment Page
If you use NetValve's Hosted Payment Page, Google Pay can be enabled per MID from the Backoffice — no keys or certificates to upload. The HPP uses NetValve's registered gateway with Google, so once Google Pay is enabled on your MID the button will appear automatically on supported devices.
Enable Google Pay on the Checkout SDK
If you use the Checkout SDK, the SDK loads Google's pay.js, builds the paymentDataRequest with the right gateway parameters and submits the Sale for you, so you skip the steps above. Once GOOGLEPAY is enabled on your site and your CSP allows https://pay.google.com, the button appears on supported devices. See Apple Pay and Google Pay in the SDK guide.
Testing
Google Pay™ is available in the NetValve sandbox. Use environment: "TEST" on PaymentsClient and your sandbox siteId / netvalveMidId as gatewayMerchantId. In TEST, merchantInfo.merchantId is ignored and no domain registration is needed, so you can test from localhost or any staging host.
To pay with Google's test cards on the sheet, join Google's test card group. Once you are a member, test cards appear automatically in the TEST payment sheet for your Google account.
Generate a test token without writing front-end code
If you want to exercise /sale before your front-end is ready, Google's token generator produces a real TEST token you can paste into the request.

Fill the fields following these guidelines:
- Gateway ID:
netvalve; - Gateway Merchant ID: your sandbox
siteIdornetvalveMidId— the same value you will send on/sale; - Join the test card group so Google's test cards are available on the sheet;
- Click the Apply button, then go to the next section and click the Buy with G Pay button:

- Choose the payment method and click the Continue button:

- As a response, Google will provide the
PaymentDataitem. There, in thepaymentMethodData.tokenizationData.tokenfield, you can find the encrypted Google Pay Token (a string of characters).
{
"signature": "MEUCIQDY3wBQyHB4sZcktRoJXKxm+OLcjHzCvdDeGn23oX0kkwIgKznRFZZL+sDMv1b5cuD+YurXMZraYBsr9hbravVY5Ro=",
"protocolVersion": "ECv1",
"signedMessage": "{\"encryptedMessage\":\"cI87tLqzqTGyCFnMMCVWcTHw3xhYIK+CEnuQ74K+nlLpCgOlfpScib9jds4sxDtN6CunCqCSMfd/3yHeeRy6aCx1yyqcT4ey6NueeBznprJpkmVVgI1JHWLQt4hzAXMUAcYASYLOabKP9fUZvHkOBDytD531jpzNXa+Spc/zrpGzFKx2C4VU9sC95q9i+ey+kr7ZMNVCOFJPWXu7lKZ105IOOqozJ6/70MKmxP3jM89eeq+/19QnyHjQLXfnQPvQjiUJKGCcRKDLlrb3XoY5ZUUzGfN5eZCLzCVg0hWEbwU+6J7KWYJyW+Wr1r8bagN9zWsrMKhDpsQbHfyzb+yBzFUoxeUgL4a7FeVvEllIcHtqsvTCf6FENV20aF5VLDv5qzUkV+PzTAIbFEuabA0God9UbVCVVv7nM8QFzvRPhzYYFVFTn4JHvL2qZ4pAR9lE+w\\u003d\\u003d\",\"ephemeralPublicKey\":\"BPHLC4sBHpenY1M0ixmiDMuWJTaTJOqggRUwtgBJMcBp28VsxHD7zPI7985x4F5EjMP5y8j/cuUzbe/cGPjOKGk\\u003d\",\"tag\":\"RaXrPOUuc5iw3oxDa0C2MOjaKxgxIRQvwOspmtFV0zU\\u003d\"}"
}
Copy the token and insert it as the googlePaySSL object in your /sale request (see Step 2):
