提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

近期公司开展关于苹果支付的相关业务,与之前不同的是,以前后台直接获取第三方Wallet封装好的接口获取支付地址,H5页面直接跳转使用ApplePay支付就行了,相当于第三方直接和银行达成合作;如今,需要我们和银行合作,自己去拉取ApplePay支付。没错,又是一个学习和摸索的一个阶段,哈哈哈哈,如果有不对的地方,欢迎大家指正!!

一、前期工作

  1. 注册苹果开发者账号
  2. 创建一个AppId
  3. 完成商户验证,配置商户号:银行需与 Apple 交换加密证书,用于验证支付请求的合法性和安全性,同时配置商户号Merchant Identity并绑定支付的域名
  4. 生产环境必须使用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. 商户验证流程
  1. 前端创建会话 ApplePaySession,设置会话最长等待时长
  2. 前端触发onvalidatemerchant商户验证事件
  3. 前端将获取到的 validationURL 通过后端提供的商户验证接口传递给后端
  4. 后端使用validationURL与商户证书向 Apple 服务器请求商户信息 merchantSession
  5. 后端返回 merchantSession 给前端,确保 merchantIdentifier 与前端一致(返回的MID可能是hash字符串)
  6. 前端调用 session.completeMerchantValidation 完成商户验证
3. 支付验证流程
  1. 用户授权支付后,触发 onpaymentauthorized 事件
  2. 前端将支付令牌 paymentToken 发送到后端
  3. 后端将支付令牌转发给支付网关(如 Stripe、银联)
  4. 后端返回支付结果给前端
  5. 前端调用 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 报错
  1. 商户 ID 未注册:商户 ID(XXX)可能未在支付服务提供商处完成注册或激活。
  2. 域名未配置:支付服务提供商可能未将 https://xxx.com 添加到该商户 ID 的允许域名列表中。
  3. 配置延迟:新注册的商户 ID 或域名配置可能需要时间生效(例如,需等待几分钟至几小时)。
  4. 域名不匹配:检查 URL 是否包含端口号、子域名等额外信息。
  5. 测试环境与生产环境混淆:确认使用的商户 ID 和 API 密钥对应正确的环境(测试 / 生产)。
2. ApplePay报错:must create a new ApplePaySession from a user gesture handler;
  1. 严格的用户手势要求:Apple Pay 会话必须直接在用户点击事件的处理函数中创建,不能通过 Promise、setTimeout、await 或其他异步方式延迟创建。(我就是在点击时先进行了订单查询的动作,再去创建ApplePay会话,存在异步操作,导致ApplePay会话都不创建了!!!)

  2. 避免预创建会话:不要在页面加载时或其他非交互时机创建 ApplePaySession 实例。

  3. 验证用户交互:确保你的按钮或其他交互元素确实被用户点击后才触发会话创建。

