Onerway
支付方式

Apple Pay

Apple Pay 在收银台、Web SDK 与 API 直连下的接入差异、域名验证与账号配置、API 直连的会话流程与 token 提交方式、钱包订阅,以及常见问题。

Apple Pay 让客户用 Wallet 中已添加的卡,通过 Face ID 或 Touch ID 完成支付。收银台与 Web SDK 由 Onerway 渲染 Apple Pay 按钮并处理 token,商户只需完成域名报备;API 直连由商户自建按钮、驱动 ApplePaySession,并把 Apple 返回的加密 payment token 提交给 Onerway。

接入方式选择

接入方式按钮与会话token 处理商户需要做的
收银台收银台渲染并驱动不经过商户系统下单传 productType=ALL,或传 lpmsInfo.lpmsType=ApplePay 锁定 Apple Pay
Web SDKSDK 在商户页面内渲染并驱动,外观见配置钱包按钮不经过商户系统完成域名验证;结果通过 payment_result 接收,见 SDK 自有按钮
API 直连商户自建按钮与 ApplePaySessionOnerway 代解密或商户自解密完成域名验证;本页「API 直连接入」全部步骤

收银台是 Onerway 托管页面,域名已由 Onerway 向 Apple 报备,商户无需再做域名验证。Web SDK 与 API 直连在商户自己的域名下展示 Apple Pay 按钮,Apple 要求该域名先通过验证,见「接入准备」。

三种接入方式都要求网站全站 HTTPS、TLS 1.2 及以上,且 Onerway 商户账号已开通 Apple Pay。沙盒测试前提与测试卡见 Apple Pay 沙盒测试。

浏览器与设备支持

Apple Pay on the Web 不再只限于 Safari:

  • Safari,以及 iOS / iPadOS 上的第三方浏览器(同为 WebKit 内核):在设备上直接完成支付。
  • Mac、Windows 与其他设备上的兼容第三方浏览器:页面显示二维码,客户用 iOS 18 / iPadOS 18 及以上的 iPhone 或 iPad 扫码完成支付。API 直连商户需要使用 Apple Pay JS SDK 1.2.0 及以上提供的 <apple-pay-button> 元素,CSS 方式渲染的按钮不支持非 Safari 浏览器。
  • 中国大陆:仅支持 iPhone / iPad 上的 Safari,第三方浏览器不可用。

收银台与 Web SDK 由 Onerway 处理以上差异;API 直连商户按本页示例使用官方 SDK 与按钮元素即可获得同样的覆盖范围。以 Apple 官方说明为准。

接入准备

Apple 要求每个展示 Apple Pay 按钮的商户域名都完成域名验证,验证依附于某个 Apple Developer 账号的 Merchant ID。按谁持有 Merchant ID,接入准备分三个层级:

层级域名验证与商户验证token 解密适用
默认商户自有 Apple Developer 账号:自建 Merchant ID、自行完成域名验证,用自持 Merchant Identity 证书向 Apple 请求 merchant sessionOnerway 代解密:支付处理证书由 Onerway 生成 CSR、商户上传到自己的 Merchant ID绝大多数商户
商户自解密同默认商户自解密:自持支付处理证书,解密结果放入 cardInfo,须满足 PCI DSS已具备 PCI DSS 与证书管理能力
Onerway 代理域名注册在 Onerway 的 Apple Developer 账号下,商户调用 Apple Pay 商户验证换取 merchant sessionOnerway 代解密没有或不想维护 Apple Developer 账号的商户

Web SDK 只涉及域名验证,按默认层级或 Onerway 代理层级完成即可。不要把商户自有 Merchant ID 与 Onerway 代理验证混用。

商户自有账号与 Onerway 代理两种域名验证方式,验证文件都部署在网站的 .well-known 路径下(以 Apple Developer 后台或 Onerway 提供的路径为准),该地址不可位于代理或重定向之后,且须允许 Apple 验证服务器访问:

