Google Pay 集成
Google Pay 使客户能够使用存储在 Google 账户中的支付信息进行快速、简单的结账。通过 Onerway 集成 Google Pay,您可以为全球数亿 Google 用户提供无缝的支付体验。
集成概述
通过 Onerway 集成 Google Pay 包含三个主要阶段:
- 配置获取 → 查询 Onerway API 获取 Google Pay 设置
- Google Pay 初始化 → 设置 SDK 并显示支付按钮
- 支付处理 → 处理支付令牌并通过 Onerway 完成交易
前置要求
在实施 Google Pay 集成之前,请确保您具备:
- HTTPS 网站:Google Pay 要求安全的 HTTPS 连接
- 支持的浏览器:Chrome、Firefox、Safari、Edge、Opera 或 UC Browser
- Onerway 商户账户:已启用 Google Pay 的有效账户
- Google 账户:用于测试,需在 Google 账户中添加支付方式
- 后端集成:服务器能够调用 Onerway API
Google Pay 优势
- 一键结账:用户通过生物识别或设备 PIN 进行身份验证
- 预填充信息:支付详情、账单和送货地址自动填充
- 广泛采用:面向全球数亿 Google 用户
- 安全处理:令牌化支付数据保护敏感信息
- 移动优化:Web 和移动平台无缝体验
集成流程
获取 Onerway 配置
关键第一步
在配置 Google Pay 之前,您必须调用 Onerway 的支付方式查询 API 以获取所需的配置参数。
为什么需要查询支付方式?
支付方式查询 API 提供必要的 Google Pay 配置:
gateway- 支付令牌化的网关标识符gatewayMerchantId- 您的支付网关商户 IDallowedCardNetworks- 您的商户账户支持的卡组织
查询 API 请求
{
"appId": "1727880846378401792",
"country": "US", // 客户所在国家
"merchantNo": "800209", // 您的商户号
"orderAmount": "29.99",
"orderCurrency": "USD",
"paymentMode": "WEB", // WEB、APP 或 WAP
"sign": "..."
}2
3
4
5
6
7
8
9
{
"respCode": "20000",
"respMsg": "Success",
"data": [
{
"productType": "LPMS",
"paymentMethod": "GooglePay",
"countryCode": "US",
"gatewayName": "ronghan", // 用作 gateway
"gatewayMerchantId": "BCR2DN6TY7DM5TDU", // 用作 gatewayMerchantId
"merchantId": "800096",
"subCardTypes": ["MASTERCARD", "VISA"] // 用作 allowedCardNetworks
}
]
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
查看支付方式查询 API 获取完整文档。
提取配置
收到响应后,提取 Google Pay 配置:
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()
// 查找 Google Pay 配置
const googlePay = result.data.find(
method => method.paymentMethod === 'GooglePay'
)
if (!googlePay) {
throw new Error('Google Pay 不可用')
}
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
// 在 setup 函数中
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 不可用')
}
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
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 不可用')
}
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
加载 Google Pay SDK
将 Google Pay JavaScript SDK 添加到您的网页:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Google Pay 集成</title>
<!-- 加载 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(() => {
// 加载 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
import { useEffect } from 'react'
function GooglePayButton() {
useEffect(() => {
// 加载 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 () => {
// 清理(如需要)
}
}, [])
return <div id="google-pay-button"></div>
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
配置支付参数
使用从 Onerway 获取的配置,构建 Google Pay 支付请求。
基础配置(固定值)
const baseRequest = {
apiVersion: 2, // 固定值
apiVersionMinor: 0 // 固定值
}
const allowedCardAuthMethods = ['PAN_ONLY', 'CRYPTOGRAM_3DS'] // 固定值2
3
4
5
6
令牌化规范(来自 Onerway)
// 从 Onerway 获取配置
const onerwayConfig = await getOnerwayGooglePayConfig()
const tokenizationSpecification = {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: onerwayConfig.gateway, // 来自 Onerway API
gatewayMerchantId: onerwayConfig.gatewayMerchantId // 来自 Onerway API
}
}2
3
4
5
6
7
8
9
10
卡支付方式
const baseCardPaymentMethod = {
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'], // 固定值
allowedCardNetworks: onerwayConfig.allowedCardNetworks // 来自 Onerway API
}
}
const cardPaymentMethod = {
...baseCardPaymentMethod,
tokenizationSpecification: tokenizationSpecification
}2
3
4
5
6
7
8
9
10
11
12
配置来源
| 参数 | 来源 | 示例值 |
|---|---|---|
gateway | Onerway API: gatewayName | "ronghan" |
gatewayMerchantId | Onerway API: gatewayMerchantId | "BCR2DN6TY7DM5TDU" |
allowedCardNetworks | Onerway API: subCardTypes | ["MASTERCARD", "VISA"] |
allowedAuthMethods | 固定值 | ["PAN_ONLY", "CRYPTOGRAM_3DS"] |
添加 Google Pay 按钮
初始化支付客户端
const paymentsClient = new google.payments.api.PaymentsClient({
environment: 'TEST' // 生产环境使用 'PRODUCTION'
})2
3
检查设备支持
async function checkGooglePaySupport() {
const isReadyToPayRequest = {
...baseRequest,
allowedPaymentMethods: [baseCardPaymentMethod]
}
try {
const response = await paymentsClient.isReadyToPay(isReadyToPayRequest)
if (response.result) {
addGooglePayButton() // 显示按钮
}
} catch (err) {
console.error('检查 Google Pay 支持时出错:', err)
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
创建并自定义按钮
function addGooglePayButton() {
const button = paymentsClient.createButton({
onClick: onGooglePayButtonClicked, // 绑定点击处理程序
allowedPaymentMethods: [baseCardPaymentMethod],
buttonColor: 'default', // 'default', 'black', 'white'
buttonType: 'buy', // 'buy', 'plain', 'donate', 'checkout' 等
buttonRadius: 4, // 边框圆角(像素)
buttonBorderType: 'default_border', // 'default_border' 或 'no_border'
buttonSizeMode: 'fill' // 'static' 或 'fill'
})
document.getElementById('google-pay-button').appendChild(button)
}2
3
4
5
6
7
8
9
10
11
12
13
定制按钮样式
按钮配置选项
Google Pay 通过 createButton() 参数提供内置按钮自定义:
const button = paymentsClient.createButton({
onClick: onGooglePayButtonClicked,
allowedPaymentMethods: [baseCardPaymentMethod],
// 样式配置
buttonColor: 'default', // 'default' | 'black' | 'white'
buttonType: 'buy', // 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'plain' | 'subscribe' | 'long' | 'short'
buttonRadius: 4, // 边框圆角(像素),最小为 0,最大值取决于按钮高度
buttonBorderType: 'default_border', // 'default_border' | 'no_border'
buttonSizeMode: 'fill', // 'static' | 'fill'
buttonLocale: 'zh' // ISO 639-1 代码: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
按钮属性参考:
| 属性 | 类型 | 说明 | 默认值 |
|---|---|---|---|
buttonColor | 'default' | 'black' | 'white' | 按钮颜色方案。'default'/'black' 适用于浅色背景,'white' 适用于深色背景。 | 'default' |
buttonType | 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'plain' | 'subscribe' | 'long' | 'short' | 按钮文本类型(根据用户浏览器设置本地化)。'plain' 仅显示 Logo。 | 'buy' |
buttonRadius | number | 边框圆角(像素)。最小为 0,最大值取决于按钮高度(例如,40px 高度的按钮最大为 20)。 | 系统默认 |
buttonBorderType | 'default_border' | 'no_border' | 是否在按钮周围显示边框。 | 'default_border' |
buttonSizeMode | 'static' | 'fill' | 'static':按按钮类型调整大小;'fill':按 CSS width/height 调整大小。 | 'static' |
buttonLocale | string | ISO 639-1 语言代码。支持的语言: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。 | 浏览器语言 |
参考文档
完整的按钮属性文档,请参见 Google Pay 按钮属性。
获取支付令牌
处理按钮点击
async function onGooglePayButtonClicked() {
const paymentDataRequest = {
...baseRequest,
allowedPaymentMethods: [cardPaymentMethod],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '29.99', // 订单金额
currencyCode: 'USD',
countryCode: 'US'
},
merchantInfo: {
merchantName: 'Example Merchant' // 您的商户名称
}
}
try {
const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest)
await processPayment(paymentData) // 处理支付
} catch (err) {
console.error('支付失败:', err)
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
提取支付令牌
function processPayment(paymentData) {
const paymentToken = paymentData.paymentMethodData.tokenizationData.token
// 发送到后端
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
后端支付处理
API 集成
注意
- 所有
JSON字段必须在提交前进行字符串化处理 - 嵌套对象必须序列化为
JSON字符串格式 JSON字段不能包含未转义的特殊字符JSON中的数组应该正确格式化JSON字符串字段示例:
{
"object": "{\"obj-key1\":\"v1\",\"obj-key2\":\"v2\"}",
"complex": "{\"k1\":\"v1\",\"array\":\"[{\\\"obj-key3\\\":\\\"v3\\\",\\\"obj-key4\\\":\\\"v4\\\"}]\"}"
}2
3
4
请求参数
| 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 参数
重要:Google Pay 支付令牌必须赋值给 tokenInfo.tokenId 字段
| 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. |
响应
| 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 |
后端实现
关键集成点
Google Pay 集成的关键部分是构建 tokenInfo 对象并处理 Onerway 的响应状态。
// 构建 Google Pay 的 tokenInfo
Map<String, Object> tokenInfo = new HashMap<>();
tokenInfo.put("provider", "GooglePay"); // 必须为 "GooglePay"
tokenInfo.put("tokenId", request.getPaymentToken()); // Google Pay 支付令牌
// 调用 Onerway API(完整请求结构见 WalletPaymentParams)
OnerwayResponse result = onerwayClient.doTransaction(params);
// 处理响应
if ("20000".equals(result.getRespCode())) {
String status = result.getData().getStatus();
if ("R".equals(status)) {
// PAN_ONLY 场景 - 需要 CVV 收集重定向
return PaymentResponse.redirect(
result.getData().getRedirectUrl() // 重定向至 CVV 页面
);
} else if ("S".equals(status)) {
// 支付成功
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
// 构建 Google Pay 的 tokenInfo
const tokenInfo = {
provider: 'GooglePay', // 必须为 "GooglePay"
tokenId: paymentToken // Google Pay 支付令牌
}
// 调用 Onerway API(完整请求结构见 WalletPaymentParams)
const result = await onerwayClient.doTransaction(requestData)
// 处理响应
if (result.respCode === '20000') {
const { status, redirectUrl, transactionId } = result.data
if (status === 'R' && redirectUrl) {
// PAN_ONLY 场景 - 需要 CVV 收集重定向
return {
success: true,
redirect: true,
url: redirectUrl // 重定向至 CVV 页面
}
} else if (status === 'S') {
// 支付成功
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
// 构建 Google Pay 的 tokenInfo
tokenInfo := map[string]interface{}{
"provider": "GooglePay", // 必须为 "GooglePay"
"tokenId": paymentToken, // Google Pay 支付令牌
}
// 调用 Onerway API(完整请求结构见 WalletPaymentParams)
result, err := onerwayClient.DoTransaction(requestData)
if err != nil {
return PaymentResponse{Success: false, Error: err.Error()}
}
// 处理响应
if result.RespCode == "20000" {
status := result.Data.Status
if status == "R" {
// PAN_ONLY 场景 - 需要 CVV 收集重定向
return PaymentResponse{
Success: true,
Redirect: true,
URL: result.Data.RedirectURL, // 重定向至 CVV 页面
}
} else if status == "S" {
// 支付成功
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
// 构建 Google Pay 的 tokenInfo
$tokenInfo = [
'provider' => 'GooglePay', // 必须为 "GooglePay"
'tokenId' => $paymentToken // Google Pay 支付令牌
];
// 调用 Onerway API(完整请求结构见 WalletPaymentParams)
$result = $onerwayClient->doTransaction($requestData);
// 处理响应
if ($result['respCode'] === '20000') {
$status = $result['data']['status'];
if ($status === 'R') {
// PAN_ONLY 场景 - 需要 CVV 收集重定向
return [
'success' => true,
'redirect' => true,
'url' => $result['data']['redirectUrl'] // 重定向至 CVV 页面
];
} else if ($status === 'S') {
// 支付成功
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
关键要点
tokenInfo.provider: 必须设置为"GooglePay"tokenInfo.tokenId: 完整的 Google Pay 支付令牌,来自paymentData.paymentMethodData.tokenizationData.token- 响应处理: 检查
status字段 -"S"表示成功,"R"表示需要重定向(PAN_ONLY)
PAN_ONLY 重定向处理
当 Google Pay 使用 PAN_ONLY 身份验证时,Onerway 可能会返回 status=R 和 redirectUrl 以进行额外的 CVV 收集。您的前端必须处理此重定向场景。
前端重定向逻辑
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 场景 - 重定向到 CVV 收集页面
window.location.href = result.url // 重定向到 Onerway CVV 页面
// 最终状态将通过 webhook 通知发送
} else {
// CRYPTOGRAM_3DS - 支付立即成功
window.location.href = '/payment-success?txnId=' + result.transactionId
}
} else {
// 支付失败
showErrorMessage(result.error || '支付失败')
}
} catch (error) {
console.error('支付处理错误:', error)
showErrorMessage('支付处理失败')
}
}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 流程
当收到 redirectUrl 时:
- 立即: 将用户重定向到 Onerway 的 CVV 收集页面
- 用户操作: 客户在 Onerway 页面输入 CVV
- 返回重定向: Onerway 将用户重定向回您的
returnUrl - 异步通知: Onerway 通过 webhook 发送最终状态
- 显示结果: 根据 webhook 数据或查询交易状态显示最终结果
身份验证方法
- CRYPTOGRAM_3DS(推荐):使用设备令牌和密文立即授权
- PAN_ONLY:需要通过重定向收集 CVV,为客户增加额外步骤
商户自行收集 CVV(可选)
如果您希望提供更流畅的用户体验,可以选择自行收集 CVV,而不使用 Onerway 的重定向页面。
展开查看详情
适用场景
- 希望提供无缝支付体验,避免页面跳转
- 需要完全自定义 CVV 输入界面
- 具备较高的技术开发能力
实施要求
- 使用 PAN_ONLY 检查接口判断是否需要 CVV
- 在前端设计并实现 CVV 输入 UI
- 在支付请求的
cardInfo字段中传递cvv - 遵守 CVV 安全处理规范(不存储、不记录日志、使用 HTTPS)
详细文档
完整的 API 文档和集成要点请参考:Google Pay PAN_ONLY 检查与自定义 CVV 收集
Webhook 通知
支付处理完成后,Onerway 会向您的服务器发送异步通知,以更新最终的交易状态。这在以下情况下尤为重要:
- PAN_ONLY 流程: CVV 收集和授权后的最终状态
- 网络延迟: 初始响应后完成的交易
- 状态更新: 拒付、退款或其他交易后事件
必需实现
Webhook 处理对于生产环境至关重要。您必须实现 Webhook 端点以接收来自 Onerway 的支付状态更新。
集成指南
有关 Webhook 实现的完整详细信息,包括:
- 请求/响应格式
- 签名验证
- 状态处理
- 最佳实践
请参阅: 交易通知文档
快速参考
- 端点配置: 在 Onerway 商户后台配置您的 Webhook URL
- 签名验证: 处理前务必验证签名
- 响应格式: 返回
transactionId确认收到通知 - 幂等性: 优雅处理重复通知
API 使用示例
以下是完整的 Google Pay 支付流程示例,包括请求、不同场景的响应和 Webhook 通知。
{
"merchantNo": "800209", // 商户号
"merchantTxnId": "3dd112e6-cb7f-42e2-b9bd-605485b466c8", // 商户唯一交易ID
"merchantTxnTime": "2025-06-25 20:18:57", // 商户交易时间
"merchantCustId": "CustId-75S6-256P", // 商户客户ID
"orderAmount": "1", // 订单金额
"orderCurrency": "USD", // 订单货币
"productType": "CARD", // 产品类型
"subProductType": "DIRECT", // 子产品类型
"txnType": "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 加密令牌
"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\"}", // 账单信息
"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\"}", // 配送信息
"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\"}", // 交易订单信息
"sign": "d39f4b5c4a045ec8adbfe886af95112ea7180788baf2b7eb01986fc18abeb3bc" // 签名
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"respCode": "20000", // 响应码:成功
"respMsg": "Success", // 响应消息
"data": {
"transactionId": null, // 交易ID(重定向时为null)
"responseTime": null, // 响应时间
"txnTime": null, // 交易时间
"txnTimeZone": null, // 交易时区
"orderAmount": "1.00", // 订单金额
"orderCurrency": "USD", // 订单货币
"txnAmount": null, // 交易金额
"txnCurrency": null, // 交易货币
"status": "R", // 状态:需要重定向
"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", // 重定向URL
"contractId": null, // 合同ID
"tokenId": null, // 令牌ID
"eci": null, // ECI值
"periodValue": null, // 周期值
"codeForm": null, // 代码形式
"presentContext": null, // 展示上下文
"actionType": "RedirectURL", // 操作类型:重定向URL
"subscriptionManageUrl": null, // 订阅管理URL
"sign": "e5fbaf27b2fc6cbbc09dbb7de05fef2eb3e95895ee8a53dc4c85535d930d1e68" // 响应签名
}
}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", // 响应码:成功
"respMsg": "Success", // 响应消息
"data": {
"transactionId": "1993514205793619968", // Onerway交易ID
"responseTime": "2025-11-26 10:56:42", // 响应时间
"txnTime": "2025-11-26 10:56:39", // 交易时间
"txnTimeZone": "+08:00", // 交易时区
"orderAmount": "1.00", // 订单金额
"orderCurrency": "USD", // 订单货币
"txnAmount": "1.00", // 交易金额
"txnCurrency": "USD", // 交易货币
"status": "S", // 状态:成功
"redirectUrl": null, // 无需重定向
"contractId": null, // 合同ID
"tokenId": null, // 令牌ID
"eci": "05", // ECI值
"periodValue": null, // 周期值
"codeForm": null, // 代码形式
"presentContext": null, // 展示上下文
"actionType": null, // 无需额外操作
"subscriptionManageUrl": null, // 订阅管理URL
"sign": "abc123..." // 响应签名
}
}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", // 通知类型:交易
"transactionId": "1993514205793619968", // Onerway交易ID
"txnType": "SALE", // 交易类型:销售
"merchantNo": "800209", // 商户号
"merchantTxnId": "3dd112e6-cb7f-42e2-b9bd-605485b466c8", // 商户交易ID
"responseTime": "2025-11-26 10:56:42", // 响应时间
"txnTime": "2025-11-26 10:56:39", // 交易时间
"txnTimeZone": "+08:00", // 交易时区
"orderAmount": "1.00", // 订单金额
"orderCurrency": "USD", // 订单货币
"status": "S", // 最终状态:成功
"cardBinCountry": "US", // 卡BIN国家
"paymentMethod": "VISA", // 支付方式
"channelRequestId": "8002091993514208116998145", // 渠道请求ID
"reason": "{\"respCode\":\"20000\",\"respMsg\":\"Success\"}", // 原因详情
"sign": "99de5c609e9e2715ed1f8297012a6dfe91677600a060d535b268e685772a606a" // Webhook签名
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
响应场景说明
PAN_ONLY 场景
当收到 status=R 和 actionType=RedirectURL 时,需要将用户重定向到 redirectUrl 以完成 CVV 收集。CVV 验证完成后,用户将返回到您在 txnOrderMsg.returnUrl 中指定的地址。
CRYPTOGRAM_3DS 场景
当收到 status=S 时,表示支付已成功完成,无需额外操作。
Webhook 处理要点
重要提示
- 签名验证: 务必验证 Webhook 的
sign字段,确保请求来自 Onerway - 幂等处理: 可能收到重复通知,使用
transactionId或merchantTxnId进行幂等处理 - 状态更新: 以 Webhook 通知中的
status为最终交易状态 - 响应格式: 收到通知后,返回包含
transactionId的 JSON 响应确认接收
详细的 Webhook 处理指南请参考:交易通知文档
完整集成示例
HTML 完整实现
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Google Pay 集成</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>
// 基础配置
const baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
}
let paymentsClient = null
let onerwayConfig = null
// 获取 Onerway 配置
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 不可用')
}
return {
gateway: googlePay.gatewayName,
gatewayMerchantId: googlePay.gatewayMerchantId,
allowedCardNetworks: googlePay.subCardTypes
}
}
// 获取基础卡支付方式
function getBaseCardPaymentMethod() {
return {
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: onerwayConfig.allowedCardNetworks
}
}
}
// 获取带令牌化的卡支付方式
function getCardPaymentMethod() {
return {
...getBaseCardPaymentMethod(),
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: onerwayConfig.gateway, // 来自 Onerway
gatewayMerchantId: onerwayConfig.gatewayMerchantId // 来自 Onerway
}
}
}
}
// 初始化 Google Pay 客户端
function getGooglePaymentsClient() {
if (paymentsClient === null) {
paymentsClient = new google.payments.api.PaymentsClient({
environment: 'TEST' // 生产环境使用 'PRODUCTION'
})
}
return paymentsClient
}
// 检查 Google Pay 支持
async function checkGooglePaySupport() {
const client = getGooglePaymentsClient()
const isReadyToPayRequest = {
...baseRequest,
allowedPaymentMethods: [getBaseCardPaymentMethod()]
}
try {
const response = await client.isReadyToPay(isReadyToPayRequest)
if (response.result) {
addGooglePayButton() // 添加按钮
}
} catch (err) {
console.error('检查 Google Pay 支持时出错:', err)
}
}
// 添加 Google Pay 按钮
function addGooglePayButton() {
const client = getGooglePaymentsClient()
const button = client.createButton({
onClick: onGooglePayButtonClicked, // 绑定点击处理程序
buttonColor: 'default', // 'default', 'black', 'white'
buttonType: 'buy', // 'buy', 'plain', 'donate', 'checkout' 等
buttonRadius: 4, // 边框圆角(像素)
buttonBorderType: 'default_border', // 'default_border' 或 'no_border'
buttonSizeMode: 'fill' // 'static' 或 'fill'
})
document.getElementById('google-pay-button').appendChild(button)
}
// 处理按钮点击
async function onGooglePayButtonClicked() {
const paymentDataRequest = {
...baseRequest,
allowedPaymentMethods: [getCardPaymentMethod()],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '29.99', // 订单金额
currencyCode: 'USD',
countryCode: 'US'
},
merchantInfo: {
merchantName: 'Example Merchant'
}
}
try {
const client = getGooglePaymentsClient()
const paymentData = await client.loadPaymentData(paymentDataRequest)
await processPayment(paymentData) // 处理支付
} catch (err) {
console.error('支付失败:', err)
}
}
// 处理支付
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 场景 - 重定向到 CVV 收集页面
window.location.href = result.url // 重定向处理
} else {
// 支付成功
alert('支付成功!交易 ID: ' + result.transactionId)
}
} else {
alert('支付失败: ' + result.error)
}
}
// 加载时初始化
async function onGooglePayLoaded() {
try {
// 步骤 1: 获取 Onerway 配置
onerwayConfig = await getOnerwayGooglePayConfig()
// 步骤 2: 检查 Google Pay 支持
await checkGooglePaySupport()
} catch (err) {
console.error('初始化失败:', err)
}
}
// 加载 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 完整实现
<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
</div>
<div v-if="loading" class="loading">处理中...</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
// 基础配置
const baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
}
// 获取 Onerway 配置
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 不可用')
}
return {
gateway: googlePay.gatewayName,
gatewayMerchantId: googlePay.gatewayMerchantId,
allowedCardNetworks: googlePay.subCardTypes
}
}
// 获取基础卡支付方式
const getBaseCardPaymentMethod = () => ({
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: onerwayConfig.allowedCardNetworks
}
})
// 获取带令牌化的卡支付方式
const getCardPaymentMethod = () => ({
...getBaseCardPaymentMethod(),
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: onerwayConfig.gateway, // 来自 Onerway
gatewayMerchantId: onerwayConfig.gatewayMerchantId // 来自 Onerway
}
}
})
// 加载 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)
})
}
// 检查 Google Pay 支持
const checkGooglePaySupport = async () => {
try {
paymentsClient = new google.payments.api.PaymentsClient({
environment: 'TEST' // 生产环境使用 'PRODUCTION'
})
const isReadyToPayRequest = {
...baseRequest,
allowedPaymentMethods: [getBaseCardPaymentMethod()]
}
const response = await paymentsClient.isReadyToPay(isReadyToPayRequest)
if (response.result) {
isGooglePaySupported.value = true
addGooglePayButton()
}
} catch (err) {
console.error('检查 Google Pay 支持时出错:', err)
error.value = '无法初始化 Google Pay'
}
}
// 添加 Google Pay 按钮
const addGooglePayButton = () => {
const button = paymentsClient.createButton({
onClick: handleGooglePayButtonClick, // 绑定点击处理程序
buttonColor: 'default', // 'default', 'black', 'white'
buttonType: 'buy', // 'buy', 'plain', 'donate', 'checkout' 等
buttonRadius: 4, // 边框圆角(像素)
buttonBorderType: 'default_border', // 'default_border' 或 'no_border'
buttonSizeMode: 'fill' // 'static' 或 'fill'
})
document.getElementById('google-pay-button').appendChild(button)
}
// 处理按钮点击
const handleGooglePayButtonClick = async () => {
loading.value = true
error.value = ''
try {
const paymentDataRequest = {
...baseRequest,
allowedPaymentMethods: [getCardPaymentMethod()],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '29.99', // 订单金额
currencyCode: 'USD',
countryCode: 'US'
},
merchantInfo: {
merchantName: 'Example Merchant'
}
}
const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest)
await processPayment(paymentData) // 处理支付
} catch (err) {
console.error('支付失败:', err)
error.value = err.message || '支付处理失败'
} finally {
loading.value = false
}
}
// 处理支付
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 场景 - 重定向到 CVV 收集页面
window.location.href = result.url // 重定向处理
} else {
// 支付成功
console.log('支付成功:', result.transactionId)
}
} else {
throw new Error(result.error || '支付失败')
}
}
// 组件挂载时初始化
onMounted(async () => {
try {
// 步骤 1: 获取 Onerway 配置
onerwayConfig = await getOnerwayGooglePayConfig()
// 步骤 2: 加载 SDK
await loadGooglePayScript()
// 步骤 3: 检查支持
await checkGooglePaySupport()
} catch (err) {
console.error('初始化失败:', err)
error.value = '无法加载 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 完整实现
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)
// 基础配置
const baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
}
// 获取 Onerway 配置
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 不可用')
}
return {
gateway: googlePay.gatewayName,
gatewayMerchantId: googlePay.gatewayMerchantId,
allowedCardNetworks: googlePay.subCardTypes
}
}, [])
// 获取基础卡支付方式
const getBaseCardPaymentMethod = useCallback(() => ({
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: onerwayConfig?.allowedCardNetworks || []
}
}), [onerwayConfig])
// 获取带令牌化的卡支付方式
const getCardPaymentMethod = useCallback(() => ({
...getBaseCardPaymentMethod(),
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: onerwayConfig.gateway, // 来自 Onerway
gatewayMerchantId: onerwayConfig.gatewayMerchantId // 来自 Onerway
}
}
}), [onerwayConfig, getBaseCardPaymentMethod])
// 加载 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)
})
}, [])
// 检查 Google Pay 支持
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('检查 Google Pay 支持时出错:', err)
setError('无法初始化 Google Pay')
}
}, [baseRequest, getBaseCardPaymentMethod])
// 添加 Google Pay 按钮
const addGooglePayButton = useCallback((client) => {
const button = client.createButton({
onClick: () => handleGooglePayButtonClick(client), // 绑定点击处理程序
buttonColor: 'default', // 'default', 'black', 'white'
buttonType: 'buy', // 'buy', 'plain', 'donate', 'checkout' 等
buttonRadius: 4, // 边框圆角(像素)
buttonBorderType: 'default_border', // 'default_border' 或 'no_border'
buttonSizeMode: 'fill' // 'static' 或 'fill'
})
const container = document.getElementById('google-pay-button')
if (container) {
container.innerHTML = ''
container.appendChild(button)
}
}, [])
// 处理支付
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 场景 - 重定向到 CVV 收集页面
window.location.href = result.url // 重定向处理
} else {
// 支付成功
console.log('支付成功:', result.transactionId)
}
} else {
throw new Error(result.error || '支付失败')
}
}
// 处理按钮点击
const handleGooglePayButtonClick = async (client) => {
setLoading(true)
setError('')
try {
const paymentDataRequest = {
...baseRequest,
allowedPaymentMethods: [getCardPaymentMethod()],
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: '29.99', // 订单金额
currencyCode: 'USD',
countryCode: 'US'
},
merchantInfo: {
merchantName: 'Example Merchant'
}
}
const paymentData = await client.loadPaymentData(paymentDataRequest)
await processPayment(paymentData) // 处理支付
} catch (err) {
console.error('支付失败:', err)
setError(err.message || '支付处理失败')
} finally {
setLoading(false)
}
}
// 组件挂载时初始化
useEffect(() => {
const initGooglePay = async () => {
try {
// 步骤 1: 获取 Onerway 配置
const config = await getOnerwayGooglePayConfig()
setOnerwayConfig(config)
// 步骤 2: 加载 SDK
await loadGooglePayScript()
// 步骤 3: 初始化客户端
const client = new window.google.payments.api.PaymentsClient({
environment: 'TEST' // 生产环境使用 'PRODUCTION'
})
setPaymentsClient(client)
// 步骤 4: 检查支持
await checkGooglePaySupport(client)
} catch (err) {
console.error('初始化失败:', err)
setError('无法加载 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
</div>
) : null}
{loading && <div className="loading">处理中...</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
错误处理
常见错误
设备支持问题
if (!window.google?.payments?.api?.PaymentsClient) {
console.error('Google Pay API 未加载')
// 显示其他支付方式
}2
3
4
配置错误
try {
const config = await getOnerwayGooglePayConfig()
} catch (error) {
console.error('获取配置失败:', error)
// 处理配置失败
}2
3
4
5
6
支付失败
paymentsClient.loadPaymentData(request)
.catch((error) => {
if (error.statusCode === 'CANCELED') {
console.log('用户取消了支付')
} else {
console.error('支付错误:', error)
}
})2
3
4
5
6
7
8
安全最佳实践
令牌管理:
- 绝不记录或存储 Google Pay 支付令牌
- 所有支付通信必须使用 HTTPS
- 始终验证 Webhook 签名
数据保护:
- 确保所有 API 调用使用 HTTPS
- 验证支付令牌完整性
- 保护敏感用户信息
数据一致性与防重放:
- 校验订单号、金额、币种与商户侧订单一致,防止前端参数被篡改
- 实施请求幂等(如使用
merchantTxnId去重)并限制令牌使用时效,防止重放
实施最佳实践
- 优先获取配置:在初始化前始终查询 Onerway API 获取 Google Pay 配置
- 支持两种验证方式:同时支持 PAN_ONLY 和 CRYPTOGRAM_3DS 身份验证
- 实施重定向:正确处理 PAN_ONLY 重定向场景以收集 CVV
- 错误处理:为所有场景实施全面的错误处理
- Webhook 处理:设置可靠的 Webhook 处理以处理异步支付通知
- 充分测试:测试成功和失败的支付场景
- 监控性能:跟踪支付成功率和响应时间
- 安全优先:始终验证签名并对所有通信使用 HTTPS
集成检查清单
在 Google Pay 上线之前:
- 从 Onerway 支付方式查询 API 获取了 Google Pay 配置
- 正确实施了
gateway和gatewayMerchantId配置 - 从 Onerway 响应(
subCardTypes)设置了allowedCardNetworks - 将
allowedAuthMethods固定为["PAN_ONLY", "CRYPTOGRAM_3DS"] - 在支持的设备上测试了 Google Pay 按钮显示
- 实施了支付令牌提取和提交
- 将
tokenInfo.provider配置为"GooglePay" - 将支付令牌赋值给
tokenInfo.tokenId - 实施了 PAN_ONLY 重定向处理以收集 CVV
- 设置了异步通知的 Webhook 端点
- 验证了 Webhook 签名验证
- 测试了成功支付流程(CRYPTOGRAM_3DS)
- 测试了 PAN_ONLY 重定向流程
- 测试了支付失败场景
- 实施了适当的错误处理和用户消息
- 验证所有页面已启用 HTTPS
- 审查了安全最佳实践合规性
下一步
完成 Google Pay 集成后:
- 沙箱测试:在测试环境中充分测试所有支付场景
- 生产审批:联系 Onerway 在生产环境中启用 Google Pay
- 上线:部署到生产环境并监控初始交易
- 监控性能:跟踪成功率和用户体验指标
相关文档
- 支付方式查询 API - 获取 Google Pay 配置
- 交易通知 - 处理 Webhook 通知
- Google Pay PAN_ONLY 检查与自定义 CVV 收集 - 自定义选项:商户自行收集 CVV
- Google Pay 官方文档 - Google 完整文档
需要帮助?
有关 Google Pay 集成的技术支持或问题,请联系 Onerway 支持或参考故障排查指南。