3.商户验证后会话立即关闭,提示未完成付款 
   1. 支付请求参数配置错误
  • countryCode 与 currencyCode 不匹配(如使用 HK 国家代码但货币为 CNY
  • supportedNetworks 中无用户已添加的有效卡类型
  • total 金额格式或精度不符合要求
   2. 商户验证流程问题
  • 商户证书与实际域名不匹配
  • merchantSession 中的签名或时间戳无效
  • 后端验证接口响应超时
   3. 设备或用户环境问题
  • 设备未添加有效支付卡
  • 支付卡已过期或被冻结
  • iOS 设置中的 Apple Pay 权限异常

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/90913.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/90913.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/90913.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flink窗口:解锁流计算的秘密武器

Flink 窗口初识在大数据的世界里&#xff0c;数据源源不断地产生&#xff0c;形成了所谓的 “无限数据流”。想象一下&#xff0c;网络流量监控中&#xff0c;每一秒都有海量的数据包在网络中穿梭&#xff0c;这些数据构成了一个无始无终的流。对于这样的无限数据流&#xff0c…

Java排序算法之<希尔排序>

目录 1、希尔排序介绍 1.1、定义 1.2、核心思想 2、希尔排序的流程 第 1 轮&#xff1a;gap 4 第 2 轮&#xff1a;gap 2 第 3 轮&#xff1a;gap 1 3、希尔排序的实现 4、时间复杂度分析 5、希尔排序的优缺点 6、适用场景 前言 希尔排序&#xff08;Shell Sort&…

c++加载qml文件

这里展示了c加载qml文件的三种方式以及qml文件中根节点的访问准备在创建工程的初期&#xff0c;遇到了一个问题&#xff0c;cmake文件以前都是系统自动生成的&#xff0c;不需要我做过多的操作修改&#xff0c;但是&#xff0c;加载qml的程序主函数是需要用到QGuiApplication&a…

007TG洞察:GPT-5前瞻与AI时代竞争力构建:技术挑战与落地路径

最近&#xff0c;GPT-5 即将发布的消息刷爆了科技圈&#xff0c;更让人期待的是&#xff0c;GPT-6 已经悄悄启动训练了&#xff0c;OpenAI 的奥特曼表示对未来1-2年的模型充满信心&#xff0c;预测AI将进化为能够发现新知识的“AI科学家”。面对日益强大的通用AI&#xff0c;企…

Windows下编译OpenVDB

本文记录在Windows下编译OpenVDB的流程。 零、环境 操作系统Windows 11VS Code1.92.1Git2.34.1MSYS2msys2-x86_64-20240507Visual StudioVisual Studio Community 2022CMake3.22.1 一、编译 1.1 下载 git clone https://github.com/AcademySoftwareFoundation/openvdb.git …

react 内置hooks 详细使用场景,使用案例

useState场景&#xff1a;组件中管理局部状态&#xff0c;如表单值、开关、计数器等。const [count, setCount] useState(0); return <button onClick{() > setCount(count 1)}>Click {count}</button>;useEffect 场景&#xff1a;组件挂载时执行副作用&#…

从0到1学Pandas(九):Pandas 高级数据结构与操作

目录一、探秘多级索引1.1 创建多级索引1.2 多级索引操作1.3 索引转换二、探索 Panel 与 xarray2.1 Panel 数据结构2.2 xarray 库2.3 高维数据操作三、时间序列高级应用3.1 时区处理3.2 时间序列重采样与频率转换3.3 时间序列分解与预测四、数据透视与重塑高级技巧4.1 复杂透视表…

C# 图像转换实战:Bitmap 转 BitmapSource 的 2 种方法

C# 图像转换实战:Bitmap 转 BitmapSource 的 2 种方法 引言 两种转换方法的完整实现 1. 基于GDI句柄的直接转换 (ToBitmapSourceFast) 2. 基于内存流的编码转换 (ToBitmapSourceSafe) 方法对比与选型指南 避坑指南 GDI句柄泄漏问题 图像显示不完整 多线程访问图像引发异常 不同…

Spring Boot 整合 Spring MVC:自动配置与扩展实践

Spring MVC 作为 Java Web 开发的核心框架&#xff0c;在传统 SSM 项目中需要大量 XML 配置&#xff08;如 DispatcherServlet、视图解析器等&#xff09;。而 Spring Boot 通过 "自动配置" 特性&#xff0c;简化了 Spring MVC 的整合过程&#xff0c;同时保留了灵活…

print(“\033[31m红\033[32m绿\033[34m蓝\033[0m默认色“)

可以让python的终端字体有着不一样的颜色。代码&#xff1a;print("\033[31m红\033[32m绿\033[34m蓝\033[0m默认色")效果&#xff1a;

LNMP-zblog分布式部署

一、准备3台主机&#xff08;rocky8&#xff09;下载相应服务[rootnginx ~]# yum install -y nginx nfs-utils[rootphp ~]# yum install -y nfs-utils php-mysqlnd php php-fpm[rootmysql ~]# yum install -y mysql-server二、挂载php端[rootphp ~]# vim /etc/exports [rootphp…

常见代码八股

1. 利用梯度下降法&#xff0c;计算二次函数yx^2x4的最小值 def target_function(x):return x ** 2 x 4def gradient(x):return 2*x 1x_init 10 x x_init steps 100 lr 0.1 for i in range(100):x x - lr*gradient(x)print(f"最小值 f(x) {target_function(x):.4f…

【深入底层】C++开发简历4+4技能描述6

简历书写 熟悉C的封装、继承、多态&#xff0c;STL常用容器&#xff0c;熟悉C11的Lambda表达式、智能指针等&#xff0c;熟悉C20协程语法&#xff0c;具有良好的编码习惯与文档能力。 回答思路 这里是基本上就是要全会&#xff0c;考察的问题也很固定&#xff0c;stl这块可以定…

forest远程调用注意事项

1、如果在项目中&#xff0c;同时依赖了其中多个框架&#xff0c;那么按 Fastjson2 > Fastjson > Jackson > Gson 这样的优先级来判断&#xff0c;Forest 会以优先级最高的框架作为 JSON 转换器。2、Forest 支持哪几种 JSON 框架&#xff1f;A: 支持 Jackson、Gson、F…

网络资源模板--基于Android Studio 实现的新闻App

目录 一、测试环境说明 二、项目简介 三、项目演示 四、部设计详情&#xff08;部分) 登录页 首页 五、项目源码 一、测试环境说明 电脑环境 Windows 11 编写语言 JAVA 开发软件 Android Studio (2020) 开发软件只要大于等于测试版本即可(近几年官网直接下载也可…

通过Location API精准获取位置信息并优化定位精度!

&#x1f44b; 你好&#xff0c;欢迎来到我的博客&#xff01;我是【菜鸟不学编程】    我是一个正在奋斗中的职场码农&#xff0c;步入职场多年&#xff0c;正在从“小码农”慢慢成长为有深度、有思考的技术人。在这条不断进阶的路上&#xff0c;我决定记录下自己的学习与成…

构建可扩展的状态系统:基于 ArkTS 的模块化状态管理设计与实现

摘要 在 HarmonyOS 的日常开发中&#xff0c;很多人都会遇到一个问题&#xff1a;多个页面之间的数据状态如何共享&#xff1f;尤其是在组件结构越来越复杂的场景下&#xff0c;如果还用传统方式来传值&#xff0c;不仅代码混乱&#xff0c;维护也很吃力。 为了解决这个问题&am…

重生之我在暑假学习微服务第二天《MybatisPlus-下篇》

本系列参考黑马程序员微服务课程&#xff0c;有兴趣的可以去查看相关视频&#xff0c;本系列内容采用渐进式方式讲解微服务核心概念与实践方法&#xff0c;每日更新确保知识点的连贯性。通过系统化学习路径帮助开发者掌握分布式系统构建的关键技术。读者可通过平台订阅功能获取…

系统整理Python的条件语句和常用方法

Python 的条件语句&#xff08;if 语句&#xff09;是控制程序流程的基础之一&#xff0c;结构清晰、语法简洁&#xff0c;非常适合初学者掌握。一、基本语法结构if 条件:执行代码块1 elif 条件2:执行代码块2 else:执行代码块3示例&#xff1a;score 85if score > 90:print…

记录个IAR程序下载后硬件复位不运行,必须断电复位才运行的问题

【问题测试】有个F407的跑马灯的例子&#xff0c;是MDK和IAR两个版本&#xff0c;MDK版本的例子下载并复位后可以正常看到LED闪烁&#xff0c;而IAR的例子下进去后&#xff0c;不会闪烁。使用TOOL的上位机内核寄存器监测工具测试发现&#xff0c;硬件复位后竟然还在调试状态&am…