https://your-store.com/.well-known/apple-developer-merchantid-domain-association

商户自有账号

  1. 创建 Merchant ID:登录 Apple Developer,在 Certificates, Identifiers & Profiles 的 Merchant IDs 中新建,填写 Description 与 Identifier(如 merchant.com.yourcompany.appname)。已有可用 Merchant ID 可跳过。
  2. 配置支付处理证书:默认层级先把 Merchant ID 提供给 Onerway 技术支持,取得 Onerway 生成的 CSR。在该 Merchant ID 的 Apple Pay Payment Processing Certificate 中创建证书,「Will payments be processed exclusively in China mainland?」选 No,上传该 CSR。证书创建成功后下载 .cer 文件回传 Onerway:私钥由 Onerway 持有,收到证书后才能解密 Apple Pay token。商户自解密层级则由商户自行生成 CSR 并保管私钥。
  3. 配置 Merchant Identity 证书(仅 API 直连需要):在 Apple Pay Merchant Identity Certificate 中按 Apple 的 CSR 指南创建并下载,安全存放在商户验证服务端,用于向 Apple 请求 merchant session。
  4. 域名验证:在 Merchant Domains 中添加域名,下载验证文件并部署到上述路径,确认 HTTPS 可直接访问后在 Apple Developer 中点击 Verify。域名验证随网站 SSL 证书一起到期:Apple 会在证书到期前 30、15、7 天回查,提前续期 SSL 证书即可自动保持验证;若证书过期后才更换,需重新验证域名。支付处理证书与 Merchant Identity 证书各 25 个月到期,Merchant ID 不过期。

Onerway 代理

  1. 向 Onerway 技术支持提供需要展示 Apple Pay 按钮的全部域名,含子域名,沙盒与生产域名都要提供,格式 https://your-store.com。
  2. Onerway 完成注册后返回验证文件,商户部署到上述路径并确认可访问:
curl -I https://your-store.com/.well-known/apple-developer-merchantid-domain-association
  1. Onerway 通过 Apple 完成域名验证后通知商户。支付处理证书与 Merchant Identity 证书由 Onerway 集中持有并续期,商户无需管理。

无论哪个层级,证书都应存放在受控环境、最小权限访问,并监控 SSL 证书与 Apple 证书的到期时间。

API 直连接入

商户自建按钮并驱动 ApplePaySession,Apple 返回的加密 payment token 按解密模式提交给 Onerway。两种模式二选一,tokenInfo 与 cardInfo 不可同时提交。

Onerway 代解密(推荐,默认与 Onerway 代理层级):把 event.payment.token 整体序列化为字符串,原样放入 tokenInfo.tokenId,不要拆解其中的 paymentData、paymentMethod 与 transactionIdentifier。

"tokenInfo": "{\"provider\":\"ApplePay\",\"tokenId\":\"<event.payment.token 序列化后的字符串>\"}"

商户自解密(自解密层级,须满足 PCI DSS):不传 tokenInfo,把解密结果按下表放入 cardInfo。

解密后的 Apple 字段cardInfo 字段
applicationPrimaryAccountNumbercardNumber
applicationExpirationDate(YYMMDD)month 取第 3、4 位;year 取前两位并补全为四位,如 30 对应 2030
onlinePaymentCryptogramcryptogram
eciIndicatoreci
paymentDataTypewallet.applePay.paymentDataType,取 3DSecure 或 EMV,解密出哪种传哪种

完整报文见创建直连交易的「Onerway 代解密 Apple Pay 支付」与「商户自解密 Apple Pay 支付」示例。

前端先加载 Apple Pay JS SDK:推荐自动更新的 1.latest 地址;跨浏览器支持要求 1.2.0 及以上;固定版本时改用 v1.x.y 路径并加 integrity,1.latest 不支持 integrity。SDK 会在非 Safari 浏览器中注入 ApplePaySession。

能力检测并展示按钮

