Accept & send payments with Google Pay™

Moov allows you to accept card payments and send payouts with Google Pay. This step by step guide covers how you can start using Google Pay on the web using the open standard Google Pay API.

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
The link Google Pay token endpoint was introduced in API version 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.

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.gateway to "moov"
  • tokenizationSpecification.parameters.gatewayMerchantId to the merchant's Moov account ID (a UUID)
  • merchantInfo.merchantId to your 20-character Google Pay merchant ID
  • merchantInfo.merchantName to 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,
};
Including billingAddressRequired: true with format: 'FULL' may reduce declines and avoid interchange downgrades.

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);
}

Moov only supports USD for the United States (US).

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 the gatewayMerchantId value passed to Google Pay.
  • paymentMethodData: The paymentMethodData property from Google Pay's PaymentData response, 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;
}
The link Google Pay token endpoint requires the /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);
  }
}

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>

For more information, refer to Google's documentation and the Google Pay PaymentData response reference.