Accept & send payments with Google Pay™
Google Pay is a fast way to move money with strong privacy and security. Use this guide to learn about Google's guidelines and follow a step by step process of adding a functional Google Pay button to accepting a payment or sending a payout.
Starting with Moov API version v2026.04.00, linking a Google Pay token returns every supported Google Pay payment method in a single response so you can choose the right flow for your use case:
| Payment method type | Directionality | Use case |
|---|---|---|
google-pay |
Payment | Accept a card payment from a payer using Google Pay |
push-to-google-pay |
Payout (OCT) | Send a near real-time disbursement to a recipient's card via Google Pay |
pull-from-google-pay |
Funding (AFT) | Pull funds from a debit or prepaid card on file in Google Pay for approved use cases |
v2026.04.00 and always returns an array of payment methods. Filter the array by paymentMethodType to pick the paymentMethodID that matches your flow (google-pay, push-to-google-pay, or pull-from-google-pay).
Prerequisites
Before getting started, make sure your website follows Google's guidelines and that your server is set up accordingly.
- Google Pay overview
- Google Pay website brand guidelines
- Google Pay integration checklist
- Google Pay prerequisites
- All pages that include Google Pay must be served over HTTPS
- Your domain must have a TLS domain-validated certificate
- You must adhere to the Google Pay API Acceptable Use Policy
- You must accept the Google Pay API Terms of Service
Add Google Pay JavaScript library
Include the Google Pay JavaScript library:
<!-- Load the Google Pay JavaScript library -->
<script
async src="https://pay.google.com/gp/p/js/pay.js"
onload="onGooglePayLoaded()">
</script>
Add Google Pay button
Add a div element to your webpage and use the Google Pay documentation to customize the button type and to see best practices on using CSS. For disbursements, we recommend using the checkout button type from Google.
Here's sample code for adding a simple black Buy with Google Pay button, using the default size and corner radius:
<div id="gpay-button-container"></div>
Set up Google Pay PaymentsClient
After adding the button, you can set up the PaymentsClient. Start with a base configuration and modify it to your needs.
In the configuration, indicate Google Pay's gateway integration with Moov by setting:
tokenizationSpecification.parameters.gatewayto"moov"tokenizationSpecification.parameters.gatewayMerchantIdto the merchant's Moov account ID (a UUID)merchantInfo.merchantIdto your 20-character Google Pay merchant IDmerchantInfo.merchantNameto your merchant display name from Google
Moov supports the PAN_ONLY and CRYPTOGRAM_3DS auth methods. Moov does not support 3DS from a third-party provider.
To accept card payments, list all card networks you intend to accept. Moov supports AMEX, DISCOVER, MASTERCARD, and VISA for Google Pay pay-ins.
// Environment: TEST or PRODUCTION
// Configure for production once your implementation testing is complete: environment: 'PRODUCTION'
const googlePayEnv = 'TEST';
// Base configuration for all Google Pay payment data requests
const googlePayBaseConfiguration = {
apiVersion: 2,
apiVersionMinor: 0,
allowedPaymentMethods: [
{
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: ['AMEX', 'DISCOVER', 'MASTERCARD', 'VISA'],
billingAddressRequired: true,
billingAddressParameters: {
format: 'FULL',
},
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: 'moov',
gatewayMerchantId: 'merchants-moov-account-id'
}
},
},
],
merchantInfo: {
merchantId: '01234567890123456789',
merchantName: 'Example Merchant',
},
emailRequired: true,
};
billingAddressRequired: true with format: 'FULL' may reduce declines and avoid interchange downgrades.
For disbursements (OCT), restrict allowedCardNetworks to networks that support push-to-card (MASTERCARD, VISA). Set existingPaymentMethodRequired: true when creating the button so that Google Pay only surfaces the button when the recipient has an eligible card on file.
// Environment: TEST or PRODUCTION
const googlePayEnv = 'TEST';
// Base configuration for Google Pay disbursement requests
const googlePayBaseConfiguration = {
apiVersion: 2,
apiVersionMinor: 0,
allowedPaymentMethods: [
{
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: ['MASTERCARD', 'VISA'],
billingAddressRequired: true,
billingAddressParameters: {
format: 'FULL',
},
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: 'moov',
gatewayMerchantId: 'merchants-moov-account-id'
}
},
},
],
merchantInfo: {
merchantId: '01234567890123456789',
merchantName: 'Example Merchant',
},
emailRequired: true,
};
AMEX and DISCOVER cards are not eligible for push-to-card transfers and must be excluded from allowedCardNetworks on disbursement flows.
Now create the PaymentsClient:
let googlePayClient = null;
// Create a new PaymentsClient instance
function getGooglePaymentsClient() {
if (googlePayClient === null) {
googlePayClient = new google.payments.api.PaymentsClient({
environment: googlePayEnv,
});
}
return googlePayClient;
}
Set up event handlers for page load and button click
Use isReadyToPay() to determine whether the customer is ready to pay, then render the Google Pay button:
let paymentRequest = null;
function onGooglePayLoaded() {
paymentRequest = Object.assign({}, googlePayBaseConfiguration);
getGooglePaymentsClient()
.isReadyToPay(paymentRequest)
.then((res) => {
if (res.result) {
const button = getGooglePaymentsClient().createButton({
onClick: onGooglePayButtonClick
});
document.getElementById('gpay-button-container').appendChild(button);
} else {
console.log("Google Pay is not ready for this user.");
}
})
.catch(console.error);
}
// Set the transactionInfo when the button is clicked and open the Google Pay sheet
function onGooglePayButtonClick() {
paymentRequest.transactionInfo = {
totalPriceStatus: 'FINAL',
totalPrice: '12.34',
currencyCode: 'USD',
countryCode: 'US',
};
googlePayClient.loadPaymentData(paymentRequest)
.then(onGooglePayPaymentLoaded)
.catch(console.error);
}
For disbursements, pass existingPaymentMethodRequired: true to isReadyToPay() so Google Pay only shows the button when the recipient has a saved card eligible for disbursements.
let paymentRequest = null;
function onGooglePayLoaded() {
paymentRequest = Object.assign({}, googlePayBaseConfiguration, {
existingPaymentMethodRequired: true
});
getGooglePaymentsClient()
.isReadyToPay(paymentRequest)
.then((res) => {
if (res.result) {
const button = getGooglePaymentsClient().createButton({
buttonType: 'checkout',
onClick: onGooglePayButtonClick
});
document.getElementById('gpay-button-container').appendChild(button);
} else {
console.log("Google Pay is not ready for this recipient.");
}
})
.catch(console.error);
}
// Set the transactionInfo for the disbursement and open the Google Pay sheet
function onGooglePayButtonClick() {
paymentRequest.transactionInfo = {
totalPriceStatus: 'FINAL',
totalPriceLabel: 'Payout',
totalPrice: '12.34',
currencyCode: 'USD',
countryCode: 'US',
};
googlePayClient.loadPaymentData(paymentRequest)
.then(onGooglePayPaymentLoaded)
.catch(console.error);
}
Moov only supports USD for the United States (US).
Link a Google Pay token
Once the customer selects their payment method, Google Pay returns a PaymentData response. Send the paymentMethodData property, unmodified, to Moov. Moov links the Google Pay token to the payer or recipient's Moov account using the Google Pay token POST endpoint.
The request requires two body fields:
merchantAccountID: The Moov account ID of the merchant accepting the payment or sending the payout. This must match thegatewayMerchantIdvalue passed to Google Pay.paymentMethodData: ThepaymentMethodDataproperty from Google Pay'sPaymentDataresponse, passed through unmodified.
The response is an array of payment methods — one per supported Google Pay flow that could be created for the linked token. Filter the array by paymentMethodType to pick the paymentMethodID you need for your transfer.
To accept a payment, use the google-pay payment method as the transfer source. Set the Moov payer account as the path parameter (accountID) and the merchant Moov account ID in the request body.
async function linkGooglePayPayInToken(paymentResponse) {
const url = `https://api.moov.io/accounts/${payerAccountID}/google-pay/tokens`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`,
'X-Moov-Version': 'v2026.04.00'
},
body: JSON.stringify({
merchantAccountID: 'merchants-moov-account-id',
paymentMethodData: paymentResponse.paymentMethodData
})
});
if (!response.ok) throw new Error('Failed to link Google Pay token');
const paymentMethods = await response.json();
return paymentMethods.find(
(pm) => pm.paymentMethodType === 'google-pay'
)?.paymentMethodID;
}
For disbursements, use the push-to-google-pay payment method (OCT) as the transfer destination. Set the Moov recipient account as the path parameter (accountID) and the merchant Moov account ID in the request body. For approved AFT use cases, pull-from-google-pay will also be present in the response.
async function linkGooglePayPayoutToken(paymentResponse) {
const url = `https://api.moov.io/accounts/${recipientAccountID}/google-pay/tokens`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`,
'X-Moov-Version': 'v2026.04.00'
},
body: JSON.stringify({
merchantAccountID: 'merchants-moov-account-id',
paymentMethodData: paymentResponse.paymentMethodData
})
});
if (!response.ok) throw new Error('Failed to link Google Pay token');
const paymentMethods = await response.json();
return paymentMethods.find(
(pm) => pm.paymentMethodType === 'push-to-google-pay'
)?.paymentMethodID;
}
/accounts/{accountID}/cards.write scope on the access token, where accountID is the Moov account ID of the payer (for pay-ins) or the recipient (for payouts). The payer or recipient account must be created before the token is requested.
Create a transfer & dismiss payment sheet
Use the paymentMethodID filtered from the link-token response as the transfer source (for pay-ins) or destination (for payouts) when calling Moov's create transfer POST endpoint.
Use the X-Wait-For header on the transfer request to receive a synchronous response so you can close out the payment sheet.
Google Pay pay-ins are created the same as other card payments — the google-pay payment method is the transfer source. To learn more about card payments with Moov, read our accept card payments use case guide.
async function onGooglePayPaymentLoaded(paymentResponse) {
try {
const paymentMethodID = await linkGooglePayPayInToken(paymentResponse);
if (!paymentMethodID) throw new Error('Unable to link Google Pay token');
// Pseudo-code: instruct your server to make the transfer
const moovTransferResponse = await fetch('/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentMethodID, amount: 12.34 })
}).then((r) => r.json());
if (moovTransferResponse.source.cardDetails.status === 'confirmed') {
// Success
} else {
// Decline / failure
}
} catch (error) {
console.error(error);
}
}
Google Pay payouts are created the same as other push to card transfers — the push-to-google-pay payment method is the transfer destination, and the source is a Moov wallet with sufficient funds. The source account must have the send-funds.push-to-card capability enabled for funds disbursement use cases.
async function onGooglePayPaymentLoaded(paymentResponse) {
try {
const paymentMethodID = await linkGooglePayPayoutToken(paymentResponse);
if (!paymentMethodID) throw new Error('Unable to link Google Pay token');
// Pseudo-code: instruct your server to make the payout
const moovTransferResponse = await fetch('/payout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentMethodID, amount: 12.34 })
}).then((r) => r.json());
if (moovTransferResponse.destination.cardDetails.status === 'completed') {
// Success
} else {
// Decline / failure
}
} catch (error) {
console.error(error);
}
}
push-to-google-pay transaction. Network velocity limits also apply — see the push to card guide for details.
Seeing it all together
Here's a sample that synthesizes the steps above all in one place. Switch tabs to see the full flow for either accepting a payment or sending a payout.
<div id="gpay-button-container"></div>
<script type="text/javascript">
//=============================================================================
// Configuration
//=============================================================================
// Environment: TEST or PRODUCTION
const googlePayEnv = 'TEST';
// Moov account ID of the payer (cardholder)
const payerAccountID = 'payer-uuid';
// Moov account ID of the merchant accepting the payment
const merchantAccountID = 'merchants-moov-account-id';
// Access token with the /accounts/{payerAccountID}/cards.write scope
const bearerToken = 'my token';
const googlePayBaseConfiguration = {
apiVersion: 2,
apiVersionMinor: 0,
allowedPaymentMethods: [
{
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: ['AMEX', 'DISCOVER', 'MASTERCARD', 'VISA'],
billingAddressRequired: true,
billingAddressParameters: { format: 'FULL' },
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: 'moov',
gatewayMerchantId: merchantAccountID
}
},
},
],
merchantInfo: {
merchantId: '01234567890123456789',
merchantName: 'Example Merchant',
},
emailRequired: true,
};
Object.freeze(googlePayBaseConfiguration);
//========================
// Google Payments Client
//========================
let googlePayClient = null;
function getGooglePaymentsClient() {
if (googlePayClient === null) {
googlePayClient = new google.payments.api.PaymentsClient({
environment: googlePayEnv,
});
}
return googlePayClient;
}
//================
// Event Handlers
//================
let paymentRequest = null;
function onGooglePayLoaded() {
paymentRequest = Object.assign({}, googlePayBaseConfiguration);
getGooglePaymentsClient()
.isReadyToPay(paymentRequest)
.then((res) => {
if (res.result) {
const button = getGooglePaymentsClient().createButton({
onClick: onGooglePayButtonClick
});
document.getElementById('gpay-button-container').appendChild(button);
} else {
console.log("Google Pay is not ready for this user.");
}
})
.catch(console.error);
}
function onGooglePayButtonClick() {
paymentRequest.transactionInfo = {
totalPriceStatus: 'FINAL',
totalPrice: '12.34',
currencyCode: 'USD',
countryCode: 'US',
};
googlePayClient.loadPaymentData(paymentRequest)
.then(onGooglePayPaymentLoaded)
.catch(console.error);
}
async function onGooglePayPaymentLoaded(paymentResponse) {
try {
// Link the Google Pay token to the payer's Moov account.
// The response is an array of payment methods; filter by `google-pay`.
const linkResponse = await fetch(
`https://api.moov.io/accounts/${payerAccountID}/google-pay/tokens`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`,
'X-Moov-Version': 'v2026.04.00'
},
body: JSON.stringify({
merchantAccountID,
paymentMethodData: paymentResponse.paymentMethodData
})
}
);
if (!linkResponse.ok) throw new Error('Failed to link Google Pay token');
const paymentMethods = await linkResponse.json();
const paymentMethodID = paymentMethods.find(
(pm) => pm.paymentMethodType === 'google-pay'
)?.paymentMethodID;
// Pseudo-code: instruct your server to make the transfer.
// X-Wait-For: rail-response is needed to receive a synchronous result.
const moovTransferResponse = await fetch('/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentMethodID, amount: 12.34 })
}).then((r) => r.json());
if (moovTransferResponse.source.cardDetails.status === 'confirmed') {
// Success
} else {
// Decline / failure
}
} catch (error) {
console.error(error);
}
}
</script>
<!-- Load the Google Pay JavaScript library -->
<script
async src="https://pay.google.com/gp/p/js/pay.js"
onload="onGooglePayLoaded()">
</script>
<div id="gpay-button-container"></div>
<script type="text/javascript">
//=============================================================================
// Configuration
//=============================================================================
const googlePayEnv = 'TEST';
// Moov account ID of the recipient (cardholder receiving the funds)
const recipientAccountID = 'recipient-uuid';
// Moov account ID of the merchant sending the payout
const merchantAccountID = 'merchants-moov-account-id';
// Access token with the /accounts/{recipientAccountID}/cards.write scope
const bearerToken = 'my token';
const googlePayBaseConfiguration = {
apiVersion: 2,
apiVersionMinor: 0,
allowedPaymentMethods: [
{
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: ['MASTERCARD', 'VISA'],
billingAddressRequired: true,
billingAddressParameters: { format: 'FULL' },
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: 'moov',
gatewayMerchantId: merchantAccountID
}
},
},
],
merchantInfo: {
merchantId: '01234567890123456789',
merchantName: 'Example Merchant',
},
emailRequired: true,
existingPaymentMethodRequired: true
};
Object.freeze(googlePayBaseConfiguration);
let googlePayClient = null;
function getGooglePaymentsClient() {
if (googlePayClient === null) {
googlePayClient = new google.payments.api.PaymentsClient({
environment: googlePayEnv,
});
}
return googlePayClient;
}
let paymentRequest = null;
function onGooglePayLoaded() {
paymentRequest = Object.assign({}, googlePayBaseConfiguration);
getGooglePaymentsClient()
.isReadyToPay(paymentRequest)
.then((res) => {
if (res.result) {
const button = getGooglePaymentsClient().createButton({
buttonType: 'checkout',
onClick: onGooglePayButtonClick
});
document.getElementById('gpay-button-container').appendChild(button);
} else {
console.log("Google Pay is not ready for this recipient.");
}
})
.catch(console.error);
}
function onGooglePayButtonClick() {
paymentRequest.transactionInfo = {
totalPriceStatus: 'FINAL',
totalPriceLabel: 'Payout',
totalPrice: '12.34',
currencyCode: 'USD',
countryCode: 'US',
};
googlePayClient.loadPaymentData(paymentRequest)
.then(onGooglePayPaymentLoaded)
.catch(console.error);
}
async function onGooglePayPaymentLoaded(paymentResponse) {
try {
// Link the Google Pay token to the recipient's Moov account.
// The response is an array of payment methods; filter by `push-to-google-pay`.
const linkResponse = await fetch(
`https://api.moov.io/accounts/${recipientAccountID}/google-pay/tokens`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`,
'X-Moov-Version': 'v2026.04.00'
},
body: JSON.stringify({
merchantAccountID,
paymentMethodData: paymentResponse.paymentMethodData
})
}
);
if (!linkResponse.ok) throw new Error('Failed to link Google Pay token');
const paymentMethods = await linkResponse.json();
const paymentMethodID = paymentMethods.find(
(pm) => pm.paymentMethodType === 'push-to-google-pay'
)?.paymentMethodID;
// Pseudo-code: instruct your server to make the payout.
// X-Wait-For: rail-response is needed to receive a synchronous result.
const moovTransferResponse = await fetch('/payout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentMethodID, amount: 12.34 })
}).then((r) => r.json());
if (moovTransferResponse.destination.cardDetails.status === 'completed') {
// Success
} else {
// Decline / failure
}
} catch (error) {
console.error(error);
}
}
</script>
<!-- Load the Google Pay JavaScript library -->
<script
async src="https://pay.google.com/gp/p/js/pay.js"
onload="onGooglePayLoaded()">
</script>
For more information, refer to Google's documentation and the Google Pay PaymentData response reference.