Google Pay

Google Pay™ lets customers pay with the cards saved in their Google account - a fast, native checkout experience. The MoneyHash SDK provides the configuration needed to initialize the Google Pay button: you render the button, Google handles the authorization sheet, and the encrypted token is submitted to MoneyHash to complete the payment. This page covers the full integration on every platform - Web (JavaScript), Android, Flutter, and React Native.

How it works

Whatever the platform, Google Pay is the same three moves: get the Google Pay configuration from MoneyHash, get an encrypted token from Google, and submit that token back to MoneyHash. The difference is how much the SDK does for you:

  • Web - you render Google's button element and configure its payment request from nativePayData; when the customer authorizes, you submit the token with proceedWith + submitPaymentReceipt.
  • Android · Flutter · React Native - after selecting Google Pay on the intent, one call presents the sheet and submits the token for you (proceedWithGooglePay on Flutter/React Native, the GooglePayLauncher on Android) and resolves with the final intent details.

Four parties touch a Google Pay payment, and each holds exactly one piece. Your app owns the button and the checkout UI - nothing sensitive. Google Pay authenticates the customer and answers with an encrypted payment token; the tokenization spec (gateway + gatewayMerchantId) determines who can decrypt that token - the gatewayMerchantId is the merchant identifier at the gateway used in the encryption (your merchant id at the underlying provider such as Checkout, or your MoneyHash merchant id, depending on the connection) - so the real card number is never exposed to your code. MoneyHash supplies the configuration the button needs (nativePayData, straight from your Google Pay connection) and is the only party that can turn the token into money - it decrypts it server-side and routes the charge to your provider.

Step by step, a payment looks like this:

  1. Your backend creates a payment intent
  2. The SDK retrieves the Google Pay configuration via getMethods
  3. You render the Google Pay button using the returned nativePayData
  4. The customer selects a card in the Google Pay sheet and authorizes
  5. Google returns an encrypted payment token
  6. The token is submitted to MoneyHash - explicitly on the web (proceedWith + submitPaymentReceipt), automatically on mobile
  7. MoneyHash processes the payment and delivers webhooks

In sequence form - on the web, you drive the button and submit the token yourself:

On Android, Flutter, and React Native, the SDK presents the sheet and submits the token in one call:


Prerequisites

  • A Google Pay connection configured and completed in your MoneyHash dashboard - enabling Google Pay is a property of the connection, not of your MoneyHash account. The allowed networks, auth methods, and country the SDK returns in nativePayData all come from this connection.
  • The gateway merchant identifier - used as gatewayMerchantId in the Google Pay tokenization spec. This is the merchant id at the gateway that decrypts the token (e.g. your merchant id at the underlying provider such as Checkout, or your MoneyHash merchant id, depending on the connection). It is returned automatically in nativePayData - never hardcode it.
  • A registered Google Pay merchant ID
  • Web only: HTTPS enabled on your domain, and your domain registered in the Google Pay Console for production use

Dashboard configuration

Before integrating, configure Google Pay in the MoneyHash dashboard:

SettingWhat it does
Allowed card networksThe supported schemes - Visa, Mastercard, Amex, Discover, JCB, Mada, etc.
Allowed auth methodsPAN_ONLY for standard cards, CRYPTOGRAM_3DS for 3DS-enabled cards
Google Pay countryThe country where payments are processed
Underlying pay-in methodLinks Google Pay to the correct provider
Note

Everything configured here is returned automatically in nativePayData from the SDK - no hardcoding required.


Integration steps

The same five steps on every platform - pick your platform's tab in each step.

Step 1 - Set up Google Pay in your app

On the web, load Google's Pay JS and (optionally) the button web component. On mobile, set the Google Pay configuration when building the SDK - most importantly the environment: TEST during development, PRODUCTION before going live.

