# 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](/api/sources/google-pay/token/) 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](#prerequisites)

Before getting started, make sure your website follows Google's guidelines and that your server is set up accordingly.

- [Google Pay overview](https://developers.google.com/pay/api/web/overview)
- [Google Pay website brand guidelines](https://developers.google.com/pay/api/web/guides/brand-guidelines)
- [Google Pay integration checklist](https://developers.google.com/pay/api/web/guides/test-and-deploy/integration-checklist)
- [Google Pay prerequisites](https://developers.google.com/pay/api/web/guides/setup)
  
  - 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](https://payments.developers.google.com/terms/aup)
  - You must accept the [Google Pay API Terms of Service](https://payments.developers.google.com/terms/sellertos)

## [Add Google Pay JavaScript library](#add-google-pay-javascript-library)

Include the Google Pay JavaScript library:

```html
<!-- 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-google-pay-button)

Add a `div` element to your webpage and use the [Google Pay documentation](https://developers.google.com/pay/api/web/guides/resources/customize) 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:

```html
<div id="gpay-button-container"></div>
```

## [Set up Google Pay PaymentsClient](#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.

[Accept payment](#tab-638247951-0-0) [Send payout](#tab-638247951-0-1)

To accept card payments, list all card networks you intend to accept. Moov supports `AMEX`, `DISCOVER`, `MASTERCARD`, and `VISA` for Google Pay pay-ins.

```javascript
// 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.

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.

```javascript
// 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:

```javascript
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](#set-up-event-handlers-for-page-load-and-button-click)

Use [`isReadyToPay()`](https://developers.google.com/pay/api/web/reference/client#isReadyToPay) to determine whether the customer is ready to pay, then render the Google Pay button:

[Accept payment](#tab-329167584-0-0) [Send payout](#tab-329167584-0-1)

```javascript
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.

```javascript
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](#link-a-google-pay-token)

Once the customer selects their payment method, Google Pay returns a [PaymentData](https://developers.google.com/pay/api/web/reference/response-objects#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](/api/sources/google-pay/token/).

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.

[Accept payment](#tab-729513648-0-0) [Send payout](#tab-729513648-0-1)

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.

```javascript
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.

```javascript
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;
}
```

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 &amp; dismiss payment sheet](#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](/api/money-movement/transfers/create/).

Use the `X-Wait-For` [header](/guides/money-movement/events-and-statuses/#transfer-responses) on the transfer request to receive a synchronous response so you can close out the payment sheet.

[Accept payment](#tab-982317546-0-0) [Send payout](#tab-982317546-0-1)

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-cases/card-acceptance/) use case guide.

```javascript
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](/guides/money-movement/send-payments/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](/guides/accounts/capabilities/enablement/) enabled for funds disbursement use cases.

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

Moov has a transfer limit of $50,000 per `push-to-google-pay` transaction. Network velocity limits also apply — see the [push to card guide](/guides/money-movement/send-payments/push-to-card/#transfer--velocity-limits) for details.

## [Seeing it all together](#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.

[Accept payment](#tab-831649572-4-0) [Send payout](#tab-831649572-4-1)

```html
<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>
```

```html
<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](https://developers.google.com/pay/api/web/guides/tutorial) and the [Google Pay PaymentData response reference](https://developers.google.com/pay/api/web/reference/response-objects#PaymentData).
