Onerway
支付方式

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商户需要做的
收银台收银台渲染按钮,token 不经过商户系统下单传 productType=ALL,或传 lpmsInfo.lpmsType=GooglePay 锁定 Google Pay
Web SDKSDK 在商户页面内渲染按钮,外观见配置钱包按钮;token 不经过商户系统结果通过 payment_result 接收,见 SDK 自有按钮;App 内嵌 WebView 的限制见 WebView 说明
API 直连商户加载 Google Pay JS SDK 并取得 token本页「API 直连接入」全部步骤

三种接入方式都要求网站 HTTPS 且 Onerway 商户账号已开通 Google Pay;浏览器与设备支持范围以 Google Pay Web 文档为准,沙盒测试用 Google 账号加入测试卡群组,见测试卡。

接入准备

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:

解密后的 Google 字段cardInfo 字段
pancardNumber
expirationMonth、expirationYearmonth、year
authMethodwallet.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 技术支持确认使用的账号后,完成上线前网站报备。

上线前网站报备

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

接受生产环境支付前,需完成网站报备并获得 Google 审批通过,沙盒测试成功不能替代。按使用的 Google Pay 账号,参照 Google 官方发布集成指南完成:

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

域名即实际调用 Google Pay API 的网站。账号只决定 merchantInfo.merchantId 的取值:使用自有账号不要求自行解密 token;由 Onerway 解密时,gatewayName 和 gatewayMerchantId 仍取查询可用支付方式的返回值。

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,创建 PaymentsClient 时将 environment 设为 PRODUCTION,并改用 Onerway 生产环境 API 地址和凭证创建交易。

API 直连接入

获取 Onerway 配置

服务端调用查询可用支付方式,取 paymentMethod=GooglePay 的记录,把配置返回前端。配置可缓存,变更时刷新。

查询返回字段Google Pay 参数
gatewayNametokenizationSpecification 的 gateway
gatewayMerchantIdtokenizationSpecification 的 gatewayMerchantId
subCardTypesallowedCardNetworks,已是 Google 要求的全大写,原样透传
merchantIdmerchantInfo.merchantId(按「商户标识」一节二选一)
countryCodetransactionInfo.countryCode

初始化并展示按钮

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

<script async src="https://pay.google.com/gp/p/js/pay.js" onload="onGooglePayLoaded()"></script>
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 面板采集联系信息。

创建交易

服务端调用创建直连交易(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 关联订单。其余状态为处理中,等待通知。

确认最终结果

最终状态以支付结果通知为准,钱包交易的通知带 walletTypeName=GooglePay;校验通知中的金额、币种与商户订单一致。验签、应答与幂等处理见 Webhook 通知。

PAN_ONLY token 的两条路径

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

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

商户自行采集 CVC 的流程:

  • 取得 token 后先调用检查 Google Pay PAN_ONLY token。
  • 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 交给服务端。

<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 集成文档、交互式演示、品牌指南。

钱包订阅

Google Pay 可用于订阅:初始订阅时传 subProductType=SUBSCRIBE 与 subscription,selfExecute=1(Onerway 托管)与 selfExecute=2(商户自行发起每期扣款)均支持。收银台路径以 lpmsInfo.lpmsType=GooglePay 锁定 Google Pay;API 直连路径以 tokenInfo 提交钱包加密 token 发起初始订阅。初始订阅成功后,订阅扣款通知返回 contractId 与订阅 tokenId,后续续费与升降级与卡订阅相同,见订阅支付。

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

常见问题

现象常见原因处理
按钮不显示未加载 pay.js、非 HTTPS、账户无可用卡、App 内嵌 WebView 不满足条件检查 isReadyToPay() 返回值;WebView 见 Web SDK 接入
配置获取失败服务端未返回 gatewayName、gatewayMerchantId 或 subCardTypes检查查询可用支付方式的调用与筛选
生产环境弹窗报商户未验证merchantInfo.merchantId 缺失或与域名报备方式不匹配按「商户标识」一节确认取值来源
PAN_ONLY 未处理创建直连交易返回 redirectUrl 但前端未跳转收到 redirectUrl 立即跳转,或改用商户自行采集 CVC 路径

上线前检查

  • API 直连:已完成网站报备并获得 Google 审批通过。
  • 不在客户端解密 token,不记录、不存储 token。
  • 已用真实卡验证 PAN_ONLY 跳转与 CRYPTOGRAM_3DS 直接成功两条路径。
  • 采用商户自行采集 CVC 路径时,已验证检查接口失败时的回退。
  • Webhook 验签、幂等与金额校验已实现。