加载 Apple Pay JS SDK 后,仅当 ApplePaySession 存在且 canMakePayments() 为 true 时展示按钮。需要判断客户是否已有可用卡时用 applePayCapabilities(),它在非 Safari 浏览器只会返回 paymentCredentialStatusUnknown,此时仍应展示按钮;canMakePaymentsWithActiveCard() 已废弃。按钮使用 SDK 提供的 <apple-pay-button> 元素,样式属性见 Apple Pay 按钮文档,品牌规范见 Human Interface Guidelines。

<script src="https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js"></script>
<apple-pay-button buttonstyle="black" type="buy" locale="zh-CN"></apple-pay-button>

获取 Onerway 配置

服务端调用查询可用支付方式,取 paymentMethod=ApplePay 的记录,把 countryCode 与 subCardTypes 返回前端,分别作为支付请求的 countryCode 与 supportedNetworks;两者已按 Apple 要求的格式返回,原样透传。applePayCapabilities() 需要商户标识:自有账号层级用商户自己的 Merchant ID,Onerway 代理层级用查询可用支付方式返回的 merchantId。

创建支付会话

用 supportsVersion() 从高到低探测浏览器支持的最高版本,再创建 ApplePaySession 并调用 begin()。支付请求至少包含 countryCode、currencyCode、supportedNetworks、merchantCapabilities(含 supports3DS)与 total(label、amount、type: 'final'),amount 为字符串。

校验 validationURL 并换取 merchant session

在 onvalidatemerchant 事件中取 validationURL,校验其主机是 Apple 的验证网关域名(apple-pay-gateway.apple.com,中国大陆为 cn-apple-pay-gateway.apple.com,沙盒为对应的 -cert 域名),其他 URL 一律调用 abort(),再交给服务端。服务端转发前必须再次校验主机,不要只依赖前端校验,也不要硬编码验证地址。商户自有账号层级由服务端用 Merchant Identity 证书向 Apple 发起 mTLS 请求,请求体含 merchantIdentifier、displayName(稳定的店铺名,不要本地化或拼入订单号)、initiative 取 web、initiativeContext 取完整域名;Onerway 代理层级调用 Apple Pay 商户验证,并把响应中的 data 解析为对象。

完成商户验证

拿到 merchant session 后立即调用 completeMerchantValidation()。merchant session 只能使用一次,创建后 5 分钟过期,只在服务端即时请求,不要在客户端直接请求 Apple。

支付授权

在 onpaymentauthorized 事件中取 event.payment.token 交给服务端,服务端调用创建直连交易(productType=CARD、subProductType=DIRECT、txnType=SALE),按解密模式提交 tokenInfo 或 cardInfo。前端根据服务端结果调用 completePayment() 传入成功或失败状态,且必须恰好调用一次,否则 Apple Pay 面板会一直停留。

确认最终结果

同步响应 status=S 为成功、P 为处理中,最终状态以支付结果通知为准,钱包交易的通知带 walletTypeName=ApplePay。验签、应答与幂等处理见 Webhook 通知。

前端示例

服务端三个内部接口由商户实现,分别对应上面的获取配置、商户验证与支付授权三步。

