前端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 Pay this.isApplePaySupported = false return } 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 = false console.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.applyPayRes if(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 权限异常