<script src="https://pay.google.com/gp/p/js/pay.js"></script>
<!-- or, as a component: npm install @google-pay/button-element -->
val moneyHash = MoneyHashSDKBuilder
    .setPublicKey("<YOUR_PUBLIC_API_KEY>")
    .apply {
        setNativeGooglePayConfig(NativeGooglePayConfig(
            environment = GooglePayEnvironment.TEST, // PRODUCTION before going live
        ))
    }
    .build()
final moneyHash = MoneyHashSDKBuilder()
    .setNativeGooglePayConfig(NativeGooglePayConfig(
      environment: GooglePayEnvironment.test, // production before going live
      collectibleBillingData: [],
    ))
    .build();
const moneyHash = MoneyHashSDKBuilder.setNativeGooglePayConfig({
  environment: GooglePayEnvironment.TEST, // PRODUCTION before going live
  collectibleBillingData: [],
}).build();

Step 2 - Check readiness

Only show the Google Pay button when the device can pay. On the web the button element handles this itself - it only renders when Google Pay is available. On mobile, ask the SDK - and remember every Google Pay API on Flutter / React Native is Android-only and throws on iOS.

// The <google-pay-button> element checks isReadyToPay for you and
// only renders when Google Pay is available on this device/browser.
// In your Composable — create and bind the launcher once:
val googlePayLauncher = rememberGooglePayLauncher(object : GooglePayResultCallback {
    override fun onResult(result: IntentDetails) { viewModel.onGooglePayResult(result) }
    override fun onError(throwable: Throwable) { viewModel.onGooglePayFailed(throwable) }
    override fun onCancel() { viewModel.onGooglePayCancelled() } // sheet dismissed
})
BindEffect(googlePayLauncher)

// Then, before showing the button:
val isReady = googlePayLauncher.isReady(
    allowedCardNetworks = googlePayData.allowedCardNetworks,
    allowedCardAuthMethods = googlePayData.allowedCardAuthMethods,
)
if (!Platform.isAndroid) return; // Google Pay APIs throw off-Android
final isReady = await moneyHash.isReadyForGooglePay();
if (!isReady) return; // hide the button
if (Platform.OS !== "android") return; // Google Pay APIs reject off-Android
const isReady = await moneyHash.isReadyForGooglePay();
if (!isReady) return; // hide the button

Step 3 - Get the Google Pay configuration

Retrieve the Google Pay configuration from the SDK. It is returned as nativePayData inside the matching express method - take every value from here, don't hardcode.

const { expressMethods } = await moneyHash.getMethods({
  currency: "<currency>",
  amount: "<amount>",
});

const googlePayMethod = expressMethods.find((method) => method.id === "GOOGLE_PAY");
const googlePayNativeData = googlePayMethod?.nativePayData;
val methods = moneyHash.getMethods(
    intentId = "<YOUR_INTENT_ID>",
    intentType = IntentType.Payment
)

val googlePayMethod = methods.expressMethods?.firstOrNull { it.id == "GOOGLE_PAY" }
val googlePayData = googlePayMethod?.nativePayData as? NativePayData.GooglePay
    ?: return // Google Pay not available on this flow
final methods = await moneyHash.getMethods(
  GetMethodsParams.withIntent("<YOUR_INTENT_ID>", IntentType.payment),
);

final googlePayMethod =
    methods.expressMethods?.firstWhere((m) => m.id == "GOOGLE_PAY");
final googlePayData = googlePayMethod?.nativePayData as GooglePayData?;
if (googlePayData == null) return; // Google Pay not available on this flow
const methods = await moneyHash.getMethods("<YOUR_INTENT_ID>", IntentType.Payment);

const googlePayMethod = methods.expressMethods?.find((m) => m.id === "GOOGLE_PAY");
const googlePayData = googlePayMethod?.nativePayData;
if (!googlePayData) return; // Google Pay not available on this flow

nativePayData reference (snake_case on the web, camelCase GooglePayData on mobile):