<apple-pay-button id="applePayButton" buttonstyle="black" type="buy" locale="zh-CN" style="display:none;"></apple-pay-button>
<script src="https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js"></script>
<script>
  const button = document.getElementById('applePayButton')

  // 服务端调用查询可用支付方式,返回 { countryCode, subCardTypes }
  const fetchConfig = () => fetch('/api/apple-pay/config').then(r => r.json())
  // 服务端按接入层级换取 merchant session:自有账号用 Merchant Identity 证书直连 Apple,Onerway 代理调用 Apple Pay 商户验证接口
  const validateMerchant = (validationURL, website) =>
    fetch('/api/apple-pay/validate-merchant', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ validationURL, website })
    }).then(r => r.json())
  // 服务端调用创建直连交易(tokenInfo.provider=ApplePay),返回 { success: boolean }
  const processPayment = (paymentToken) =>
    fetch('/api/apple-pay/process-payment', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ paymentToken })
    }).then(r => r.json())

  // 只接受 Apple 的验证网关主机,覆盖中国大陆与沙盒域名
  const APPLE_PAY_GATEWAY = /^(cn-)?apple-pay-gateway(-[a-z0-9-]+)?\.apple\.com$/

  const highestSupportedVersion = () => {
    for (let version = 14; version >= 3; version -= 1) {
      if (ApplePaySession.supportsVersion(version)) return version
    }
    return 3
  }

  function startSession(config) {
    const paymentRequest = {
      countryCode: config.countryCode,
      currencyCode: 'USD',
      supportedNetworks: config.subCardTypes,
      merchantCapabilities: ['supports3DS'],
      total: { label: 'Example Store', amount: '99.99', type: 'final' }
    }
    const session = new ApplePaySession(highestSupportedVersion(), paymentRequest)

    session.onvalidatemerchant = async (event) => {
      // 服务端必须再校验一次,不要只依赖这里
      if (!APPLE_PAY_GATEWAY.test(new URL(event.validationURL).hostname)) {
        session.abort()
        return
      }
      try {
        const merchantSession = await validateMerchant(event.validationURL, window.location.hostname)
        session.completeMerchantValidation(merchantSession)
      } catch {
        session.abort()
      }
    }

    session.onpaymentauthorized = async (event) => {
      try {
        const result = await processPayment(event.payment.token)
        session.completePayment(result.success ? ApplePaySession.STATUS_SUCCESS : ApplePaySession.STATUS_FAILURE)
      } catch {
        session.completePayment(ApplePaySession.STATUS_FAILURE)
      }
    }

    session.oncancel = () => {
      // 用户关闭了 Apple Pay 面板,恢复页面交互
    }

    session.begin()
  }

  async function init() {
    if (!window.ApplePaySession || !ApplePaySession.canMakePayments()) return
    const config = await fetchConfig()
    button.style.display = 'block'
    button.addEventListener('click', () => startSession(config))
  }
  init()
</script>

Apple 官方交互式演示可体验完整流程。

钱包订阅

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

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

常见问题

现象常见原因处理
按钮不显示未加载 Apple Pay JS SDK 或使用了 CSS 按钮、非 HTTPS、设备或浏览器不支持、所在国家或地区不支持 Apple Pay使用 SDK 的 <apple-pay-button>;只在 canMakePayments() 为 true 时展示;非 Safari 浏览器下 applePayCapabilities() 返回未知状态属正常;提供其他支付方式
点击后面板一闪而过商户验证失败:validationURL 未通过校验、merchant session 超过 5 分钟或被复用、域名验证文件缺失或不可访问、网站 SSL 证书过期导致域名验证失效、initiativeContext 与验证域名不一致确认 completeMerchantValidation 被调用;用 curl -I 确认验证文件返回 200;核对域名一致;证书问题联系 Onerway 重新生成;沙盒与生产的商户标识不要混用
确认支付后页面提示未完成未调用或多次调用 completePayment();total.amount 不是字符串或精度错误;支付处理证书状态异常自检前端逻辑;仍失败时联系 Onerway 技术支持
沙盒测试卡被拒未使用 sandbox 测试账户、设备地区与测试卡卡组织不匹配、卡未添加到 Wallet按 Apple Pay 沙盒测试完成前提

联系 Onerway 技术支持时请提供商户号与接入层级、发生时间与环境(沙盒或生产)、设备、操作系统与浏览器版本、完整的控制台与网络日志、复现步骤。

上线前检查

  • 沙盒与生产域名已完成验证,验证文件可访问,SSL 证书续期已纳入监控。
  • merchant session 只在服务端请求,validationURL 在前端与服务端都已校验。
  • completePayment() 在成功与失败时都恰好调用一次。
  • 不记录、不存储 Apple Pay payment token。
  • Webhook 验签与幂等已实现。