提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
近期公司开展关于苹果支付的相关业务,与之前不同的是,以前后台直接获取第三方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 Pay
this.isApplePaySupported = false
console.log('设备不支持 Apple Pay')
} else { // 未添加支付有效卡
this.isApplePaySupported = true
console.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 获取paymentToken
let 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 =true
let merchantSession = res.data.data
if (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 权限异常