Google Pay Integration
Google Pay enables fast, simple checkout with payment information stored in customers' Google accounts. By integrating Google Pay through Onerway, you can offer a seamless payment experience to hundreds of millions of Google users worldwide.
Integration Overview
Google Pay integration with Onerway involves three main phases:
- Configuration Retrieval → Query Onerway API for Google Pay settings
- Google Pay Initialization → Set up SDK and display payment button
- Payment Processing → Handle payment tokens and process through Onerway
Prerequisites
Before implementing Google Pay integration, ensure you have:
- HTTPS Website: Google Pay requires secure HTTPS connection
- Supported Browser: Chrome, Firefox, Safari, Edge, Opera, or UC Browser
- Onerway Merchant Account: Active account with Google Pay enabled
- Google Account: For testing, add a payment method to your Google account
- Backend Integration: Server capability to call Onerway APIs
Google Pay Benefits
- One-Touch Checkout: Users authenticate with biometrics or device PIN
- Pre-Filled Information: Payment details, billing, and shipping automatically populated
- Wide Adoption: Available to hundreds of millions of Google users
- Secure Processing: Tokenized payment data protects sensitive information
- Mobile Optimized: Seamless experience across web and mobile platforms
Integration Flow
Getting Onerway Configuration
Critical First Step
Before configuring Google Pay, you MUST call Onerway's Payment Methods Query API to obtain the required configuration parameters.
Why Query Payment Methods?
The Payment Methods Query API provides essential Google Pay configuration:
gateway- Gateway identifier for payment tokenizationgatewayMerchantId- Merchant ID for your payment gatewayallowedCardNetworks- Supported card networks for your merchant account
Query API Request
{
"appId": "1727880846378401792",
"country": "US", // Customer country
"merchantNo": "800209", // Your merchant number
"orderAmount": "29.99",
"orderCurrency": "USD",
"paymentMode": "WEB", // WEB, APP, or WAP
"sign": "..."
}2
3
4
5
6
7
8
9
{
"respCode": "20000",
"respMsg": "Success",
"data": [
{
"productType": "LPMS",
"paymentMethod": "GooglePay",
"countryCode": "US",
"gatewayName": "ronghan", // Use as gateway
"gatewayMerchantId": "BCR2DN6TY7DM5TDU", // Use as gatewayMerchantId
"merchantId": "800096",
"subCardTypes": ["MASTERCARD", "VISA"] // Use as allowedCardNetworks
}
]
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
See Payment Methods Query API for complete documentation.
Extract Configuration
After receiving the response, extract Google Pay configuration:
async function getOnerwayGooglePayConfig() {
const response = await fetch('/api/payment-methods', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
country: 'US',
merchantNo: '800209',
orderAmount: '29.99',
orderCurrency: 'USD',
paymentMode: 'WEB'
})
});
const result = await response.json();
// Find Google Pay configuration
const googlePay = result.data.find(
method => method.paymentMethod === 'GooglePay'
);
if (!googlePay) {
throw new Error('Google Pay not available');
}
return {
gateway: googlePay.gatewayName, // "ronghan"
gatewayMerchantId: googlePay.gatewayMerchantId, // "BCR2DN6TY7DM5TDU"
allowedCardNetworks: googlePay.subCardTypes // ["MASTERCARD", "VISA"]
};
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// In setup function
const getOnerwayGooglePayConfig = async () => {
const response = await fetch('/api/payment-methods', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
country: 'US',
merchantNo: '800209',
orderAmount: orderAmount.value,
orderCurrency: 'USD',
paymentMode: 'WEB'
})
});
const result = await response.json();
const googlePay = result.data.find(
method => method.paymentMethod === 'GooglePay'
);
if (!googlePay) {
throw new Error('Google Pay not available');
}
return {
gateway: googlePay.gatewayName, // [!code focus]
gatewayMerchantId: googlePay.gatewayMerchantId, // [!code focus]
allowedCardNetworks: googlePay.subCardTypes // [!code focus]
};
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const getOnerwayGooglePayConfig = async () => {
const response = await fetch('/api/payment-methods', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
country: 'US',
merchantNo: '800209',
orderAmount: '29.99',
orderCurrency: 'USD',
paymentMode: 'WEB'
})
});
const result = await response.json();
const googlePay = result.data.find(
method => method.paymentMethod === 'GooglePay'
);
if (!googlePay) {
throw new Error('Google Pay not available');
}
return {
gateway: googlePay.gatewayName,
gatewayMerchantId: googlePay.gatewayMerchantId,
allowedCardNetworks: googlePay.subCardTypes
};
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Loading Google Pay SDK
Add the Google Pay JavaScript SDK to your webpage:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Google Pay Integration</title>
<!-- Load Google Pay SDK -->
<script src="https://pay.google.com/gp/p/js/pay.js" async></script>
</head>
<body>
<div id="google-pay-button"></div>
</body>
</html>2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
<div id="google-pay-button"></div>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
// Load Google Pay SDK
const script = document.createElement('script')
script.src = 'https://pay.google.com/gp/p/js/pay.js'
script.async = true
document.head.appendChild(script)
})
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useEffect } from 'react'
function GooglePayButton() {
useEffect(() => {
// Load Google Pay SDK
const script = document.createElement('script')
script.src = 'https://pay.google.com/gp/p/js/pay.js'
script.async = true
document.head.appendChild(script)
return () => {
// Cleanup if needed
}
}, [])
return <div id="google-pay-button"></div>
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Configuring Payment Parameters
Using the configuration from Onerway, build the Google Pay payment request.
Base Configuration (Fixed Values)
const baseRequest = {
apiVersion: 2, // Fixed
apiVersionMinor: 0 // Fixed
}
const allowedCardAuthMethods = ['PAN_ONLY', 'CRYPTOGRAM_3DS'] // Fixed2
3
4
5
6
Tokenization Specification (From Onerway)
// Get configuration from Onerway
const onerwayConfig = await getOnerwayGooglePayConfig()
const tokenizationSpecification = {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: onerwayConfig.gateway, // From Onerway API
gatewayMerchantId: onerwayConfig.gatewayMerchantId // From Onerway API
}
}2
3
4
5
6
7
8
9
10
Card Payment Method
const baseCardPaymentMethod = {
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'], // Fixed
allowedCardNetworks: onerwayConfig.allowedCardNetworks // From Onerway API
}
}
const cardPaymentMethod = {
...baseCardPaymentMethod,
tokenizationSpecification: tokenizationSpecification
}2
3
4
5
6
7
8
9
10
11
12
Configuration Source
| Parameter | Source | Example Value |
|---|---|---|
gateway | Onerway API: gatewayName | "ronghan" |
gatewayMerchantId | Onerway API: gatewayMerchantId | "BCR2DN6TY7DM5TDU" |
allowedCardNetworks | Onerway API: subCardTypes | ["MASTERCARD", "VISA"] |
allowedAuthMethods | Fixed Value | ["PAN_ONLY", "CRYPTOGRAM_3DS"] |
Adding Google Pay Button
Initialize Payment Client
const paymentsClient = new google.payments.api.PaymentsClient({
environment: 'TEST' // Use 'PRODUCTION' for live
})2
3
Check Device Support
async function checkGooglePaySupport() {
const isReadyToPayRequest = {
...baseRequest,
allowedPaymentMethods: [baseCardPaymentMethod]
}
try {
const response = await paymentsClient.isReadyToPay(isReadyToPayRequest)
if (response.result) {
addGooglePayButton() // Display button
}
} catch (err) {
console.error('Error checking Google Pay support:', err)
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Create and Customize Button
function addGooglePayButton() {
const button = paymentsClient.createButton({
onClick: onGooglePayButtonClicked, // Bind click handler
allowedPaymentMethods: [baseCardPaymentMethod],
buttonColor: 'default', // 'default', 'black', 'white'
buttonType: 'buy', // 'buy', 'plain', 'donate', 'checkout', etc.
buttonRadius: 4, // Border radius in pixels
buttonBorderType: 'default_border', // 'default_border' or 'no_border'
buttonSizeMode: 'fill' // 'static' or 'fill'
})
document.getElementById('google-pay-button').appendChild(button)
}2
3
4
5
6
7
8
9
10
11
12
13
Customizing Button Style
Button Configuration Options
Google Pay provides built-in button customization through createButton() parameters:
const button = paymentsClient.createButton({
onClick: onGooglePayButtonClicked,
allowedPaymentMethods: [baseCardPaymentMethod],
// Style configuration
buttonColor: 'default', // 'default' | 'black' | 'white'
buttonType: 'buy', // 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'plain' | 'subscribe' | 'long' | 'short'
buttonRadius: 4, // Border radius in pixels (min: 0, max: depends on button height)
buttonBorderType: 'default_border', // 'default_border' | 'no_border'
buttonSizeMode: 'fill', // 'static' | 'fill'
buttonLocale: 'en' // ISO 639-1 code: en, ar, bg, ca, cs, da, de, el, es, et, fi, fr, hr, id, it, ja, ko, ms, nl, no, pl, pt, ru, sk, sl, sr, sv, th, tr, uk, zh
})2
3
4
5
6
7
8
9
10
11
12
Button Properties Reference:
| Property | Type | Description | Default |
|---|---|---|---|
buttonColor | 'default' | 'black' | 'white' | Button color scheme. 'default'/'black' for light backgrounds, 'white' for dark backgrounds. | 'default' |
buttonType | 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'plain' | 'subscribe' | 'long' | 'short' | Button text type (localized based on user's browser settings). 'plain' shows only the logo. | 'buy' |
buttonRadius | number | Border radius in pixels. Min is 0, max depends on button height (e.g., max 20 for 40px height). | System default |
buttonBorderType | 'default_border' | 'no_border' | Whether to display a border around the button. | 'default_border' |
buttonSizeMode | 'static' | 'fill' | 'static': sized by button type; 'fill': sized by CSS width/height. | 'static' |
buttonLocale | string | ISO 639-1 language code. Supported locales: en, ar, bg, ca, cs, da, de, el, es, et, fi, fr, hr, id, it, ja, ko, ms, nl, no, pl, pt, ru, sk, sl, sr, sv, th, tr, uk, zh. | Browser locale |
Reference
For complete button properties documentation, see Google Pay Button Properties.
Getting Payment Token
Handle Button Click
async function onGooglePayButtonClicked() {
const paymentDataRequest = {
...baseRequest,
allowedPaymentMethods: [cardPaymentMethod],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '29.99', // Order amount
currencyCode: 'USD',
countryCode: 'US'
},
merchantInfo: {
merchantName: 'Example Merchant' // Your merchant name
}
}
try {
const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest)
await processPayment(paymentData) // Process payment
} catch (err) {
console.error('Payment failed:', err)
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Extract Payment Token
function processPayment(paymentData) {
const paymentToken = paymentData.paymentMethodData.tokenizationData.token
// Send to backend
fetch('/api/google-pay/process-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
paymentToken,
orderAmount: '29.99',
orderCurrency: 'USD'
})
})
}2
3
4
5
6
7
8
9
10
11
12
13
14
Backend Payment Processing
API Integration
Note
- All
JSONfields must be stringified before submission - Nested objects must be serialized to
JSONstring format JSONfields must not contain unescaped special characters- Arrays in
JSONshould be properly formatted - Example of
JSONstring field:
{
"object": "{\"obj-key1\":\"v1\",\"obj-key2\":\"v2\"}",
"complex": "{\"k1\":\"v1\",\"array\":\"[{\\\"obj-key3\\\":\\\"v3\\\",\\\"obj-key4\\\":\\\"v4\\\"}]\"}"
}2
3
4
Request Parameters
| Parameter | Type | Length | Required | Signed | Description |
|---|---|---|---|---|---|
billingInformation | String | / | Yes | Yes | Transaction billing information in JSON string format. See TransactionAddressfor complete structure. |
cardInfo | String | / | Conditional | Yes | Card information for wallet payments in JSON string format. See TxnCardInfoWalletfor complete structure. |
merchantCustId | String | 50 | Conditional | Yes | Unique customer identifier in merchant system. |
merchantNo | String | 20 | Yes | Yes | Merchant number assigned by |
merchantTxnId | String | 64 | Yes | Yes | Unique transaction identifier for each customer payment, generated by the merchant system. |
merchantTxnOriginalId | String | 128 | No | Yes | Master transaction ID generated by merchant for grouping related transactions. |
merchantTxnTime | String | / | No | Yes | Transaction timestamp when the merchant initiated the transaction. |
merchantTxnTimeZone | String | 64 | No | Yes | Transaction timezone offset for the merchant transaction time. |
orderAmount | String | 19 | Yes | Yes | Transaction amount in the specified currency, formatted as a decimal string. |
orderCurrency | String | 8 | Yes | Yes | |
productType | String | 16 | Yes | Yes | Payment method category that determines which payment options are available to customers. See ProductTypeEnumfor all available options. |
shippingInformation | String | / | Yes | Yes | Transaction shipping information in JSON string format. See TransactionAddressfor complete structure. |
sign | String | / | Yes | No | Digital signature string for request verification and security. Please refer to Signature for signature generation method. |
subProductType | String | 16 | Yes | Yes | Specific implementation method within the selected product type, defining how the payment is processed. See SubProductTypeEnumfor all available options. |
tokenInfo | String | / | Conditional | Yes | Token information in JSON string format. See TokenInfofor complete structure. |
txnOrderMsg | String | / | Yes | Yes | Transaction business information in JSON string format. See TxnOrderMsgfor complete structure. |
txnType | String | 16 | Yes | Yes | Transaction type that defines the payment operation to be performed. See TxnTypeEnumfor all available options and detailed usage scenarios. |
TokenInfo Parameters
Important: Google Pay payment token must be assigned to tokenInfo.tokenId field
| Name | Type | Length | Required | Description |
|---|---|---|---|---|
tokenId | String | / | Yes | Token ID from payment method binding. For digital wallets: • Apple Pay: Use • Google Pay: Use |
provider | String | / | Conditional | Token provider. Defaults to for other scenarios. |
Response
| Name | Type | Description |
|---|---|---|
respCode | String | Response code from |
respMsg | String | Response message from |
data | Object | Response data. Refer to object data |
data
| Name | Type | Description |
|---|---|---|
transactionId | String | Transaction order number created by |
responseTime | String | Interface response time Format: |
txnTime | String | Transaction completion time Format: |
txnTimeZone | String | Transaction time zone Format: |
orderAmount | String | Order amount |
orderCurrency | String | |
txnAmount | String | Order amount after conversion to settlement currency |
txnCurrency | String | |
status | String | Transaction processing result Refer to TxnStatusEnum |
redirectUrl | String | Redirection URL for 3D Secure verification |
contractId | String | Subscription contract number |
tokenId | String | Payment token |
eci | String | Electronic Commerce Indicator |
periodValue | String | Installment payment number of periods |
codeForm | String | Code form for specific payment methods See CodeFormEnum |
presentContext | String | Context information for presentation layer |
actionType | String | Action type for the transaction See ActionTypeEnum |
subscriptionManageUrl | String | Subscription management URL |
sign | String |
Backend Implementation
Key Integration Point
The critical part for Google Pay integration is constructing the tokenInfo object and handling Onerway's response status.
// Build tokenInfo for Google Pay
Map<String, Object> tokenInfo = new HashMap<>();
tokenInfo.put("provider", "GooglePay"); // Must be "GooglePay"
tokenInfo.put("tokenId", request.getPaymentToken()); // Google Pay payment token
// Call Onerway API (see WalletPaymentParams for complete request structure)
OnerwayResponse result = onerwayClient.doTransaction(params);
// Handle response
if ("20000".equals(result.getRespCode())) {
String status = result.getData().getStatus();
if ("R".equals(status)) {
// PAN_ONLY scenario - requires CVV collection redirect
return PaymentResponse.redirect(
result.getData().getRedirectUrl() // Redirect to CVV page
);
} else if ("S".equals(status)) {
// Payment successful
return PaymentResponse.success(
result.getData().getTransactionId()
);
}
}
return PaymentResponse.failure(result.getRespMsg());2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Build tokenInfo for Google Pay
const tokenInfo = {
provider: 'GooglePay', // Must be "GooglePay"
tokenId: paymentToken // Google Pay payment token
}
// Call Onerway API (see WalletPaymentParams for complete request structure)
const result = await onerwayClient.doTransaction(requestData)
// Handle response
if (result.respCode === '20000') {
const { status, redirectUrl, transactionId } = result.data
if (status === 'R' && redirectUrl) {
// PAN_ONLY scenario - requires CVV collection redirect
return {
success: true,
redirect: true,
url: redirectUrl // Redirect to CVV page
}
} else if (status === 'S') {
// Payment successful
return {
success: true,
transactionId
}
}
}
return { success: false, error: result.respMsg }2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Build tokenInfo for Google Pay
tokenInfo := map[string]interface{}{
"provider": "GooglePay", // Must be "GooglePay"
"tokenId": paymentToken, // Google Pay payment token
}
// Call Onerway API (see WalletPaymentParams for complete request structure)
result, err := onerwayClient.DoTransaction(requestData)
if err != nil {
return PaymentResponse{Success: false, Error: err.Error()}
}
// Handle response
if result.RespCode == "20000" {
status := result.Data.Status
if status == "R" {
// PAN_ONLY scenario - requires CVV collection redirect
return PaymentResponse{
Success: true,
Redirect: true,
URL: result.Data.RedirectURL, // Redirect to CVV page
}
} else if status == "S" {
// Payment successful
return PaymentResponse{
Success: true,
TransactionID: result.Data.TransactionID,
}
}
}
return PaymentResponse{Success: false, Error: result.RespMsg}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Build tokenInfo for Google Pay
$tokenInfo = [
'provider' => 'GooglePay', // Must be "GooglePay"
'tokenId' => $paymentToken // Google Pay payment token
];
// Call Onerway API (see WalletPaymentParams for complete request structure)
$result = $onerwayClient->doTransaction($requestData);
// Handle response
if ($result['respCode'] === '20000') {
$status = $result['data']['status'];
if ($status === 'R') {
// PAN_ONLY scenario - requires CVV collection redirect
return [
'success' => true,
'redirect' => true,
'url' => $result['data']['redirectUrl'] // Redirect to CVV page
];
} else if ($status === 'S') {
// Payment successful
return [
'success' => true,
'transactionId' => $result['data']['transactionId']
];
}
}
return ['success' => false, 'error' => $result['respMsg']];2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Key Points
tokenInfo.provider: Must be set to"GooglePay"tokenInfo.tokenId: The complete Google Pay payment token frompaymentData.paymentMethodData.tokenizationData.token- Response Handling: Check
statusfield -"S"for success,"R"for redirect (PAN_ONLY)
PAN_ONLY Redirect Handling
When Google Pay uses PAN_ONLY authentication, Onerway may return status=R with a redirectUrl for additional CVV collection. Your frontend must handle this redirect scenario.
Frontend Redirect Logic
async function handlePaymentResponse(paymentData) {
const paymentToken = paymentData.paymentMethodData.tokenizationData.token
try {
const response = await fetch('/api/google-pay/process-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentToken, orderAmount: '100.00', orderCurrency: 'USD' })
})
const result = await response.json()
if (result.success) {
if (result.redirect && result.url) {
// PAN_ONLY scenario - redirect to CVV collection
window.location.href = result.url // Redirect to Onerway CVV page
// Final status will be sent via webhook notification
} else {
// CRYPTOGRAM_3DS - payment successful immediately
window.location.href = '/payment-success?txnId=' + result.transactionId
}
} else {
// Payment failed
showErrorMessage(result.error || 'Payment failed')
}
} catch (error) {
console.error('Payment processing error:', error)
showErrorMessage('Payment processing failed')
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
PAN_ONLY Flow
When receiving redirectUrl:
- Immediate: Redirect user to Onerway's CVV collection page
- User Action: Customer enters CVV on Onerway page
- Redirect Back: Onerway redirects user back to your
returnUrl - Async Notification: Onerway sends final status via webhook
- Display Result: Show final status based on webhook data or query transaction status
Authentication Methods
- CRYPTOGRAM_3DS (Recommended): Immediate authorization with device token and cryptogram
- PAN_ONLY: Requires CVV collection via redirect, adding an extra step for customers
Merchant-Collected CVV (Optional)
If you want to provide a smoother user experience, you can choose to collect CVV yourself instead of using Onerway's redirect page.
Expand to view details
Use Cases
- Provide seamless payment experience without page redirects
- Need full control over CVV input UI customization
- Have higher technical development capabilities
Implementation Requirements
- Using the PAN_ONLY Check API to determine if CVV is needed
- Designing and implementing a CVV input UI on your frontend
- Passing
cvvin thecardInfofield of the payment request - Following CVV security handling guidelines (no storage, no logging, HTTPS only)
Detailed Documentation
For complete API documentation and integration guidelines, see: Google Pay PAN_ONLY Check and Custom CVV Collection
Webhook Notifications
After payment processing, Onerway sends asynchronous notifications to your server to update the final transaction status. This is especially important for:
- PAN_ONLY flows: Final status after CVV collection and authorization
- Network delays: Transactions that complete after the initial response
- Status updates: Chargebacks, refunds, or other post-transaction events
Required Implementation
Webhook handling is critical for production environments. You must implement a webhook endpoint to receive payment status updates from Onerway.
Integration Guide
For complete webhook implementation details, including:
- Request/response format
- Signature verification
- Status handling
- Best practices
Please refer to: Transaction Notification Documentation
Quick Reference
- Endpoint: Configure your webhook URL in Onerway merchant dashboard
- Verification: Always verify the signature before processing
- Response: Return
transactionIdto acknowledge receipt - Idempotency: Handle duplicate notifications gracefully
API Usage Examples
The following examples demonstrate a complete Google Pay payment flow, including request, responses for different scenarios, and webhook notification.
{
"merchantNo": "800209", // Merchant number
"merchantTxnId": "3dd112e6-cb7f-42e2-b9bd-605485b466c8", // Unique merchant transaction ID
"merchantTxnTime": "2025-06-25 20:18:57", // Merchant transaction time
"merchantCustId": "CustId-75S6-256P", // Merchant customer ID
"orderAmount": "1", // Order amount
"orderCurrency": "USD", // Order currency
"productType": "CARD", // Product type
"subProductType": "DIRECT", // Sub-product type
"txnType": "SALE", // Transaction type: Sale
"tokenInfo": "{\"provider\":\"GooglePay\",\"tokenId\":\"{\\\"signature\\\":\\\"MEYCIQD4ZdJBmw/iM7p4FaL9BAGE9xkoFbJJ1Vft4e1JWd05bAIhAKRBBC8ouYwcjIbezWepdyOBj2ju+7VWxo8kwiNi0cr7\\\",\\\"intermediateSigningKey\\\":{\\\"signedKey\\\":\\\"{\\\\\\\"keyValue\\\\\\\":\\\\\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUAAFODDgZuh4Aty3fqhrmIL5eiYtRYw1SJbNLFx0jzhO0HVKha/u7Djjo5uDy/5gbIgPbLCBFK/+Lpmn7ZDvYQ\\\\\\\\u003d\\\\\\\\u003d\\\\\\\",\\\\\\\"keyExpiration\\\\\\\":\\\\\\\"1764807149874\\\\\\\"}\\\",\\\"signatures\\\":[\\\"MEUCIQDAqrcxwZJXro0NRKe+Mhrf+Hv9bNJPO0EsxZKR6LGV9QIgXxS4+H5z8n+WuOaXu4SAwmwQx/JsUowrB6ExeKWpQIo=\\\"]},\\\"protocolVersion\\\":\\\"ECv2\\\",\\\"signedMessage\\\":\\\"{\\\\\\\"encryptedMessage\\\\\\\":\\\\\\\"s7qdsVd7GZAm1wxpeivwoLwLjDG+Xayaeh+08Icl9PIXB6kBBcugw84ce4M+GLo1w/iTZciapsGddLHPLdahI0B+Q3DlZG3bXn7UEk4wf8bGytZl1N/F93q3CMDSyI4EGLnXhApR0qNy1elsoKlQcLnefwio+ItOD6rrLLtbIcCzdxIEv7p1KmcgwubeOvKSfBw5N4/5KnVdjLjeJ+PumXIAW5mhyMBxVoaXjuJoIbJpu4ophmigXpj8fhAHGmKeDx03sLh6RB4TYo+qOS6q6hEuEXo7sIdCbit/WeePpf4IbfM5lM51KSyujplsvdVcMTp4X2v0sqZytwQk34R356RMMzaXJwyZqPZ1avhtWB/tHoDyiR1CqLYlXQXO1OsbtbNbV/wHP79JZRBhMunn79sSxXpKVw+udOdK5oJvEe5aXQHNGckgvR+oBSMZSFzqC3rHRmdozKluN/SYDwfRHm04fRbxv4hIf9yC2XjuZ5Cpf+E/+6dnOHTYmwQb4pjOHurL9MwJoEwQGAhpP86fEa/Qaw4uzHq2B1BfzalJaW518JafDEyjTQ68jdhA/L9bpI59ZWL5HmnblwGXkJBnJM9/mmupZ8g\\\\\\\\u003d\\\\\\\",\\\\\\\"ephemeralPublicKey\\\\\\\":\\\\\\\"BK0m8U6RD3wLrCSxkUyOcvM1o92AClouF5st34XJOQCe8rn63xIl1OrOdiVlulfBBE1Z29hhUvsKJz09N30VwGI\\\\\\\\u003d\\\\\\\",\\\\\\\"tag\\\\\\\":\\\\\\\"uoTGqKEPhfryDTN+ZdOu2T9JTGNSc1dncwydpngzGX4\\\\\\\\u003d\\\\\\\"}\\\"}\"}", // Google Pay encrypted token
"billingInformation": "{\"address\":\"889 Mill Close\",\"city\":\"Buckridgefield\",\"country\":\"US\",\"email\":\"Ryan.Purdy@gmail.com\",\"firstName\":\"Frederik\",\"identityNumber\":\"92066033023\",\"lastName\":\"Pfannerstill\",\"phone\":\"15209608948\",\"postalCode\":\"96542\",\"province\":\"CO\"}", // Billing information
"shippingInformation": "{\"address\":\"72981 Alejandrin Port\",\"city\":\"Lake Judd\",\"country\":\"US\",\"email\":\"Tracy_Stanton58@gmail.com\",\"firstName\":\"Noble\",\"identityNumber\":\"84880795738\",\"lastName\":\"Baumbach\",\"phone\":\"18434119887\",\"postalCode\":\"12053\",\"province\":\"CO\"}", // Shipping information
"txnOrderMsg": "{\"accept\":\"*/*\",\"appId\":\"1727880846378401792\",\"colorDepth\":\"24\",\"contentLength\":\"65536\",\"javaEnabled\":false,\"language\":\"en-US\",\"products\":\"[{\\\"currency\\\":\\\"USD\\\",\\\"name\\\":\\\"butternut pumpkin\\\",\\\"num\\\":\\\"96\\\",\\\"price\\\":\\\"708.69\\\",\\\"type\\\":\\\"est\\\"},{\\\"currency\\\":\\\"USD\\\",\\\"name\\\":\\\"kiwi fruit\\\",\\\"num\\\":\\\"35\\\",\\\"price\\\":\\\"276.59\\\",\\\"type\\\":\\\"amet aliquip eiusmod\\\"}]\",\"returnUrl\":\"https://docs.onerway.com/\",\"notifyUrl\":\"https://sandbox-acq.onerway.com/callback/testReceiveNotification\",\"screenHeight\":\"1200\",\"screenWidth\":\"375\",\"timeZoneOffset\":\"180\",\"transactionIp\":\"252.238.110.248\",\"userAgent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36\"}", // Transaction order information
"sign": "d39f4b5c4a045ec8adbfe886af95112ea7180788baf2b7eb01986fc18abeb3bc" // Request signature
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"respCode": "20000", // Response code: Success
"respMsg": "Success", // Response message
"data": {
"transactionId": null, // Transaction ID (null when redirect required)
"responseTime": null, // Response time
"txnTime": null, // Transaction time
"txnTimeZone": null, // Transaction timezone
"orderAmount": "1.00", // Order amount
"orderCurrency": "USD", // Order currency
"txnAmount": null, // Transaction amount
"txnCurrency": null, // Transaction currency
"status": "R", // Status: Redirect required
"redirectUrl": "https://sandbox-checkout.onerway.com/additional-information?name=%7B%22lastName%22%3A%22Pfannerstill%22%2C%22firstName%22%3A%22Frederik%22%7D&returnUrl=https%3A%2F%2Fdocs.onerway.com%2F&key=64089c441df14ef8ba30489b8f0f17ae", // Redirect URL
"contractId": null, // Contract ID
"tokenId": null, // Token ID
"eci": null, // ECI value
"periodValue": null, // Period value
"codeForm": null, // Code form
"presentContext": null, // Present context
"actionType": "RedirectURL", // Action type: Redirect URL
"subscriptionManageUrl": null, // Subscription management URL
"sign": "e5fbaf27b2fc6cbbc09dbb7de05fef2eb3e95895ee8a53dc4c85535d930d1e68" // Response signature
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"respCode": "20000", // Response code: Success
"respMsg": "Success", // Response message
"data": {
"transactionId": "1993514205793619968", // Onerway transaction ID
"responseTime": "2025-11-26 10:56:42", // Response time
"txnTime": "2025-11-26 10:56:39", // Transaction time
"txnTimeZone": "+08:00", // Transaction timezone
"orderAmount": "1.00", // Order amount
"orderCurrency": "USD", // Order currency
"txnAmount": "1.00", // Transaction amount
"txnCurrency": "USD", // Transaction currency
"status": "S", // Status: Success
"redirectUrl": null, // No redirect needed
"contractId": null, // Contract ID
"tokenId": null, // Token ID
"eci": "05", // ECI value
"periodValue": null, // Period value
"codeForm": null, // Code form
"presentContext": null, // Present context
"actionType": null, // No additional action needed
"subscriptionManageUrl": null, // Subscription management URL
"sign": "abc123..." // Response signature
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"notifyType": "TXN", // Notification type: Transaction
"transactionId": "1993514205793619968", // Onerway transaction ID
"txnType": "SALE", // Transaction type: Sale
"merchantNo": "800209", // Merchant number
"merchantTxnId": "3dd112e6-cb7f-42e2-b9bd-605485b466c8", // Merchant transaction ID
"responseTime": "2025-11-26 10:56:42", // Response time
"txnTime": "2025-11-26 10:56:39", // Transaction time
"txnTimeZone": "+08:00", // Transaction timezone
"orderAmount": "1.00", // Order amount
"orderCurrency": "USD", // Order currency
"status": "S", // Final status: Success
"cardBinCountry": "US", // Card BIN country
"paymentMethod": "VISA", // Payment method
"channelRequestId": "8002091993514208116998145", // Channel request ID
"reason": "{\"respCode\":\"20000\",\"respMsg\":\"Success\"}", // Reason details
"sign": "99de5c609e9e2715ed1f8297012a6dfe91677600a060d535b268e685772a606a" // Webhook signature
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Response Scenario Descriptions
PAN_ONLY Scenario
When you receive status=R and actionType=RedirectURL, redirect the user to redirectUrl to complete CVV collection. After CVV verification, the user will be returned to the URL specified in txnOrderMsg.returnUrl.
CRYPTOGRAM_3DS Scenario
When you receive status=S, the payment has been successfully completed and no additional action is needed.
Webhook Handling Guidelines
Important Notes
- Signature Verification: Always verify the
signfield to ensure the request is from Onerway - Idempotent Handling: You may receive duplicate notifications; use
transactionIdormerchantTxnIdfor idempotency - Status Updates: Use the
statusin the webhook notification as the final transaction status - Response Format: After receiving the notification, return a JSON response containing
transactionIdto acknowledge receipt
For detailed webhook handling guidelines, please refer to: Transaction Notification Documentation
Complete Integration Examples
HTML Complete Implementation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Google Pay Integration</title>
<style>
#google-pay-button {
border-radius: 8px;
width: 100%;
max-width: 400px;
height: 48px;
margin: 20px auto;
display: block;
}
</style>
</head>
<body>
<div id="google-pay-button"></div>
<script>
// Base configuration
const baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
}
let paymentsClient = null
let onerwayConfig = null
// Get Onerway configuration
async function getOnerwayGooglePayConfig() {
const response = await fetch('/api/payment-methods', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
country: 'US',
merchantNo: '800209',
orderAmount: '29.99',
orderCurrency: 'USD',
paymentMode: 'WEB'
})
})
const result = await response.json()
const googlePay = result.data.find(m => m.paymentMethod === 'GooglePay')
if (!googlePay) {
throw new Error('Google Pay not available')
}
return {
gateway: googlePay.gatewayName,
gatewayMerchantId: googlePay.gatewayMerchantId,
allowedCardNetworks: googlePay.subCardTypes
}
}
// Get base card payment method
function getBaseCardPaymentMethod() {
return {
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: onerwayConfig.allowedCardNetworks
}
}
}
// Get card payment method with tokenization
function getCardPaymentMethod() {
return {
...getBaseCardPaymentMethod(),
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: onerwayConfig.gateway, // From Onerway
gatewayMerchantId: onerwayConfig.gatewayMerchantId // From Onerway
}
}
}
}
// Initialize Google Pay client
function getGooglePaymentsClient() {
if (paymentsClient === null) {
paymentsClient = new google.payments.api.PaymentsClient({
environment: 'TEST' // Use 'PRODUCTION' for live
})
}
return paymentsClient
}
// Check Google Pay support
async function checkGooglePaySupport() {
const client = getGooglePaymentsClient()
const isReadyToPayRequest = {
...baseRequest,
allowedPaymentMethods: [getBaseCardPaymentMethod()]
}
try {
const response = await client.isReadyToPay(isReadyToPayRequest)
if (response.result) {
addGooglePayButton() // Add button
}
} catch (err) {
console.error('Error checking Google Pay support:', err)
}
}
// Add Google Pay button
function addGooglePayButton() {
const client = getGooglePaymentsClient()
const button = client.createButton({
onClick: onGooglePayButtonClicked, // Bind click handler
buttonColor: 'default', // 'default', 'black', 'white'
buttonType: 'buy', // 'buy', 'plain', 'donate', 'checkout', etc.
buttonRadius: 4, // Border radius in pixels
buttonBorderType: 'default_border', // 'default_border' or 'no_border'
buttonSizeMode: 'fill' // 'static' or 'fill'
})
document.getElementById('google-pay-button').appendChild(button)
}
// Handle button click
async function onGooglePayButtonClicked() {
const paymentDataRequest = {
...baseRequest,
allowedPaymentMethods: [getCardPaymentMethod()],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '29.99', // Order amount
currencyCode: 'USD',
countryCode: 'US'
},
merchantInfo: {
merchantName: 'Example Merchant'
}
}
try {
const client = getGooglePaymentsClient()
const paymentData = await client.loadPaymentData(paymentDataRequest)
await processPayment(paymentData) // Process payment
} catch (err) {
console.error('Payment failed:', err)
}
}
// Process payment
async function processPayment(paymentData) {
const paymentToken = paymentData.paymentMethodData.tokenizationData.token
const response = await fetch('/api/google-pay/process-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
paymentToken,
orderAmount: '29.99',
orderCurrency: 'USD'
})
})
const result = await response.json()
if (result.success) {
if (result.redirect) {
// PAN_ONLY scenario - redirect to CVV collection
window.location.href = result.url // Redirect handling
} else {
// Payment successful
alert('Payment successful! Transaction ID: ' + result.transactionId)
}
} else {
alert('Payment failed: ' + result.error)
}
}
// Initialize on load
async function onGooglePayLoaded() {
try {
// Step 1: Get Onerway configuration
onerwayConfig = await getOnerwayGooglePayConfig()
// Step 2: Check Google Pay support
await checkGooglePaySupport()
} catch (err) {
console.error('Initialization failed:', err)
}
}
// Load SDK
const script = document.createElement('script')
script.src = 'https://pay.google.com/gp/p/js/pay.js'
script.async = true
script.onload = onGooglePayLoaded
document.head.appendChild(script)
</script>
</body>
</html>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
Vue 3 Complete Implementation
<template>
<div class="google-pay-container">
<div
v-if="isGooglePaySupported"
id="google-pay-button"
class="gpay-button"
></div>
<div v-else-if="!loading" class="error-message">
Google Pay is not available on this device
</div>
<div v-if="loading" class="loading">Processing...</div>
<div v-if="error" class="error">{{ error }}</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const isGooglePaySupported = ref(false)
const loading = ref(false)
const error = ref('')
let paymentsClient = null
let onerwayConfig = null
// Base configuration
const baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
}
// Get Onerway configuration
const getOnerwayGooglePayConfig = async () => {
const response = await fetch('/api/payment-methods', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
country: 'US',
merchantNo: '800209',
orderAmount: '29.99',
orderCurrency: 'USD',
paymentMode: 'WEB'
})
})
const result = await response.json()
const googlePay = result.data.find(m => m.paymentMethod === 'GooglePay')
if (!googlePay) {
throw new Error('Google Pay not available')
}
return {
gateway: googlePay.gatewayName,
gatewayMerchantId: googlePay.gatewayMerchantId,
allowedCardNetworks: googlePay.subCardTypes
}
}
// Get base card payment method
const getBaseCardPaymentMethod = () => ({
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: onerwayConfig.allowedCardNetworks
}
})
// Get card payment method with tokenization
const getCardPaymentMethod = () => ({
...getBaseCardPaymentMethod(),
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: onerwayConfig.gateway, // From Onerway
gatewayMerchantId: onerwayConfig.gatewayMerchantId // From Onerway
}
}
})
// Load Google Pay SDK
const loadGooglePayScript = () => {
return new Promise((resolve, reject) => {
if (window.google?.payments?.api?.PaymentsClient) {
resolve()
return
}
const script = document.createElement('script')
script.src = 'https://pay.google.com/gp/p/js/pay.js'
script.async = true
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
}
// Check Google Pay support
const checkGooglePaySupport = async () => {
try {
paymentsClient = new google.payments.api.PaymentsClient({
environment: 'TEST' // Use 'PRODUCTION' for live
})
const isReadyToPayRequest = {
...baseRequest,
allowedPaymentMethods: [getBaseCardPaymentMethod()]
}
const response = await paymentsClient.isReadyToPay(isReadyToPayRequest)
if (response.result) {
isGooglePaySupported.value = true
addGooglePayButton()
}
} catch (err) {
console.error('Error checking Google Pay support:', err)
error.value = 'Unable to initialize Google Pay'
}
}
// Add Google Pay button
const addGooglePayButton = () => {
const button = paymentsClient.createButton({
onClick: handleGooglePayButtonClick, // Bind click handler
buttonColor: 'default', // 'default', 'black', 'white'
buttonType: 'buy', // 'buy', 'plain', 'donate', 'checkout', etc.
buttonRadius: 4, // Border radius in pixels
buttonBorderType: 'default_border', // 'default_border' or 'no_border'
buttonSizeMode: 'fill' // 'static' or 'fill'
})
document.getElementById('google-pay-button').appendChild(button)
}
// Handle button click
const handleGooglePayButtonClick = async () => {
loading.value = true
error.value = ''
try {
const paymentDataRequest = {
...baseRequest,
allowedPaymentMethods: [getCardPaymentMethod()],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '29.99', // Order amount
currencyCode: 'USD',
countryCode: 'US'
},
merchantInfo: {
merchantName: 'Example Merchant'
}
}
const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest)
await processPayment(paymentData) // Process payment
} catch (err) {
console.error('Payment failed:', err)
error.value = err.message || 'Payment processing failed'
} finally {
loading.value = false
}
}
// Process payment
const processPayment = async (paymentData) => {
const paymentToken = paymentData.paymentMethodData.tokenizationData.token
const response = await fetch('/api/google-pay/process-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
paymentToken,
orderAmount: '29.99',
orderCurrency: 'USD'
})
})
const result = await response.json()
if (result.success) {
if (result.redirect) {
// PAN_ONLY scenario - redirect to CVV collection
window.location.href = result.url // Redirect handling
} else {
// Payment successful
console.log('Payment successful:', result.transactionId)
}
} else {
throw new Error(result.error || 'Payment failed')
}
}
// Initialize on mount
onMounted(async () => {
try {
// Step 1: Get Onerway configuration
onerwayConfig = await getOnerwayGooglePayConfig()
// Step 2: Load SDK
await loadGooglePayScript()
// Step 3: Check support
await checkGooglePaySupport()
} catch (err) {
console.error('Initialization failed:', err)
error.value = 'Unable to load Google Pay'
}
})
</script>
<style scoped>
.google-pay-container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
}
.gpay-button {
border-radius: 8px;
width: 100%;
height: 48px;
}
.loading,
.error-message,
.error {
text-align: center;
padding: 10px;
margin-top: 10px;
}
.error {
color: #d32f2f;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
React Complete Implementation
import React, { useState, useEffect, useCallback } from 'react'
const GooglePayButton = () => {
const [isGooglePaySupported, setIsGooglePaySupported] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [paymentsClient, setPaymentsClient] = useState(null)
const [onerwayConfig, setOnerwayConfig] = useState(null)
// Base configuration
const baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
}
// Get Onerway configuration
const getOnerwayGooglePayConfig = useCallback(async () => {
const response = await fetch('/api/payment-methods', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
country: 'US',
merchantNo: '800209',
orderAmount: '29.99',
orderCurrency: 'USD',
paymentMode: 'WEB'
})
})
const result = await response.json()
const googlePay = result.data.find(m => m.paymentMethod === 'GooglePay')
if (!googlePay) {
throw new Error('Google Pay not available')
}
return {
gateway: googlePay.gatewayName,
gatewayMerchantId: googlePay.gatewayMerchantId,
allowedCardNetworks: googlePay.subCardTypes
}
}, [])
// Get base card payment method
const getBaseCardPaymentMethod = useCallback(() => ({
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: onerwayConfig?.allowedCardNetworks || []
}
}), [onerwayConfig])
// Get card payment method with tokenization
const getCardPaymentMethod = useCallback(() => ({
...getBaseCardPaymentMethod(),
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: onerwayConfig.gateway, // From Onerway
gatewayMerchantId: onerwayConfig.gatewayMerchantId // From Onerway
}
}
}), [onerwayConfig, getBaseCardPaymentMethod])
// Load Google Pay SDK
const loadGooglePayScript = useCallback(() => {
return new Promise((resolve, reject) => {
if (window.google?.payments?.api?.PaymentsClient) {
resolve()
return
}
const script = document.createElement('script')
script.src = 'https://pay.google.com/gp/p/js/pay.js'
script.async = true
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
}, [])
// Check Google Pay support
const checkGooglePaySupport = useCallback(async (client) => {
try {
const isReadyToPayRequest = {
...baseRequest,
allowedPaymentMethods: [getBaseCardPaymentMethod()]
}
const response = await client.isReadyToPay(isReadyToPayRequest)
if (response.result) {
setIsGooglePaySupported(true)
addGooglePayButton(client)
}
} catch (err) {
console.error('Error checking Google Pay support:', err)
setError('Unable to initialize Google Pay')
}
}, [baseRequest, getBaseCardPaymentMethod])
// Add Google Pay button
const addGooglePayButton = useCallback((client) => {
const button = client.createButton({
onClick: () => handleGooglePayButtonClick(client), // Bind click handler
buttonColor: 'default', // 'default', 'black', 'white'
buttonType: 'buy', // 'buy', 'plain', 'donate', 'checkout', etc.
buttonRadius: 4, // Border radius in pixels
buttonBorderType: 'default_border', // 'default_border' or 'no_border'
buttonSizeMode: 'fill' // 'static' or 'fill'
})
const container = document.getElementById('google-pay-button')
if (container) {
container.innerHTML = ''
container.appendChild(button)
}
}, [])
// Process payment
const processPayment = async (paymentData) => {
const paymentToken = paymentData.paymentMethodData.tokenizationData.token
const response = await fetch('/api/google-pay/process-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
paymentToken,
orderAmount: '29.99',
orderCurrency: 'USD'
})
})
const result = await response.json()
if (result.success) {
if (result.redirect) {
// PAN_ONLY scenario - redirect to CVV collection
window.location.href = result.url // Redirect handling
} else {
// Payment successful
console.log('Payment successful:', result.transactionId)
}
} else {
throw new Error(result.error || 'Payment failed')
}
}
// Handle button click
const handleGooglePayButtonClick = async (client) => {
setLoading(true)
setError('')
try {
const paymentDataRequest = {
...baseRequest,
allowedPaymentMethods: [getCardPaymentMethod()],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '29.99', // Order amount
currencyCode: 'USD',
countryCode: 'US'
},
merchantInfo: {
merchantName: 'Example Merchant'
}
}
const paymentData = await client.loadPaymentData(paymentDataRequest)
await processPayment(paymentData) // Process payment
} catch (err) {
console.error('Payment failed:', err)
setError(err.message || 'Payment processing failed')
} finally {
setLoading(false)
}
}
// Initialize on mount
useEffect(() => {
const initGooglePay = async () => {
try {
// Step 1: Get Onerway configuration
const config = await getOnerwayGooglePayConfig()
setOnerwayConfig(config)
// Step 2: Load SDK
await loadGooglePayScript()
// Step 3: Initialize client
const client = new window.google.payments.api.PaymentsClient({
environment: 'TEST' // Use 'PRODUCTION' for live
})
setPaymentsClient(client)
// Step 4: Check support
await checkGooglePaySupport(client)
} catch (err) {
console.error('Initialization failed:', err)
setError('Unable to load Google Pay')
}
}
initGooglePay()
}, [getOnerwayGooglePayConfig, loadGooglePayScript, checkGooglePaySupport])
return (
<div className="google-pay-container">
{isGooglePaySupported ? (
<div
id="google-pay-button"
className="gpay-button"
></div>
) : !loading ? (
<div className="error-message">
Google Pay is not available on this device
</div>
) : null}
{loading && <div className="loading">Processing...</div>}
{error && <div className="error">{error}</div>}
</div>
)
}
export default GooglePayButton2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
Error Handling
Common Errors
Device Support Issues
if (!window.google?.payments?.api?.PaymentsClient) {
console.error('Google Pay API not loaded')
// Show alternative payment methods
}2
3
4
Configuration Errors
try {
const config = await getOnerwayGooglePayConfig()
} catch (error) {
console.error('Failed to get configuration:', error)
// Handle configuration failure
}2
3
4
5
6
Payment Failures
paymentsClient.loadPaymentData(request)
.catch((error) => {
if (error.statusCode === 'CANCELED') {
console.log('User cancelled payment')
} else {
console.error('Payment error:', error)
}
})2
3
4
5
6
7
8
Security Best Practices
Token Management:
- Never log or store Google Pay payment tokens
- All payment communications must use HTTPS
- Always verify webhook signatures
Data Protection:
- Ensure all API calls use HTTPS
- Validate payment token integrity
- Protect sensitive user information
Data Consistency & Anti-Replay:
- Verify order number, amount, and currency match merchant records
- Implement request idempotency (using
merchantTxnIdfor deduplication) - Limit token usage timeframe to prevent replay attacks
Implementation Best Practices
- Get Configuration First: Always query Onerway API for Google Pay configuration before initialization
- Handle Both Auth Methods: Support both PAN_ONLY and CRYPTOGRAM_3DS authentication
- Implement Redirects: Properly handle PAN_ONLY redirect scenarios for CVV collection
- Error Handling: Implement comprehensive error handling for all scenarios
- Webhook Processing: Set up reliable webhook handling for async payment notifications
- Test Thoroughly: Test with both successful and failed payment scenarios
- Monitor Performance: Track payment success rates and response times
- Security First: Always verify signatures and use HTTPS for all communications
Integration Checklist
Before going live with Google Pay:
- Retrieved Google Pay configuration from Onerway Payment Methods Query API
- Implemented proper
gatewayandgatewayMerchantIdconfiguration - Set
allowedCardNetworksfrom Onerway response (subCardTypes) - Fixed
allowedAuthMethodsto["PAN_ONLY", "CRYPTOGRAM_3DS"] - Tested Google Pay button display on supported devices
- Implemented payment token extraction and submission
- Configured
tokenInfo.provideras"GooglePay" - Assigned payment token to
tokenInfo.tokenId - Implemented PAN_ONLY redirect handling for CVV collection
- Set up webhook endpoint for async notifications
- Verified webhook signature validation
- Tested successful payment flow (CRYPTOGRAM_3DS)
- Tested PAN_ONLY redirect flow
- Tested payment failure scenarios
- Implemented proper error handling and user messaging
- Verified HTTPS is enabled on all pages
- Reviewed security best practices compliance
Next Steps
After completing Google Pay integration:
- Test in Sandbox: Thoroughly test all payment scenarios in test environment
- Production Approval: Contact Onerway to enable Google Pay in production
- Go Live: Deploy to production and monitor initial transactions
- Monitor Performance: Track success rates and user experience metrics
Related Documentation
- Payment Methods Query API - Get Google Pay configuration
- Transaction Notification - Handle webhook notifications
- Google Pay PAN_ONLY Check and Custom CVV Collection - Custom option: Merchant-collected CVV
- Google Pay Official Docs - Google's complete documentation
Need Help?
For technical support or questions about Google Pay integration, contact Onerway support or refer to the troubleshooting guide.