三.苹果支付 - 漏单补单处理

2024-06-17 16:28

本文主要是介绍三.苹果支付 - 漏单补单处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

介绍

苹果的支付流程并不复杂,我们可以在很短的时间内实现正常的支付流程。

但是苹果支付有一个很大特点就是慢,包括唤起支付弹窗,点击完成到服务端验单完成,整个流程很长,任何一个环节都有可能因为断网,或者退出APP导致支付流程中断。

一旦用户输入的支付密码,但是支付流程没有进行完成,这个时候就产生了漏单的情况,也就是说扣了用户的费用,但是购买的东西没有发放到账户。

本篇博客就来介绍苹果支付的漏单处理方案。

支付流程

上面提到任何环节出现中断,都会产生漏单,那么我们就需要清楚整个支付流程分成那几个阶段。

1.发起购买:

通常完整的支付流程,都会拿到用户选择的商品到服务端生成一个预备订单。生成订单完成之后会返回一个订单id preorderId。(这些和自己产品的业务需求相关,每个服务端实现的方案可能不都一样),拿到订单id preorderId,发起苹果支付,商品被添加到购买列表。

2.输入密码:

商品被添加到列表后,会弹出支付弹窗要求用户输入支付密码。

3.支付完成:

用户输入完支付密码之后,提示支付成功,弹窗消失,根据网络情况会在几秒内弹出购买成功的弹窗。

4.服务端验单:

点击购买完成的OK按钮,进入购买完成状态,发起验单请求。

5.购买完成:

验单通过,完成购买流程。

漏单情况

在用户没有输入密码之前取消支付操作,或者其它原因中断支付操作,因为没有进行付款,所以我们可以直接销单,这都没有问题。

但是一旦用户已经输入了密码,也就是从第3步以及以后的步骤,发生了网络中断,或APP退出,那么都算是发生了漏单。

支付过程中我们可以通过paymentQueue方法来监听支付流程和支付结果

    //MARK: 购买结果 监听回调func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {for tran in transactions {switch tran.transactionState {case .purchased://购买完成CSLog.info(module: "apple pay", "购买完成")completePay(transaction: tran)breakcase.purchasing://商品添加进列表CSLog.info(module: "apple pay", "商品添加到列表")breakcase.restored://已经购买过该商品SKPaymentQueue.default().finishTransaction(tran)CSLog.info(module: "apple pay", "pay success 1,productId=\(self.proId ?? "")")self.resultBlock?(true,nil,"已经购买过该商品")breakcase.failed://购买失败SKPaymentQueue.default().finishTransaction(tran)CSLog.info(module: "apple pay", "pay failed 4,productId=\(self.proId ?? ""),error=\(String(describing: tran.error))")self.resultBlock?(false,tran.error,"购买失败")breakdefault:break}}}

发起支付,商品被添加到列表的时候会进入该代理方法,并且有一个transactionState == .purchasing状态的SKPaymentTransaction。

当用户输入密码完成之后并不会调用这个代理,而是当用户看见完成购买的弹窗并点击OK时,才会再次进入该代理方法,并且 transactionState == .purchased,此时我们去服务端进行验单。

那么产生漏单就有两种情况:

  1. 当用户输入密码但是没有看见完成的弹窗,或者没有点击OK,直接退出APP。
  2. 还有用户点击OK但是当还没有进行服务端验单,或验单未完成,直接退出APP。

漏单处理

既然已经清楚了漏单的具体情况,那么我们就来着手处理这两种情况。

  • 仅剩服务端未处理

先来看比较简单的情况,用户点击OK,但是服务端验单未完成。

购买成功

