# Google Pay

> Google Pay 在收银台、Web SDK 与 API 直连下的接入差异、上线前网站报备、商户标识与解密模式的选择、API 直连的前端配置与下单流程、PAN_ONLY token 的两条处理路径、钱包订阅，以及常见问题。

Google Pay 让用户用 Google 账户中保存的卡快速完成支付，支持 Chrome、Safari、Firefox、Edge 等主流浏览器。收银台与 Web SDK 由 Onerway 渲染 Google Pay 按钮并处理 token；API 直连由商户自行加载 Google Pay JS SDK 取得加密 payment token，再提交给 Onerway。

## 接入方式选择

| 接入方式 | 按钮与 token | 商户需要做的 |
| --- | --- | --- |
| [收银台](/zh/payments/online-payments/checkout) | 收银台渲染按钮，token 不经过商户系统 | 下单传 `productType=ALL`，或传 `lpmsInfo.lpmsType=GooglePay` 锁定 Google Pay |
| [Web SDK](/zh/payments/online-payments/sdk) | SDK 在商户页面内渲染按钮，外观见[配置钱包按钮](/zh/payments/online-payments/sdk#%E9%85%8D%E7%BD%AE%E9%92%B1%E5%8C%85%E6%8C%89%E9%92%AE)；token 不经过商户系统 | 结果通过 `payment_result` 接收，见 [SDK 自有按钮](/zh/payments/online-payments/sdk#apple-paygoogle-pay-%E7%AD%89-sdk-%E8%87%AA%E6%9C%89%E6%8C%89%E9%92%AE)；App 内嵌 WebView 的限制见 [WebView 说明](/zh/payments/online-payments/sdk#%E5%9C%A8-app-%E5%86%85%E5%B5%8C-webview-%E4%B8%AD%E4%BD%BF%E7%94%A8-google-pay) |
| [API 直连](/zh/payments/online-payments/api) | 商户加载 Google Pay JS SDK 并取得 token | 本页「API 直连接入」全部步骤 |

三种接入方式都要求网站 HTTPS 且 Onerway 商户账号已开通 Google Pay；浏览器与设备支持范围以 [Google Pay Web 文档](https://developers.google.com/pay/api/web)为准，沙盒测试用 Google 账号加入测试卡群组，见[测试卡](/zh/payments/get-started/testing#google-pay-%E6%B5%8B%E8%AF%95%E5%8D%A1)。

## 接入准备

API 直连接入前，先确定 token 的认证方式如何处理、由谁解密，以及商户标识的来源。

### token 认证方式

`authMethod` 由用户的卡在 Google 侧的状态决定，商户不能指定；Onerway 要求 `allowedAuthMethods` 同时声明以下两个取值，不能只接受其中之一：

- `CRYPTOGRAM_3DS`：用户在设备上完成过 token 化的卡，token 含动态密文与 3DS 凭证，可直接授权。
- `PAN_ONLY`：只保存在 Google 账户、未在设备上 token 化的卡，token 不含 3DS 凭证，扣款前需要补采 CVC，处理方式见「PAN_ONLY token 的两条路径」。

两个取值都会在真实流量中出现，`PAN_ONLY` 不声明就无法向这部分用户收款。`PAN_ONLY` 也不意味着商户要接触卡号：Onerway 代解密模式下 token 以 Onerway 密钥加密，卡号不进商户系统；标准路径的 CVC 由 Onerway 页面采集，商户无需 PCI DSS 资质。只有选择商户自解密或商户自行采集 CVC 时，才涉及 PCI DSS。

### 解密模式

| 模式 | 前端 tokenization | 提交给 Onerway | 前提 |
| --- | --- | --- | --- |
| **Onerway 代解密**（推荐） | `PAYMENT_GATEWAY`，网关参数取查询可用支付方式的返回值 | `tokenInfo`，token 原样透传 | 无 |
| **商户自解密** | `DIRECT`，`ECv2` 加商户在自己的 Google Pay & Wallet Console 注册的公钥 | 不传 `tokenInfo`，解密结果放入 `cardInfo` | 须满足 PCI DSS |

商户自解密时，解密结果按下表放入 [`cardInfo`](/zh/payments/api-reference/endpoints/direct-create-transaction#request-cardInfo)：

| 解密后的 Google 字段 | `cardInfo` 字段 |
| --- | --- |
| `pan` | `cardNumber` |
| `expirationMonth`、`expirationYear` | `month`、`year` |
| `authMethod` | `wallet.googlePay.authMethod` |
| `cryptogram`、`eciIndicator`（仅 `CRYPTOGRAM_3DS`） | `cryptogram`、`eci` |

### 商户标识

Google 规定 `environment` 为 `PRODUCTION` 时 `merchantInfo.merchantId` 必填（`TEST` 可省略），取值取决于域名报备方式，二选一：

- 由 Onerway 在其 Google Pay & Wallet Console 档案下报备商户域名：用查询可用支付方式返回的 `merchantId`。
- 商户自行注册 Google Pay & Wallet Console 并登记域名：用自己的商户标识。商户自解密只能选这种方式。

向 Onerway 技术支持确认使用的账号后，完成[上线前网站报备](#%E4%B8%8A%E7%BA%BF%E5%89%8D%E7%BD%91%E7%AB%99%E6%8A%A5%E5%A4%87)。

## 上线前网站报备

本要求仅适用于 **API 直连**；收银台和 Web SDK 使用 Onerway 的 Google Pay 能力，无需报备网站。

接受生产环境支付前，需完成网站报备并获得 Google 审批通过，沙盒测试成功不能替代。按使用的 Google Pay 账号，参照 Google 官方[发布集成指南](https://developers.google.com/pay/api/web/guides/test-and-deploy/publish-your-integration)完成：

| Google Pay 账号 | 报备主体 | 商户需要做的 |
| --- | --- | --- |
| 商户自有账号 | 商户 | 在自己的 Google Pay & Wallet Console 添加网站，提供域名与集成截图并提交审批。 |
| Onerway 账号 | Onerway | 将域名和下述五张截图提交给 Onerway 技术支持，由 Onerway 代为报备并通知结果。 |

域名即实际调用 Google Pay API 的网站。账号只决定 [`merchantInfo.merchantId`](#%E5%95%86%E6%88%B7%E6%A0%87%E8%AF%86) 的取值：使用自有账号不要求自行解密 token；由 Onerway 解密时，`gatewayName` 和 `gatewayMerchantId` 仍取[查询可用支付方式](/zh/payments/api-reference/endpoints/list-available-payment-methods)的返回值。

### Onerway 代报备所需的五张截图

按购买流程的五个阶段各提供一张截图：

| 截图 | 需要展示的内容 |
| --- | --- |
| 商品选择页（Item selection） | 用户浏览商品或服务。 |
| 购买前页面（Pre-purchase screen） | 用户准备进行购买。 |
| 支付方式页面（Payment method screen） | 用户选择 Google Pay 作为支付方式。 |
| Google Pay 支付面板（Google Pay API payment screen） | Google Pay 面板显示用户已保存的支付信息。 |
| 购买完成页面（Post-purchase screen） | 用户成功购买后显示的页面。 |

Android 无法截取 Google Pay 支付面板时，可用另一台设备拍照；仅该项也接受错误信息的截图或照片。

### 审批通过后切换生产环境

Google 审批通过后，配置报备账号对应的 [`merchantInfo.merchantId`](#%E5%95%86%E6%88%B7%E6%A0%87%E8%AF%86)，创建 `PaymentsClient` 时将 `environment` 设为 `PRODUCTION`，并改用 Onerway 生产环境 API 地址和凭证创建交易。

## API 直连接入

<steps level="3">

### 获取 Onerway 配置

服务端调用[查询可用支付方式](/zh/payments/api-reference/endpoints/list-available-payment-methods)，取 `paymentMethod=GooglePay` 的记录，把配置返回前端。配置可缓存，变更时刷新。

| 查询返回字段 | Google Pay 参数 |
| --- | --- |
| `gatewayName` | `tokenizationSpecification` 的 `gateway` |
| `gatewayMerchantId` | `tokenizationSpecification` 的 `gatewayMerchantId` |
| `subCardTypes` | `allowedCardNetworks`，已是 Google 要求的全大写，原样透传 |
| `merchantId` | `merchantInfo.merchantId`（按「商户标识」一节二选一） |
| `countryCode` | `transactionInfo.countryCode` |

### 初始化并展示按钮

加载 Google Pay JS SDK，创建 `PaymentsClient`，用 `isReadyToPay()` 做能力检测，通过后 `createButton()` 渲染官方按钮。

```html
<script async src="https://pay.google.com/gp/p/js/pay.js" onload="onGooglePayLoaded()"></script>
```

```js
const paymentsClient = new google.payments.api.PaymentsClient({ environment: 'TEST' }) // 生产改为 'PRODUCTION'
```

### 发起支付

点击按钮时构造 `PaymentDataRequest`，调用 `loadPaymentData()`，从返回结果的 `tokenizationData.token` 取出 token 交给服务端。请求至少包含 `apiVersion: 2`、含 tokenization 参数的 `allowedPaymentMethods`、`transactionInfo`（`totalPriceStatus` 取 `FINAL`，以及 `totalPrice`、`currencyCode`、`countryCode`）与 `merchantInfo`。可按需开启 `emailRequired`、`shippingAddressRequired` 让 Google Pay 面板采集联系信息。

### 创建交易

服务端调用[创建直连交易](/zh/payments/api-reference/endpoints/direct-create-transaction)（`productType=CARD`、`subProductType=DIRECT`、`txnType=SALE`），按解密模式提交 `tokenInfo` 或 `cardInfo`；完整报文见该接口的「Onerway 代解密 Google Pay 支付」「商户自解密 Google Pay CRYPTOGRAM_3DS 支付」示例。

### 处理同步响应

`respCode` 不为 `20000` 为失败；`data.status=S` 为成功；`data.status=R` 且 `actionType=RedirectURL` 表示 token 为 `PAN_ONLY`、需要补采 CVC，前端立即把用户跳转到 `redirectUrl`（Onerway 页面），用户输入后回跳 `txnOrderMsg.returnUrl`；此时响应中的 `transactionId` 为 `null`，只能以 `merchantTxnId` 关联订单。其余状态为处理中，等待通知。

### 确认最终结果

最终状态以[支付结果通知](/zh/payments/api-reference/webhooks/payment-result)为准，钱包交易的通知带 `walletTypeName=GooglePay`；校验通知中的金额、币种与商户订单一致。验签、应答与幂等处理见 [Webhook 通知](/zh/payments/get-started/webhooks)。

</steps>

## PAN_ONLY token 的两条路径

| 对比项 | 标准路径 | 商户自行采集 CVC |
| --- | --- | --- |
| CVC 采集 | Onerway 页面 | 商户自建输入框 |
| 用户体验 | 一次跳转与回跳 | 无跳转 |
| 实现 | 只需处理 `status=R` 跳转 | 多一次检查接口调用与 CVC 输入界面 |
| CVC 安全责任 | Onerway | 商户：不存储、不落日志、HTTPS 传输、用后即清 |

两条路径都无需额外开通。

商户自行采集 CVC 的流程：

- 取得 token 后先调用[检查 Google Pay PAN_ONLY token](/zh/payments/api-reference/endpoints/check-google-pay-pan-only)。
- `checkResult=false`：向用户采集 CVC，创建直连交易时传同一个 `tokenInfo`，并在 `cardInfo.cvv` 提交 CVC。提交 `PAN_ONLY` token 时，若未传 `cardInfo.cvv`，交易响应返回 `data.status=R` 和 `data.redirectUrl`；商户需引导用户跳转至 `data.redirectUrl`，在 Onerway 托管页面输入 CVC。是否在创建交易前调用检查接口不影响此行为。
- `checkResult=true`：通过同一个 `tokenInfo` 创建直连交易，无需提交 `cardInfo`。
- 检查与下单必须使用同一个 token 与同一个 `merchantTxnId`；检查接口失败时回退到标准路径。
- CVC 输入框按卡组织要求的位数实时校验（多数卡组织 3 位，American Express 4 位）、以密码类型显示，并向用户说明为何需要输入。

## 前端示例

配置从服务端取得后填入，`processPayment` 把 token 交给服务端。

```html
<div id="container"></div>
<script>
  // 以下配置来自服务端调用查询可用支付方式返回的 GooglePay 记录
  const onerwayConfig = {
    gateway: '<gatewayName>',
    gatewayMerchantId: '<gatewayMerchantId>',
    allowedCardNetworks: ['MASTERCARD', 'VISA'],
    merchantId: '<merchantId>',
    countryCode: 'US'
  }

  const baseRequest = { apiVersion: 2, apiVersionMinor: 0 }
  const baseCardPaymentMethod = {
    type: 'CARD',
    parameters: { allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'], allowedCardNetworks: onerwayConfig.allowedCardNetworks }
  }
  const cardPaymentMethod = {
    ...baseCardPaymentMethod,
    tokenizationSpecification: {
      type: 'PAYMENT_GATEWAY',
      parameters: { gateway: onerwayConfig.gateway, gatewayMerchantId: onerwayConfig.gatewayMerchantId }
    }
  }

  let paymentsClient = null
  function getPaymentsClient() {
    if (!paymentsClient) {
      paymentsClient = new google.payments.api.PaymentsClient({ environment: 'TEST' }) // 生产改为 'PRODUCTION'
    }
    return paymentsClient
  }

  function getPaymentDataRequest() {
    return {
      ...baseRequest,
      allowedPaymentMethods: [cardPaymentMethod],
      transactionInfo: { countryCode: onerwayConfig.countryCode, currencyCode: 'USD', totalPriceStatus: 'FINAL', totalPrice: '99.99' },
      merchantInfo: { merchantId: onerwayConfig.merchantId, merchantName: 'Example Store' }
    }
  }

  function onGooglePayLoaded() {
    getPaymentsClient()
      .isReadyToPay({ ...baseRequest, allowedPaymentMethods: [baseCardPaymentMethod] })
      .then((res) => {
        if (!res.result) return
        const button = getPaymentsClient().createButton({ onClick: onButtonClicked, allowedPaymentMethods: [baseCardPaymentMethod] })
        document.getElementById('container').appendChild(button)
      })
  }

  function onButtonClicked() {
    getPaymentsClient()
      .loadPaymentData(getPaymentDataRequest())
      .then(processPayment)
      .catch(() => {
        // 用户关闭了 Google Pay 面板或支付失败，恢复页面交互
      })
  }

  async function processPayment(paymentData) {
    // 不要记录或存储 token
    const paymentToken = paymentData.paymentMethodData.tokenizationData.token
    // 服务端调用创建直连交易（tokenInfo.provider=GooglePay），把 respCode 非 20000 映射为 status 'F'，返回 { status, redirectUrl }
    const res = await fetch('/api/google-pay/process-payment', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ paymentToken })
    }).then(r => r.json())

    if (res.status === 'R' && res.redirectUrl) {
      window.location.href = res.redirectUrl // PAN_ONLY：跳转 Onerway 页面采集 CVC，最终状态以通知为准
    } else if (res.status === 'S') {
      // 同步成功，仍以支付结果通知为准
    } else if (res.status === 'F') {
      // 失败，提示用户更换支付方式
    } else {
      // 处理中，等待支付结果通知
    }
  }
</script>
<script async src="https://pay.google.com/gp/p/js/pay.js" onload="onGooglePayLoaded()"></script>
```

Google 官方资源：[Web 集成文档](https://developers.google.com/pay/api/web)、[交互式演示](https://developers.google.com/pay/api/web/guides/resources/demos)、[品牌指南](https://developers.google.com/pay/api/web/guides/brand-guidelines)。

## 钱包订阅

Google Pay 可用于订阅：初始订阅时传 `subProductType=SUBSCRIBE` 与 `subscription`，`selfExecute=1`（Onerway 托管）与 `selfExecute=2`（商户自行发起每期扣款）均支持。收银台路径以 `lpmsInfo.lpmsType=GooglePay` 锁定 Google Pay；API 直连路径以 `tokenInfo` 提交钱包加密 token 发起初始订阅。初始订阅成功后，[订阅扣款通知](/zh/payments/api-reference/webhooks/subscription-payment)返回 `contractId` 与订阅 `tokenId`，后续续费与升降级与卡订阅相同，见[订阅支付](/zh/payments/online-payments/scenarios/subscriptions)。

与卡订阅的差异：钱包加密 token 是一次性的，不能保存复用，订阅期间的扣款凭证只有订阅 token；钱包订阅不产生保存支付方式结果通知，也不返回卡 token。

## 常见问题

| 现象 | 常见原因 | 处理 |
| --- | --- | --- |
| 按钮不显示 | 未加载 `pay.js`、非 HTTPS、账户无可用卡、App 内嵌 WebView 不满足条件 | 检查 `isReadyToPay()` 返回值；WebView 见 [Web SDK 接入](/zh/payments/online-payments/sdk#%E5%9C%A8-app-%E5%86%85%E5%B5%8C-webview-%E4%B8%AD%E4%BD%BF%E7%94%A8-google-pay) |
| 配置获取失败 | 服务端未返回 `gatewayName`、`gatewayMerchantId` 或 `subCardTypes` | 检查查询可用支付方式的调用与筛选 |
| 生产环境弹窗报商户未验证 | `merchantInfo.merchantId` 缺失或与域名报备方式不匹配 | 按「商户标识」一节确认取值来源 |
| PAN_ONLY 未处理 | 创建直连交易返回 `redirectUrl` 但前端未跳转 | 收到 `redirectUrl` 立即跳转，或改用商户自行采集 CVC 路径 |

## 上线前检查

- API 直连：已完成[网站报备](#%E4%B8%8A%E7%BA%BF%E5%89%8D%E7%BD%91%E7%AB%99%E6%8A%A5%E5%A4%87)并获得 Google 审批通过。
- 不在客户端解密 token，不记录、不存储 token。
- 已用真实卡验证 `PAN_ONLY` 跳转与 `CRYPTOGRAM_3DS` 直接成功两条路径。
- 采用商户自行采集 CVC 路径时，已验证检查接口失败时的回退。
- Webhook 验签、幂等与金额校验已实现。
