Apple Pay
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 SDK | SDK 在商户页面内渲染并驱动,外观见配置钱包按钮 | 不经过商户系统 | 完成域名验证;结果通过 payment_result 接收,见 SDK 自有按钮 |
| API 直连 | 商户自建按钮与 ApplePaySession | Onerway 代解密或商户自解密 | 完成域名验证;本页「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 session | Onerway 代解密:支付处理证书由 Onerway 生成 CSR、商户上传到自己的 Merchant ID | 绝大多数商户 |
| 商户自解密 | 同默认 | 商户自解密:自持支付处理证书,解密结果放入 cardInfo,须满足 PCI DSS | 已具备 PCI DSS 与证书管理能力 |
| Onerway 代理 | 域名注册在 Onerway 的 Apple Developer 账号下,商户调用 Apple Pay 商户验证换取 merchant session | Onerway 代解密 | 没有或不想维护 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
商户自有账号
- 创建 Merchant ID:登录 Apple Developer,在 Certificates, Identifiers & Profiles 的 Merchant IDs 中新建,填写 Description 与 Identifier(如
merchant.com.yourcompany.appname)。已有可用 Merchant ID 可跳过。 - 配置支付处理证书:默认层级先把 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 并保管私钥。 - 配置 Merchant Identity 证书(仅 API 直连需要):在 Apple Pay Merchant Identity Certificate 中按 Apple 的 CSR 指南创建并下载,安全存放在商户验证服务端,用于向 Apple 请求 merchant session。
- 域名验证:在 Merchant Domains 中添加域名,下载验证文件并部署到上述路径,确认 HTTPS 可直接访问后在 Apple Developer 中点击 Verify。域名验证随网站 SSL 证书一起到期:Apple 会在证书到期前 30、15、7 天回查,提前续期 SSL 证书即可自动保持验证;若证书过期后才更换,需重新验证域名。支付处理证书与 Merchant Identity 证书各 25 个月到期,Merchant ID 不过期。
Onerway 代理
- 向 Onerway 技术支持提供需要展示 Apple Pay 按钮的全部域名,含子域名,沙盒与生产域名都要提供,格式
https://your-store.com。 - Onerway 完成注册后返回验证文件,商户部署到上述路径并确认可访问:
curl -I https://your-store.com/.well-known/apple-developer-merchantid-domain-association
- 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 字段 |
|---|---|
applicationPrimaryAccountNumber | cardNumber |
applicationExpirationDate(YYMMDD) | month 取第 3、4 位;year 取前两位并补全为四位,如 30 对应 2030 |
onlinePaymentCryptogram | cryptogram |
eciIndicator | eci |
paymentDataType | wallet.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 验签与幂等已实现。