上面已经提到,当用户点击OK之后会进入代理方法,并且transactionState == .purchased,此时我们认为支付已经完成,开始验单。

    //MARK: 购买成功验证凭证func completePay(transaction:SKPaymentTransaction) {//获取交易凭证if let base64Str = self.getRecepitStr(transaction: transaction) {if let product = self.product {guard let productId = product.productId else { return }guard let amount = product.showAmount else { return }guard let currency = product.currency else { return }guard let preorderId = preorderId else { return }guard let transactionId = transaction.transactionIdentifier else { return }// 服务端校验verifyReceipt(productId: productId, preorderId: preorderId, amount: amount, currency: currency, receipt: base64Str, transactionId: transactionId, transaction: transaction)}}}
服务端校验

可以看见本次业务中验单需要的数据有很多,但无论有什么数据,处理起来都是一样的,当我们获取到购买成功之后,将验单所需的所有数据保存一份到本地。

    ///校验凭证///- Parameters:///   - productId: 商品id///   - preorderId: 订单id///   - amount: 金额///   - currency: 货币类型///   - receipt: 凭证///   - transactionId: 交易idfunc verifyReceipt(productId:String,preorderId:String,amount:String,currency:String,receipt:String,transactionId:String,transaction:SKPaymentTransaction) {if presenter == nil {presenter = CSWalletPresenter()}// 保存self.saveReciptInfo(productId: productId, receipt: receipt, preorderId: preorderId, amount: amount, currency: currency, transactionId: transactionId)presenter?.requestApplePayReceipt(productId: productId,preorderId: preorderId, amount: amount, currency: currency, receipt: receipt, adid: "", transactionId: transactionId, extParamater: ["":""]) {[weak self] success,error inguard let self = self else { return }if success {SKPaymentQueue.default().finishTransaction(transaction)// 清除AppPayManager.shareInstance.removeReciptInfo()self.resultBlock?(true,nil,"支付成功")} else {self.resultBlock?(false,error,"支付失败")}}}
保存数据

保存数据的代码如下:

    /// 保存交易凭证,订单,及其它商品信息/// - Parameters:///  - productId: 商品id///  - receipt: 交易凭证///  - preorderId: 订单id///  - amount: 金额///  - currency: 货币///  - transactionId: 交易idfunc saveReciptInfo(productId:String? = nil,receipt:String? = nil,preorderId:String? = nil,amount:String? = nil,currency:String? = nil,transactionId:String? = nil) {CSLog.info(module: "apple pay", "存储交易凭证")let userDefault = UserDefaults.standardlet uid = CSAccountManager.shared.account?.user?.id ?? 0let dict = userDefault.dictionary(forKey: "\(uid)-receiptInfo")let dictM = NSMutableDictionary.init(dictionary: dict ?? [:])if productId != nil {dictM["productId"] = productId}if receipt != nil {dictM["receipt"] = receipt}if preorderId != nil {dictM["preorderId"] = preorderId}if amount != nil {dictM["amount"] = amount}if currency != nil {dictM["currency"] = currency}if transactionId != nil {dictM["transactionId"] = transactionId}userDefault.set(dictM, forKey: "\(uid)-receiptInfo")userDefault.synchronize()}

这里根据用户的uid存储了用户收据到userdefault中,是为了避免验单到其他用户,将金币加给其他用户。

清除数据

如果这订单校验完成,那么清除掉收据信息,清除的代码如下:

    //MARK: 移除交易凭证,订单,及其它商品信息func removeReciptInfo() {CSLog.info(module: "apple pay", "移除交易凭证")let userDefault = UserDefaults.standardlet uid = CSAccountManager.shared.account?.user?.id ?? 0userDefault.removeObject(forKey: "\(uid)-receiptInfo")userDefault.synchronize()}
开启漏单检测

在启动时AppDelegate中进行漏单检测:

        // 进行漏单检测AppPayManager.shareInstance.validateReceipt()

