前端ApplePay支付-H5全流程实战指南
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
近期公司开展关于苹果支付的相关业务,与之前不同的是,以前后台直接获取第三方Wallet封装好的接口获取支付地址,H5页面直接跳转使用ApplePay支付就行了,相当于第三方直接和银行达成合作;如今,需要我们和银行合作,自己去拉取ApplePay支付。没错,又是一个学习和摸索的一个阶段,哈哈哈哈,如果有不对的地方,欢迎大家指正!!
一、前期工作
注册苹果开发者账号
创建一个AppId
完成商户验证,配置商户号:银行需与 Apple 交换加密证书,用于验证支付请求的合法性和安全性,同时配置商户号Merchant Identity并绑定支付的域名。
生产环境必须使用HTTPS,沙盒可以使用HTTP,但商户验证仍需HTTPS
PS:银行提供支付处理证书,使用该证书提交到Apple开发者后台用于获取商户身份证书。
具体包括:
- 商户身份证书(Merchant Identity Certificate):由银行协助商户在 Apple 开发者后台申请,用于前端唤起 Apple Pay 时的身份验证。
支付处理证书(Payment Processing Certificate):银行需支持解密 Apple Pay 生成的支付令牌(Token),通常由银行提供证书或密钥给商户的支付服务商。
二、前端后端支付流程
1.初始校验
作用:验证是否存在ApplePay内核,存在则显示ApplePay支付方式
内核主要是唤起 Apple Pay 界面、收集用户支付信息(如银行卡、金额)、生成加密的 “支付令牌(Payment Token)” 并传递给后端。
// 检验ApplePay环境 - 针对存在applePay内核时,才显示ApplePay支付方式
async checkApplePaySupport() {if (!window.ApplePaySession) {//浏览器不支持 Apple Paythis.isApplePaySupported = falsereturn}try {// 检测当前设备是否支持 Apple Pay 功能const canMakePayments = await ApplePaySession.canMakePayments(); // 用于检查设备是否支持 Apple Pay 并且 用户已添加至少一张有效支付卡。const canMakePaymentsWithActiveCard = await ApplePaySession.canMakePaymentsWithActiveCard(this.merchantIdentifier); if (canMakePayments && canMakePaymentsWithActiveCard) {this.isApplePaySupported = true} else if (!canMakePayments) { // 设备不支持 Apple Paythis.isApplePaySupported = falseconsole.log('设备不支持 Apple Pay')} else { // 未添加支付有效卡this.isApplePaySupported = trueconsole.log('未添加支付有效卡')}} catch (error) {// 检查失败: ${error.message}`this.isApplePaySupported = falseconsole.log(error.message)}// console.log('是否支持Apple',this.isApplePaySupported)// // 测试支持的支付网络 - 低版本不支持-所以干脆去掉了// ApplePaySession.getApplePayCapability({// merchantIdentifier: this.merchantIdentifier,// supportedNetworks: ['visa', 'masterCard', 'chinaUnionPay', 'amex', 'jcb']// })// .then(capabilities => console.log('支持的网络:', capabilities.supportedNetworks))// .catch(error => console.error('检测失败:', error));
},
2. 商户验证流程
- 前端创建会话
ApplePaySession,
设置会话最长等待时长 - 前端触发
onvalidatemerchant
商户验证事件 - 前端将获取到的
validationURL
通过后端提供的商户验证接口传递给后端 - 后端使用
validationURL
与商户证书向 Apple 服务器请求商户信息merchantSession
- 后端返回
merchantSession
给前端,确保merchantIdentifier
与前端一致(返回的MID可能是hash字符串) - 前端调用
session.completeMerchantValidation
完成商户验证
3. 支付验证流程
- 用户授权支付后,触发
onpaymentauthorized
事件 - 前端将支付令牌
paymentToken
发送到后端 - 后端将支付令牌转发给支付网关(如 Stripe、银联)
- 后端返回支付结果给前端
- 前端调用
session.completePayment
通知 Apple Pay 支付状态
用户授权支付动作:指的是输入了支付密码这个动作,当初因为测试机没有卡,一直无法触发支付验证事件,找了半天原因。
// ApplePay支付
applePayClicked() {this.session = null// 预支付信息const paymentRequest = {countryCode: 'HK',// 交易的国家currencyCode: 'HKD',// 货币supportedNetworks: ['visa', 'masterCard', 'chinaUnionPay', 'amex', 'jcb'],// supportedNetworks 列出支持的卡 chinaUnionPay 银联merchantCapabilities: ['supports3DS'],// 支持的支付特性total: { label: 'XXX電子支付', // 支付的标签和金额amount: 100.00,type: 'final' },}// 1.创建ApplePay支付会话this.session = new window.ApplePaySession(3, paymentRequest);// 设置会话最长等待时间this.session.timeoutInterval = 120; // 单位:秒// 2.触发商户验证事件 - 获取validationURL,传递给后台this.session.onvalidatemerchant = (event) => {this.validateMerchant(event.validationURL)};// 3.支付授权事件 - 用户授权支付后,触发 onpaymentauthorized 事件this.session.onpaymentauthorized = async(event) => {console.log('=== 支付授权 ===');this.info = event // 3.1 获取paymentTokenlet paymentToken = event.payment.token.paymentData; this.paymentToken = JSON.stringify(paymentToken) || ''// 3.2 前端将支付令牌paymentToken+订单信息发送到后端 let initPayResult = await this.goPay();if(initPayResult =='fail'){ // this.goPay() 提交订单信息,接口返回错误时,关闭当前会话this.session.completePayment(window.ApplePaySession.STATUS_FAILURE);return}// 3.3 轮询订单查询接口try{ this.applyPayRes = searchApi({orderId:this.id});let res = await this.applyPayResif(res.status===0){ // 3.4 查询到订单结果 - 需要completePayment 通知 Apple Pay 支付状态await this.session.completePayment(window.ApplePaySession.STATUS_SUCCESS); // 必须调用!!!// 跳转成功界面uni.navigateTo({url:'XXXXXURL',})}else{await this.session.completePayment(window.ApplePaySession.STATUS_FAILURE);this.openPopup('該訂單查詢結果失敗')}}catch(error){console.log('支付報錯',error)this.applyPayRes = null;this.session.completePayment(window.ApplePaySession.STATUS_FAILURE);}finally{ // 轮询完成或失败时才重置实例this.applePayRes = null}};// 4.错误事件this.session.onerror = (error) => {console.error('Apple Pay 错误:', error);this.openPopup('支付错误: ${error.message}')};// 5.取消事件this.session.oncancel = () => {console.log('用户取消支付');this.cancelPolling() // 取消輪詢 this.session = null;};// 启动支付流程this.session.begin();
},
// 2.1 ApplePay商户验证(调用后端 API)- 实现商户验证逻辑
validateMerchant(validationURL) {return new Promise((resolve, reject) => {uni.request({url: '/XXXX/validate',method: 'POST',data: { validationURL },success: (res) => {if (res.data.success) { // res.data.success =truelet merchantSession = res.data.dataif (this.validateMerchantSession(merchantSession)) { // 校验接口返回字段// 2.3 完成商户验证this.session.completeMerchantValidation(merchantSession);resolve(merchantSession);} else {reject(new Error('商户会话数据无效'));}} else {reject(new Error(res.data.message));}},fail: (err) => {console.error('商户验证请求失败:', err);reject(err);}});});
},// 2.2 验证商户会话接口返回数据
validateMerchantSession(sessionData) {const requiredFields = ['signature', 'merchantIdentifier', 'domainName', 'expiresAt'];for (const field of requiredFields) {if (!sessionData[field]) {console.error(`缺少必需字段: ${field}`);return false;}}// 验证域名一致性if (sessionData.domainName !== window.location.hostname) {console.error(`域名不匹配: ${sessionData.domainName} vs ${window.location.hostname}`);return false;}// 验证有效期if (sessionData.expiresAt < Date.now()) {console.error('商户会话已过期');return false;}return true;
}
三、调试与部署注意事项
1.证书配置
- 确保后端持有有效的 Apple Pay 商户证书和私钥
- 证书需与商户 ID 和域名匹配
2.HTTPS要求
- 生产环境必须使用 HTTPS
- 沙盒环境可以使用 HTTP,但商户验证仍需 HTTPS
3.地区与货币
- 针对香港或内地用户,对应的国家地区countryCode与货币currencyCode是不一样的,香港countryCode:'HK',currencyCode:'HKD';而澳门是'MO' 与'MOP'
- 确保支付网关支持香港或内地地区的货币和卡组织
- PS:一定要注意卡卡卡!!!我们做的香港业务,内地支付卡是无效的!所以需要包含对应地区的支付卡,哪怕内地卡是可以输入密码,但仍然无法获取后台所需要的paymentToken参数,甚至就跟陷入无限循环一样,不扣钱但也无法获取参数。
4.商户ID一致性
- 确保
merchantIdentifier
与证书中的商户 ID 一致
商户Id如果不正确会导致拉取ApplePay会话后,可进行商户验证,但无法触发支付验证并且界面立刻关闭。后端在商户验证的接口中返回merchantIdentifier
是一串hash数字,经过加密的,无需前端处理,直接将获取得到的数据进行商户校验即可。
5.使用completePayment 告知ApplePay支付结果
支付查询到结果后使用completePayment告知ApplePay会话结果;如果不调用会导致会话持续处于 "处理中" 状态。
我就是没调用告知结果,付完钱一直搁那溜溜转了很久,最终界面显示取消支付,立刻关闭。
四、报错记录:
1. payment Services Exception merchantId=XXX not registered for domain=XXX 报错
- 商户 ID 未注册:商户 ID(XXX)可能未在支付服务提供商处完成注册或激活。
- 域名未配置:支付服务提供商可能未将
https://xxx.com
添加到该商户 ID 的允许域名列表中。 - 配置延迟:新注册的商户 ID 或域名配置可能需要时间生效(例如,需等待几分钟至几小时)。
- 域名不匹配:检查 URL 是否包含端口号、子域名等额外信息。
- 测试环境与生产环境混淆:确认使用的商户 ID 和 API 密钥对应正确的环境(测试 / 生产)。
2. ApplePay报错:must create a new ApplePaySession from a user gesture handler;
严格的用户手势要求:Apple Pay 会话必须直接在用户点击事件的处理函数中创建,不能通过 Promise、setTimeout、await 或其他异步方式延迟创建。(我就是在点击时先进行了订单查询的动作,再去创建ApplePay会话,存在异步操作,导致ApplePay会话都不创建了!!!)
避免预创建会话:不要在页面加载时或其他非交互时机创建
ApplePaySession
实例。验证用户交互:确保你的按钮或其他交互元素确实被用户点击后才触发会话创建。
3.商户验证后会话立即关闭,提示未完成付款
1. 支付请求参数配置错误
countryCode
与currencyCode
不匹配(如使用HK
国家代码但货币为CNY
)supportedNetworks
中无用户已添加的有效卡类型total
金额格式或精度不符合要求
2. 商户验证流程问题
- 商户证书与实际域名不匹配
merchantSession
中的签名或时间戳无效- 后端验证接口响应超时
3. 设备或用户环境问题
- 设备未添加有效支付卡
- 支付卡已过期或被冻结
- iOS 设置中的 Apple Pay 权限异常