Web fieldMobile fieldDescription
gatewaygatewayAlways "moneyhash" - the gateway identifier for Google Pay tokenization
gateway_merchant_idgatewayMerchantIdThe merchant identifier at the gateway, used in the token encryption - your merchant id at the underlying provider (e.g. Checkout) or your MoneyHash merchant id, depending on the connection
merchant_idmerchantIdYour Google Pay merchant ID
merchant_namemerchantNameYour business name
amountamountPayment amount
currency_codecurrencyCodeISO 4217 currency code
country_codecountryCodeCountry code where the payment is processed
allowed_card_networksallowedCardNetworksSupported card networks - e.g. ["VISA", "MASTERCARD"]
allowed_card_auth_methodsallowedCardAuthMethodsAuth methods - ["PAN_ONLY", "CRYPTOGRAM_3DS"]
method_id-MoneyHash method identifier (web only)

Step 4 - Select Google Pay and configure the button

On the web, use nativePayData to build the button's payment request. On mobile, attach Google Pay to the intent with proceedWithMethod - the sheet comes in the next step.

import "@google-pay/button-element";

const button = document.querySelector("google-pay-button");

button.paymentRequest = {
  apiVersion: 2,
  apiVersionMinor: 0,
  allowedPaymentMethods: [{
    type: "CARD",
    parameters: {
      allowedAuthMethods: googlePayNativeData.allowed_card_auth_methods,
      allowedCardNetworks: googlePayNativeData.allowed_card_networks,
      billingAddressRequired: true,
    },
    tokenizationSpecification: {
      type: "PAYMENT_GATEWAY",
      parameters: {
        gateway: googlePayNativeData.gateway,
        gatewayMerchantId: googlePayNativeData.gateway_merchant_id,
      },
    },
  }],
  merchantInfo: {
    merchantId: googlePayNativeData.merchant_id,
    merchantName: googlePayNativeData.merchant_name,
  },
  transactionInfo: {
    totalPriceStatus: "FINAL",
    totalPriceLabel: "Total",
    totalPrice: `${googlePayNativeData.amount}`,
    currencyCode: googlePayNativeData.currency_code,
    countryCode: googlePayNativeData.country_code,
  },
  emailRequired: true,
};
moneyHash.proceedWithMethod(
    intentId = "<YOUR_INTENT_ID>",
    intentType = IntentType.Payment,
    selectedMethodId = "GOOGLE_PAY",
    methodType = MethodType.EXPRESS_METHOD,
    methodMetaData = null
)
await moneyHash.proceedWithMethod(
  "<YOUR_INTENT_ID>",
  IntentType.payment,
  "GOOGLE_PAY",
  MethodType.expressMethod,
  null, // methodMetaData
  null, // useWalletBalance
);
await moneyHash.proceedWithMethod(
  "<YOUR_INTENT_ID>",
  IntentType.Payment,
  "GOOGLE_PAY",
  MethodType.ExpressMethod
);
Note

Web: use environment="TEST" on the button during development and switch to environment="PRODUCTION" before going live. Your domain must be registered in the Google Pay Console for production use.

Step 5 - Complete the payment

On the web, handle the payment data event and submit the encrypted token yourself. On mobile, one call presents the sheet, collects the token, and submits it - you get the final intent details back.

button.addEventListener("loadpaymentdata", async (event) => {
  const { paymentMethodData, email } = event.detail;

  const googlePayReceipt = {
    receipt: paymentMethodData.tokenizationData.token,
    receiptBillingData: { email },
  };

  try {
    await moneyHash.proceedWith({
      type: "method",
      id: "GOOGLE_PAY",
      intentId,
    });

    const intentDetails = await moneyHash.submitPaymentReceipt({
      intentId,
      nativeReceiptData: googlePayReceipt,
    });

    // Handle success
  } catch (error) {
    // Handle error
  }
});

