• 内购


    http://www.jianshu.com/p/2845b92cb71f  这个是原文

    上个版本做的内购支付,在内购封装方法中有过初步介绍和整理,结果在版本上线后收到用户的反馈说是支付成功,但是充值账户却不能到账,结果引发了退款等恶性问题,下面就我在实际项目中遇到的问题以及解决方案给出详细的介绍(上述给出的链接是swift版本的,由于笔者项目依旧是OC语言,所以下面依旧以OC语言来介绍)

    1.封装的内购工具一定要设置为单例模式,且在程序启动的时候初始化并在初始化中设置观察者模式

    笔者上个版本中虽说封装了内购支付工具,但是由于经验缺乏,内购工具只在支付页面中有效,结果有一个巨大的坑,用户可能在支付完成之前就退出了支付页面,导致了支付成功但是却没有充值成功的情形,在检查代码之后,我将内购支付工具做成了单例,而且,这个单例的初始化放在了程序入口处,这一点要说明的是,为什么放到入口处呢?是因为放到这里,如果之前有未移除的订单,可以在这里做一些逻辑处理,因为项目及实际情况,笔者是这样处理的:

    - (void)removeOldTransaction {
    
        NSArray *tansactions = [SKPaymentQueue defaultQueue].transactions;
        //如果没有移除过订单信息
        BOOL result = NO;
    
        if ( ![kUserDefaults boolForKey:@"hasFinishOldTransaction"] && tansactions.count > 0) {
            for (SKPaymentTransaction *transaction in tansactions) {
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            }
            result = YES;
        }
        [kUserDefaults setBool:YES forKey:@"hasFinishOldTransaction"];
        if (result) {
            return;
        }
    
    }
    + (instancetype)sharedInstance {
    
        static YGIAPTool *tool;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            tool = [[YGIAPTool alloc] init];
        });
        return  tool;
    }
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            [self removeOldTransaction];
            [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        }
        return self;
    }

    为什么要移除掉旧的订单呢?因为我之前的错误逻辑,导致一些订单就算支付成功而且成功充值,也没有移除订单,这个时候如果设置了观察者,苹果提供的系统API中会自动去查询有没有未移除的订单,这样就会继续执行充值逻辑,可能会造成重复充值的情形,为了避免这种情况带来的损失,笔者就只能硬性要求在版本升级后启动时移除旧的订单,这样就不会有这种隐忧了。
    didFinishLaunching中调用初始化方法 [YGIAPTool sharedInstance];

    2.关于何时移除订单的问题

    我之前搜索过相关的问题,网上给出的答案大都是在充值业务成功之后再移除订单,这个也有一定的问题,主要的就是网络问题或者是用户在充值完成之前就退出或者意外中断的时候引发的问题,这些情况下都会造成订单不能及时移除,给支付体验和充值风险上带来一定的问题。那么,怎么解决这种情况呢?当然,我所提供的方案也只是相对自己遇到的问题上有所改善,至于全面而深入的方案,有知道的大神麻烦指点一下,不胜感激。

    我们都知道,如果在客户端去处理验证凭证的逻辑,很容易被有心人入侵做手脚,这个时候常用的保险做法就是客户端将本次交易产生的凭证发给服务端,让服务端去和苹果服务器验证,在一定程度上能够保证了安全性,那么这样也有一个隐忧,万一我传给服务端了,但是服务端验证失败了呢?或者万一由于网络问题传送失败呢?这个时候再加一层保险,就是客户端在传递给服务端之前先将本凭证存储下来(关于存储方法,笔者在后面会介绍,这里也有),然后服务器验证成功,返回到我们的success回调中去移除本地凭证,而相对应的服务端也已经存储了我们的凭证,当然考虑到服务器验证失败的问题,这个逻辑就要在服务端处理,笔者这里简单说下:就是服务器接到客户端传的凭证后,也是先存下来,直到验证成功并充值完成后才移除,否则就定时去发送验证,知道成功为止。
    服务端不多做介绍,主要还是客户端逻辑,在移除本地凭证后,如果服务端正常处理,那么充值就应该到位了。

    3.关于存储凭证的坑

    笔者一开始存储用的是NSUserDefault方法,在每次支付成功后都会存储凭证到本地,然后在服务器验证成功后,将本地存储的凭证清空。这样看似乎没有毛病,但是如果用户频繁操作,会导致创建两次或者更多次订单,那么问题来了,NSUserDefault只能覆盖(因为存储的凭证对应的key是同一个),这样会造成只能保留最后一个存储的凭证,会产生一些意想不到的支付问题,所以在得知这个之后,笔者改成了用数据库存储到本地,这样我就可以在验证成功后根据当前凭证去删除数据库中的数据,而且还有一个好处是,如果凭证发送失败,在合适的地点我可以遍历数据库中的凭证,然后进行凭证验证,这样用户支付过的订单就很难出现充值不对等的问题(到账延迟问题是必然的,这个不知道有什么好方法没)

    4.关于观察者方法updatedTransactions对应状态的处理问题。

    SKPaymentTransactionStatePurchased:充值成功

    SKPaymentTransactionStateFailed:充值失败

    SKPaymentTransactionStateRestored:恢复内购

    SKPaymentTransactionStatePurchasing:正在采购

    对于这四种状态对应的处理情况,我这里简单介绍一下:
    正在采购:只要添加订单,第一步就会走到这里,这里可以不作处理,要注意的是千万不能在这里移除订单,否则会崩溃,提示不能再采购状态移除订单。

    至于恢复内购,笔者倒没有遇到,不过这里主要进行以下操作

    - (void)removeTransaction {
    
        [[SKPaymentQueue defaultQueue] finishTransaction:self.currentTransaction];
    }

    只需要移除订单就好了

    充值失败:毋庸置疑,这时候订单交易失败,就是废订单了,所以同样要移除

    充值成功:能进入到这里,说明用户支付成功,钱已经扣掉了,那么它之后的相关处理就比较重要了,为了说明清晰,笔者用代码来展示:

    - (void)requestValidReceipt:(SKPaymentTransaction *)transaction {
    
        self.currentTransaction = transaction;
    
        //获取交易的凭证
        NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
        NSData *receiptData = [NSData dataWithContentsOfURL:recepitURL];
    
        if(!receiptData){
            [kWindow showLoadingView:@"获取支付凭证为空"];
            return;
        }
        //转化为base64字符串
        NSString *receiptString= [receiptData base64EncodedStringWithOptions:0];
        //判断本地是否已经有过这个凭证,如果有,为了避免重复交易,什么也不做(这个可能没什么用,不过为了财政安全和保险,加上也不错)
        if ([YGDataBase isReceiptExists:receiptString]) {
            return;
        }
    
        [self buySuccess];//这个不用管,是项目中的统计作用
    
        //1.先将凭证存起来
        [YGDataBase saveReceiptAndGoodsID:receiptString goodId:self.ID];
    //移除当前支付的交易
        [self removeTransaction];
    //统计日志
        [self startValidReceipt:receiptString];
    
        //2.传给服务端凭证数据
        [kWindow showLoadingView];
        [[YGNetWorkTool sharedInstance] ApplePayReceiptVerifyBuyId:self.ID buyType:1 receipt:receiptString success:^(id responseObj) {
            [kWindow hideLoadingView];
            if ([responseObj[@"code"] intValue] != 200 ) {
                [kWindow showLoadingView:responseObj[@"msg"]];
            }else {//充值成功之后将凭证移除 这一点要注意,一定是服务端返回200的时候才能将本地凭证移除,否则会造成支付后没到账的丢单问题
    
                [YGDataBase removeReceipt:receiptString];
            }
            if (self.transactionSuccess) {
                self.transactionSuccess(self.currentTransaction);
            }
            [self showAlert];
            self.ID = nil;
    
        } failure:^(NSError *error) {
            [kWindow hideLoadingView];
            if (self.transactionSuccess) {
                self.transactionSuccess(self.currentTransaction);
            }
            self.ID = nil;
        }];
    
    }

    按照这个逻辑走下来,一般的内购支付问题应该能够解决了,笔者也是花了两天的时间,反复验证测试,将各种可能出现的奇葩操作都测试了一遍,结果充值都能够正常进行,希望能够给有需要的童鞋一些帮助,源码详见原文

     
     
  • 相关阅读:
    java 正则表达式
    jqGrid初次使用遇到的问题及解决方法
    JavaScript设计模式 -- 读书笔记
    CSS 7阶层叠水平
    高性能的JavaScript -- 读书笔记
    javaWeb学习笔记
    eclipse内存溢出报错:java.lang.OutOfMemoryError:Java heap space.
    解决eclipse插件svn不显示svn信息和显示的信息为数字的问题
    JDK环境变量配置
    Maven3.0.3的环境变量配置
  • 原文地址:https://www.cnblogs.com/isItOk/p/6922454.html
Copyright © 2020-2023  润新知