检测代码如下:

    //MARK: 漏单检测func validateReceipt(transaction:SKPaymentTransaction? = nil) {//获取交易凭证信息guard let reciptInfo = self.getReciptInfo() else {if let transaction = transaction {CSLog.info(module: "apple pay", "未完成订单 但是没有凭证,直接销单")SKPaymentQueue.default().finishTransaction(transaction)}return}// 商品idguard let productId = reciptInfo["productId"] as? String else { return }// 订单idguard let preorderId = reciptInfo["preorderId"] as? String else { return }// 交易金额guard let amount = reciptInfo["amount"] as? String else { return }// 货币类型guard let currency = reciptInfo["currency"] as? String else { return }// 交易凭证var receipt = reciptInfo["receipt"] as? Stringif receipt == nil {CSLog.info(module: "apple pay", "未点击完成的漏单处理")receipt = self.getRecepitStr()}// 交易idvar transactionId = reciptInfo["transactionId"] as? Stringvar current_transaction:SKPaymentTransaction?if transaction == nil {let transactions = SKPaymentQueue.default().transactionsfor transaction in transactions {if let transactionId = transactionId {if transaction.transactionIdentifier == transactionId {current_transaction = transactionCSLog.info(module: "apple pay", "获取到付款完成的交易")break}} else {transactionId = transaction.transactionIdentifiercurrent_transaction = transactionCSLog.info(module: "apple pay", "获取到未付款完成的交易")}}} else {CSLog.info(module: "apple pay", "用户已经完成购买,直接验单")current_transaction = transactiontransactionId = transaction?.transactionIdentifier}guard let transaction = current_transaction else {CSLog.info(module: "apple pay", "transaction is nil")return }guard let receipt = receipt else {CSLog.info(module: "apple pay", "receipt is nil")return }guard let transactionId = transactionId else {CSLog.info(module: "apple pay", "transactionId is nil")return }/// 服务端校验收据verifyReceipt(productId: productId, preorderId: preorderId, amount: amount, currency: currency, receipt: receipt, transactionId: transactionId, transaction: transaction)}

漏单检测的代码看起来有一点复杂,简单一点说:

我们需要从已经保存的数据中获取服务端所需要的数据,比如订单id,商品id,交易id,金额,货币等等。
从SKPaymentQueue.default().transactions交易队列中获取没有消除的交易。

获取到这些数据之后调用服务端验单接口。

输入密码但苹果未响应

当用户输入密码完成之后,理论上会出现支付成功弹窗,点击弹窗的完成后进购买结果的回调方法中。

但是如果因为用户操作,或者网络原因导致没有出现支付成功弹窗。也会中断支付流程,导致充值金币不能到账。

为了处理这种情况,我们需要提前存储服务端验单所需要的信息。

我们将它放置在发起购买时,代码如下:

    //MARK: 发起购买请求func startPay(product:DGProductModel,preorderId:String,resultBlock:((_ success:Bool,_ error:Error?,_ result:String)->Void)?)  {self.resultBlock = resultBlockself.product = productself.preorderId = preorderIdif !SKPaymentQueue.canMakePayments() {CSLog.info(module: "pay", "pay failed2,productId=\(proId)")resultBlock?(false,nil,"不可使用苹果支付")return}.....// 存储一些交易信息guard let productId = product.productId else { return }guard let amount = product.showAmount else { return }guard let currency = product.currency else { return }self.saveReciptInfo(productId: productId,preorderId: preorderId, amount: amount,currency: currency)}

等到APP重新启动时,会直接触发购买监听的回调方法,进入购买完成状态,进入completePay方法,但此时没有product,以及订单等其他信息,所以我们之间转到validateReceipt方法进行验证,这时候上一步我们存储的信息就派上了用场:

    //MARK: 购买成功验证凭证func completePay(transaction:SKPaymentTransaction) {//获取交易凭证if let base64Str = self.getRecepitStr(transaction: transaction) {if let product = self.product {guard let productId = product.productId else { return }guard let amount = product.showAmount else { return }guard let currency = product.currency else { return }guard let preorderId = preorderId else { return }guard let transactionId = transaction.transactionIdentifier else { return }// 服务端校验verifyReceipt(productId: productId, preorderId: preorderId, amount: amount, currency: currency, receipt: base64Str, transactionId: transactionId, transaction: transaction)} else {validateReceipt(transaction: transaction)}}}

