# Accept & send payments with Google Pay™

> Add a Google Pay button to your website with the Google Pay API, link Google Pay tokens, and use them to accept card payments or send payouts.

> AI agents: search and read all Moov docs with the docs MCP server at https://docs.moov.io/mcp (no API key). Setup: https://docs.moov.io/guides/developer-tools/mcp-ai/docs-mcp/index.md

Add a Google Pay button to your website with the Google Pay API, then use the linked Google Pay token to accept a card payment or send 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 |

> **Tip:** The link Google Pay token [endpoint](/api/sources/google-pay/token/index.md) 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

- **Capabilities:** see [payment methods and capabilities](/guides/money-movement/payment-methods/index.md#payment-methods-and-capabilities). Pay-ins need `collect-funds.card-payments` on the merchant; payouts need `send-funds.push-to-card` on the sender. Both need `wallet.balance`.
- **Google Pay merchant ID:** your 20-character merchant ID and merchant name from Google, used in `merchantInfo` (step 3 below).
- **Access token scope:** `/accounts/{accountID}/cards.write` for the payer (pay-ins) or recipient (payouts). That account must exist before you request the token.
- **API version:** `v2026.04.00` or later, where the link Google Pay token endpoint was introduced.
- **Website:** every page with Google Pay is served over HTTPS with a TLS domain-validated certificate, and you've accepted the [Google Pay API Terms of Service](https://payments.developers.google.com/terms/sellertos) and follow the [Acceptable Use Policy](https://payments.developers.google.com/terms/aup). Google's [setup guide](https://developers.google.com/pay/api/web/guides/setup), [brand guidelines](https://developers.google.com/pay/api/web/guides/brand-guidelines), and [integration checklist](https://developers.google.com/pay/api/web/guides/test-and-deploy/integration-checklist) cover the rest.

## 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 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

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**

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

> **Tip:** Including `billingAddressRequired: true` with `format: 'FULL'` may reduce declines and avoid interchange downgrades.

**Send payout**

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

> **Tip:**
> 
> `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

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**

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

**Send payout**

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

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/index.md).

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**

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.07.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;
}
```

**Send payout**

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.07.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;
}
```

> **Note:** 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](/api/money-movement/transfers/create/index.md).

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

**Accept payment**

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](/guides/accept-payments/cards/online/index.md) 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);
  }
}
```

**Send payout**

Use the `push-to-google-pay` payment method as the transfer `destination`. Otherwise, Google Pay payouts follow the same source and capability requirements as any [push to card](/guides/send-payments/push-to-card/index.md) transfer.

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

> **Tip:** 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/send-payments/push-to-card/index.md#velocity-limits) for details.

A pay-in is authorized when `source.cardDetails.status` is `confirmed`; a payout is delivered when `destination.cardDetails.status` is `completed`. Subscribe to the `transfer.updated` webhook to track the transfer afterward. See [card transfer statuses](/guides/accept-payments/cards/timing/index.md#card-transfer-statuses) and, to undo a payment, [reversals](/guides/accept-payments/cards/reversals/index.md).

## Full example

The tabs below combine the steps above into one Google Pay sample, for accepting a payment or sending a payout.

**Accept payment**

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

**Send payout**

```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.07.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).