button.addEventListener("cancel", () => {
  // Customer closed the Google Pay sheet
});
// Presents the sheet, submits the token, and reports through the
// GooglePayResultCallback you registered in Step 2:
googlePayLauncher.presentForPaymentIntent(
    "<YOUR_INTENT_ID>",
    googlePayData.currencyCode.orEmpty(),
    googlePayData.countryCode.orEmpty(),
    googlePayData.amount ?: 0.0,
    googlePayData.gateway.orEmpty(),
    googlePayData.gatewayMerchantID.orEmpty(),
    googlePayData.merchantId.orEmpty(),
    googlePayData.merchantName.orEmpty(),
    googlePayData.allowedCardNetworks,
    googlePayData.allowedCardAuthMethods
)

// in the callback: onResult(result: IntentDetails) → render the state
// Presents the sheet, submits the token, and resolves with the result:
final intentDetails = await moneyHash.proceedWithGooglePay(
  "<YOUR_INTENT_ID>",
  googlePayData.currencyCode!,
  googlePayData.amount!,
  googlePayData.countryCode!,
  googlePayData.gateway!,
  googlePayData.gatewayMerchantId!,
  googlePayData.merchantId!,
  googlePayData.merchantName!,
  googlePayData.allowedCardNetworks,
  googlePayData.allowedCardAuthMethods,
);
// Presents the sheet, submits the token, and resolves with the result:
const intentDetails = await moneyHash.proceedWithGooglePay({
  intentId: "<YOUR_INTENT_ID>",
  currency: googlePayData.currencyCode!,
  amount: googlePayData.amount!,
  countryCode: googlePayData.countryCode!,
  gateway: googlePayData.gateway!,
  gatewayMerchantId: googlePayData.gatewayMerchantId!,
  merchantId: googlePayData.merchantId!,
  merchantName: googlePayData.merchantName!,
  allowedCardNetworks: googlePayData.allowedCardNetworks,
  allowedCardAuthMethods: googlePayData.allowedCardAuthMethods,
});
Warning

Web: always call proceedWith before submitPaymentReceipt - submitting the token without first selecting Google Pay on the intent will cause the payment to fail. Mobile: call proceedWithMethod (Step 4) before presenting the sheet; the SDK submits the token for you.

Note

Need the raw token on mobile? Every platform also exposes the two-step variant: generateGooglePayReceipt(...) presents the sheet and returns the receipt (on Android it's on the launcher, delivered through the callback's onResult(result: NativePayReceipt) overload), which you then pass to submitPaymentReceipt(intentId, receipt) yourself. Use it when you want to act between authorization and submission; otherwise prefer the one-call flow above.


Testing

Use environment="TEST" (GooglePayEnvironment.TEST on mobile) and Google's test card numbers during development:

NetworkTest card number
Visa4111111111111111
Mastercard5555555555554444
American Express378282246310005
Note

You can also use the MoneyHash interactive sandbox to test Google Pay end-to-end without a live provider connection.


Handling

  • Order matters once. The token can only be submitted after Google Pay is selected on the intent - explicit on the web, enforced by doing proceedWithMethod before presenting the sheet on mobile.
  • Tokens are single-use. Each sheet authorization produces one token for one attempt - re-run the flow for a retry.
  • Cancellation is not failure. Web: handle the button's cancel event. Mobile: the one-call flow reports a cancelled result - return the customer to the method list.
  • Take everything from nativePayData. Networks, auth methods, country, amounts, and both merchant ids come from your dashboard connection via getMethods - hardcoding them invites mismatches the sheet will reject.
  • Gate by platform. The button element self-gates on web; isReadyForGooglePay() / launcher.isReady() on mobile - and on Flutter / React Native every Google Pay API is Android-only.
  • Environment mismatch is the #1 test failure. A TEST button against a production-configured connection (or vice versa) fails opaquely - flip both together.
  • Card intelligence before submitting (web): binLookupByReceipt accepts Google Pay receipts too - run it between authorization and submitPaymentReceipt to branch on the card's brand, type, or issuer. The Android SDK has no receipt lookup - see SDK Architecture.
  • Webhooks are the source of truth. The SDK response tells the customer what happened; your backend should confirm on transaction.purchase.successful.

Did this page help you?