validateReceipt方法的实现和上面的实现是一样的,就不重复粘贴代码了。

结语

以上就是客户端处理漏单的两种情况,这些漏单的处理方案可以大大减少客户端支付出现的漏单问题。通过有效的监控和处理机制,不仅能够提升用户体验,还能提高支付的成功率,保障用户权益和业务的顺利进行。希望这些方法能为开发者在实际应用中提供有价值的参考,确保支付流程的稳定与可靠。

这篇关于三.苹果支付 - 漏单补单处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1069949

相关文章

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

Thymeleaf:生成静态文件及异常处理java.lang.NoClassDefFoundError: ognl/PropertyAccessor

我们需要引入包: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>sp

jenkins 插件执行shell命令时,提示“Command not found”处理方法

首先提示找不到“Command not found,可能我们第一反应是查看目标机器是否已支持该命令,不过如果相信能找到这里来的朋友估计遇到的跟我一样,其实目标机器是没有问题的通过一些远程工具执行shell命令是可以执行。奇怪的就是通过jenkinsSSH插件无法执行,经一番折腾各种搜索发现是jenkins没有加载/etc/profile导致。 【解决办法】: 需要在jenkins调用shell脚

明明的随机数处理问题分析与解决方案

明明的随机数处理问题分析与解决方案 引言问题描述解决方案数据结构设计具体步骤伪代码C语言实现详细解释读取输入去重操作排序操作输出结果复杂度分析 引言 明明生成了N个1到500之间的随机整数,我们需要对这些整数进行处理,删去重复的数字,然后进行排序并输出结果。本文将详细讲解如何通过算法、数据结构以及C语言来解决这个问题。我们将会使用数组和哈希表来实现去重操作,再利用排序算法对结果

8. 自然语言处理中的深度学习:从词向量到BERT

引言 深度学习在自然语言处理(NLP)领域的应用极大地推动了语言理解和生成技术的发展。通过从词向量到预训练模型(如BERT)的演进,NLP技术在机器翻译、情感分析、问答系统等任务中取得了显著成果。本篇博文将探讨深度学习在NLP中的核心技术,包括词向量、序列模型(如RNN、LSTM),以及BERT等预训练模型的崛起及其实际应用。 1. 词向量的生成与应用 词向量(Word Embedding)

使用协程实现高并发的I/O处理

文章目录 1. 协程简介1.1 什么是协程?1.2 协程的特点1.3 Python 中的协程 2. 协程的基本概念2.1 事件循环2.2 协程函数2.3 Future 对象 3. 使用协程实现高并发的 I/O 处理3.1 网络请求3.2 文件读写 4. 实际应用场景4.1 网络爬虫4.2 文件处理 5. 性能分析5.1 上下文切换开销5.2 I/O 等待时间 6. 最佳实践6.1 使用 as

Level3 — PART 3 — 自然语言处理与文本分析

目录 自然语言处理概要 分词与词性标注 N-Gram 分词 分词及词性标注的难点 法则式分词法 全切分 FMM和BMM Bi-direction MM 优缺点 统计式分词法 N-Gram概率模型 HMM概率模型 词性标注(Part-of-Speech Tagging) HMM 文本挖掘概要 信息检索(Information Retrieval) 全文扫描 关键词

PHP7扩展开发之数组处理

前言 这次,我们将演示如何在PHP扩展中如何对数组进行处理。要实现的PHP代码如下: <?phpfunction array_concat ($arr, $prefix) {foreach($arr as $key => $val) {if (isset($prefix[$key]) && is_string($val) && is_string($prefix[$key])) {$arr[

PHP7扩展开发之字符串处理

前言 这次,我们来看看字符串在PHP扩展里面如何处理。 示例代码如下: <?phpfunction str_concat($prefix, $string) {$len = strlen($prefix);$substr = substr($string, 0, $len);if ($substr != $prefix) {return $prefix." ".$string;} else