Accept Google Pay™
This guide will walk you through the process of accepting Google Pay payments on your website, while leveraging the Basis Theory platform to perform all necessary operations on PCI-sensitive data. First, let's take a look at how the Google Pay payment flow works.
First, you will render the Google Pay button on your page using Google's SDKs. When the user clicks the button, Google Pay will present the payment sheet to the user, which will contain the payment information (e.g., shipping details, transaction amount, card number, expiration date, etc.).
When the user clicks the Google "Pay" button, a sequence of three high-level steps is initiated:
- Creation of Google Pay payment data - The client application (Android or Web) creates a payment data request object that contains the payment information and sends that request to the Google Pay API. The Google Pay API returns a payment data response object, which contains the encrypted payment information.
- Decryption of the payment data - The encrypted payment data is then sent to Basis Theory for decryption and storage of the sensitive cardholder information. Then Basis Theory returns a Token Intent that can be used to process the payment.
- Processing of the payment data - The Basis Theory Token Intents are forwarded to the payment processor via Basis Theory Proxy, which translates Token Intents back to raw data before sending the request. When the successful payment response returns, the client application can complete the payment and inform the user that the payment was successful.
Getting Started
The first steps will be to set up your Basis Theory account and ensure you can use your Google account for making transactions. Then we'll get into rendering the Google Pay button on your web page.
Basis Theory Setup
To get started, you will need to create a Basis Theory Account and a TEST Tenant.
Google Pay Setup
To start, we want to ensure that you're able to connect to Google's APIs. Make sure you've met all the pre-requisites in Google's setup guide.
Rendering the Google Pay Button
The integration between your client application and Google can be done directly, without any involvement from Basis Theory.
We'll be building our integration using plain HTML and JS, but there are quickstarts and API documentation provided for React and other frameworks from Google. We'll be following Google's official guides for Web all the way until step 9.
<!DOCTYPE html>
<html>
<head>
<title>Google Pay Integration</title>
<script async src="https://pay.google.com/gp/p/js/pay.js" onload="onGooglePayLoaded()"></script>
<script>
let paymentsClient;
let baseRequest = {
apiVersion: 2,
apiVersionMinor: 0,
};
let tokenizationSpecification = {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: 'basistheory',
gatewayMerchantId: '<TENANT_ID>'
}
}
const allowedCardNetworks = ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"];
const allowedCardAuthMethods = ["PAN_ONLY", "CRYPTOGRAM_3DS"];
const baseCardPaymentMethod = {
type: 'CARD',
parameters: {
allowedAuthMethods: allowedCardAuthMethods,
allowedCardNetworks: allowedCardNetworks
}
};
// Initialize Google Pay API when the library is loaded
async function onGooglePayLoaded() {
paymentsClient = new google.payments.api.PaymentsClient({environment: 'TEST'});
const isReadyToPayRequest = Object.assign({}, baseRequest, {
allowedPaymentMethods: [baseCardPaymentMethod]
});
try {
// Check if the user is ready to pay
const response = await paymentsClient.isReadyToPay(isReadyToPayRequest);
if (response.result) {
createAndAddButton();
} else {
console.error('Google Pay is not available.');
}
} catch (error) {
console.error('Error checking readiness:', error);
}
}
// Create and add the Google Pay button
function createAndAddButton() {
const button = paymentsClient.createButton({
onClick: onGooglePaymentButtonClicked
});
document.getElementById('container').appendChild(button);
}
// Handle button click and load payment data
async function onGooglePaymentButtonClicked() {
const paymentDataRequest = Object.assign({}, baseRequest, {
allowedPaymentMethods: [
Object.assign({}, baseCardPaymentMethod, {
tokenizationSpecification: tokenizationSpecification
})
],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '10.00',
currencyCode: 'USD'
},
merchantInfo: {
merchantName: 'Example Merchant'
}
});
try {
const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest);
console.log('Payment data:', paymentData);
} catch (error) {
console.error('Error loading payment data:', error);
}
}
</script>
</head>
<body>
<h1>Google Pay Integration</h1>
<div id="container"></div>
</body>
</html>
Note that for the tokenizationSpecification
, we're setting gateway
to basistheory
and gatewayMerchantId
to your tenantId
created from the Basis Theory Setup.
Now when we run this code, we should see a Google Pay button on our page. Clicking the "GPay" button will present a payment sheet. Click on the "Continue" button and take a look at the console to see the encrypted payment data.
Storing the Google Pay Payment Data with Basis Theory
Now that we have access to the encrypted payment data, we'll create a Public Application and send the payment data to Basis Theory using the Google Pay connections endpoint in the onGooglePaymentButtonClicked
function.
Create a Public Application
You will need a Public Application for your frontend. Click here to create one using the Basis Theory Customer Portal.
This will create an application with the following Access Controls:
- Permissions:
token-intent:create
Tokenize the Google Pay Payment Data
Let's modify the onGooglePaymentButtonClicked
function and add a new tokenizeGooglePayTokenWithBasisTheory
to send the payment data to Basis Theory.
async function onGooglePaymentButtonClicked() {
const paymentDataRequest = Object.assign({}, baseRequest, {
allowedPaymentMethods: [
Object.assign({}, baseCardPaymentMethod, {
tokenizationSpecification: tokenizationSpecification
})
],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '10.00',
currencyCode: 'USD'
},
merchantInfo: {
merchantName: 'Example Merchant'
}
});
try {
const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest);
console.log('Payment data:', paymentData);
let encryptedPaymentToken = paymentData.paymentMethodData.tokenizationData.token;
let tokenIntent = await tokenizeGooglePayTokenWithBasisTheory(encryptedPaymentToken);
console.log(JSON.stringify(tokenIntent));
} catch (error) {
console.error('Error loading payment data:', error);
}
}
// Tokenize Google Pay data using Basis Theory
async function tokenizeGooglePayTokenWithBasisTheory(encryptedPaymentToken) {
const response = await fetch('https://api.basistheory.com/connections/google-pay/tokenize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'BT-API-KEY': '<PUBLIC_API_KEY>'
},
body: JSON.stringify({
google_payment_method_token: encryptedPaymentToken
})
});
let jsonResponse = await response.json();
return jsonResponse.token_intent;
}
The result of this call will be a Token Intent that you can use to process the payment. The type of Token Intent will either be card
or network_token
depending on the authMethod
of the Google Pay payment method that was available for the chosen card. The authMethod
is how the card was authenticated. The table below shows the differences between these two types.
Google Pay Payment Data authMethod | Token Intent Type | Details |
---|---|---|
CRYPTOGRAM_3DS | network_token | The payment method stored is a DPAN (Device Primary Account Number). This payment method is tied to the device for which it was added. This needs to be top of mind when charging with these payment methods in the future because we don't have insight into if these payment methods get revoked or updated. |
PAN_ONLY | card | The payment method stored is an FPAN (Funding Primary Account Number). This payment method is the underlying card itself and we have the capability to update the tokens tied to those payments with our Account Updater |
Remember that Token Intents are short-lived and expire after 24 hours by default. If you're wanting to use the payment data stored in a Token Intent for recurring transactions, for example, you can convert the Token Intent to a Token by calling the create Token endpoint.
Let's try clicking the "GPay" button again and then the "Continue" button once again. You should see a Token Intent printed to your console now.
Processing the Google Pay Payment Data
We'll send the Basis Theory Token Intent to a Payments Service Provider (PSP) to test charging a card. We'll be doing this from our backend by using a Proxy and a Private Application.
Create a Private Application
You will need a Private Application to allow your backend to call Basis Theory APIs. Click here to create one using the Basis Theory Customer Portal.
This will create an application with the following Access Controls:
- Permissions:
token:use
Charge a Card
Payment service providers (PSPs) may differ in how they handle this operation, offering workflows like "authorization and capture" or "direct charge."
We'll change our frontend application one last time to pass in the Token Intent Ids to the backend to process the payment.
async function onGooglePaymentButtonClicked() {
...
try {
const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest);
console.log('Payment data:', paymentData);
let encryptedPaymentToken = paymentData.paymentMethodData.tokenizationData.token;
let tokenIntent = await tokenizeGooglePayTokenWithBasisTheory(encryptedPaymentToken);
console.log(JSON.stringify(tokenIntent));
let { status } = await chargePayment(tokenIntent.id);
if (status === 201) {
console.log('Payment successful!');
} else {
console.error('Payment failed.');
}
} catch (error) {
console.error('Error loading payment data:', error);
}
}
// Charge Payment in backend
async function chargePayment(tokenIntentId) {
const response = await fetch('http://localhost:3000/charge-payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tokenIntentId: tokenIntentId
})
});
return await response.json();
}
We'll setup and invoke the ephemeral Proxy on our backend.
- Checkout
curl 'https://api.basistheory.com/proxy' \
-X 'POST' \
-H 'Content-Type: application/json' \
-H 'BT-API-KEY: <PRIVATE_API_KEY>' \
-H 'BT-PROXY-URL: https://api.sandbox.checkout.com/payments' \
-H 'Authorization: Bearer <CHECKOUT_AUTH_TOKEN>' \
-d '{
"source": {
"type": "network_token",
"token_type": "googlepay",
"token": "{{ token_intent: <TOKEN_INTENT_ID> | json: \"$.data.number\" }}",
"expiry_month": "{{ token_intent: <TOKEN_INTENT_ID> | json: \"$.data\" | card_exp: \"MM\" }}",
"expiry_year": "{{ token_intent: <TOKEN_INTENT_ID> | json: \"$.data\" | card_exp: \"YYYY\" }}"
},
"amount": 1000,
"currency": "USD",
"processing_channel_id": "<CHECKOUT_PROCESSING_CHANNEL_ID>"
}'
You may notice that in this example we are not passing in any 3DS cryptogram or eci indicator. This is not necessary for charging FPANs. If you need to charge a DPAN, you will need to pass in the 3DS cryptogram and eci indicator available on the authentication
property of the Token Intent.
{
"source": {
"type": "network_token",
"token_type": "googlepay",
"token": "{{ token_intent: <TOKEN_INTENT_ID> | json: \"$.data.number\" }}",
"expiry_month": "{{ token_intent: <TOKEN_INTENT_ID> | json: \"$.data\" | card_exp: \"MM\" }}",
"expiry_year": "{{ token_intent: <TOKEN_INTENT_ID> | json: \"$.data\" | card_exp: \"YYYY\" }}",
"eci": "{{ token_intent: <TOKEN_INTENT_ID> | json: \"$.data.authentication.eciIndicator\" }}",
"cryptogram": "{{ token_intent: <TOKEN_INTENT_ID> | json: \"$.data.authentication.threeds_cryptogram\" }}"
},
...
}
Preparing for Production
Once you're ready for production, you'll want to publish your integration with Google Pay by following the official guide. When you have your official merchantId
, you can update your merchantId
in the payment data request to Google Pay, and change the environment
to PRODUCTION
. Keep in mind that you'll need to use a Basis Theory PROD
Tenant to decrypt PRODUCTION
Google Pay payment data.
...
// Initialize Google Pay API when the library is loaded
async function onGooglePayLoaded() {
paymentsClient = new google.payments.api.PaymentsClient({environment: 'PRODUCTION'});
const isReadyToPayRequest = Object.assign({}, baseRequest, {
allowedPaymentMethods: [baseCardPaymentMethod]
});
...
// Handle button click and load payment data
async function onGooglePaymentButtonClicked() {
const paymentDataRequest = Object.assign({}, baseRequest, {
allowedPaymentMethods: [
Object.assign({}, baseCardPaymentMethod, {
tokenizationSpecification: tokenizationSpecification
})
],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '10.00',
currencyCode: 'USD'
},
merchantInfo: {
merchantName: 'Example Merchant',
merchantId: 'ABC123'
}
...
});
Now that we've updated the merchantId
and environment
in the payment data request, your integration with Google Pay is ready for production. We hope you found this guide helpful. If you have any questions or need further assistance, please don't hesitate to reach out to our support team.