iOS内购你看我就够了(一)

作者: 超_iOS | 来源:发表于2016-08-24 11:56 被阅读10001次

    内购坑多,爬了好久(起码简书里的内购相关文章几乎看过来完了),算是懂了点.对你有用告诉我,有疑问联系我.
    开始(建议先去看写入门的)

    @import StoreKit;
    
    #import "IAPManager.h"
    
    @implementation IAPManager
    + (instancetype)sharedInstance
    {
        static id sharedInstance = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedInstance = [[IAPManager alloc] init]; 
        });
        return sharedInstance;
    }
    单利的入口,传进来一个后台得到的商品ID(在苹果网站注册的)
    - (void)payBtnPressed:(NSString *)product_id {//这传进来个商品id,lzc
        if ([SKPaymentQueue canMakePayments])//是否允许应用内付费
        {
            SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:product_id]];
            request.delegate = self;
            [request start];
        }else
            DLog(@"用户不允许内购");//提示框
    }
    
    // 查询成功后的回调
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
    {
        //这里菊花消失
        DLog(@"菊花消失");
        NSArray *products = response.products;
        if (products.count != 0) {
            
            self.product = products[0];
            SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:self.product];//lzc 改
            payment.applicationUsername = @(1000000).stringValue;//充值用户的id,也就是uid.
            [[SKPaymentQueue defaultQueue] addPayment:payment];//发起购买
    当然还有监听,这个东西写在程序入口比较好,至于为何下次讲
        }
        if (products.count == 0){
            DLog(@"无法获取商品");
        }
    }
    //查询失败后的回调
    - (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
        //菊花消失
        DLog(@"请求苹果服务器失败%@",[error localizedDescription]);
    }
    

    下边就是监听结果了

    //监听购买结果,每个状态下都要结束订单,否则就坑爹了
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {//当用户购买的操作有结果时,就会触发下面的回调函数,
        //菊花消失
        DLog(@"来监听购买结果吧");
        for (SKPaymentTransaction *transaction in transactions) {
           
            switch (transaction.transactionState) {
                case SKPaymentTransactionStatePurchased://交易成功
                
                    [self completeTransaction:transaction];//验证
                    //如果用户在这中间退出,咋办??不知道的看下一篇,坑坑坑都是坑
                    DLog(@"结束订单了");
                    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];//验证成功与否,咱们都注销交易,否则会出现虚假凭证信息一直验证不通过..每次进程序都得输入苹果账号的情况
                    break;
                    
                case SKPaymentTransactionStateFailed:
                 
                   
                    [self failedTransaction:transaction];//交易失败方法
                    break;
                    
                case SKPaymentTransactionStateRestored://已经购买过该商品
    
                    DLog(@"已经购买过");
                    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];//消耗型不支持恢复,所以我就不写了
    
                    break;
                case SKPaymentTransactionStatePurchasing:
                    DLog(@"已经在商品列表中");//菊花
                    break;
                case SKPaymentTransactionStateDeferred:
                    DLog(@"最终状态未确定 ");
                    break;
                default:
                    break;
            }
        }
    }
    
    - (void)failedTransaction:(SKPaymentTransaction *)transaction
    {
        if(transaction.error.code != SKErrorPaymentCancelled) {
            NSLog(@"购买失败");
        } else {
            NSLog(@"用户取消交易");
        }
        [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    }
    

    下边就是和服务器验证凭证了,重点坑在这里

    - (void)completeTransaction:(SKPaymentTransaction *)transaction
    {
        if (self.cash != nil) { //self.cash这玩意我是点击商品后传进来的,所以通过它判断是不是漏单的,有他的话就走正常流程
            DLog(@"购买成功验证订单");
            NSData *data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
            NSString *a = [data base64EncodedStringWithOptions:0];
            NSLog(@"base64JSONString =---%@---",a);//得到凭证
          
            NSDictionary * dic = @{@"uid":用户id,
                                   @"receipt":a,
                                   还有后台要的其他东西,起码凭证和用户id是必须的吧};
            DLog(@"验证信息%@",dic);
            //存起来
            [self addDicToPayAry:dic];//先把这个信息存起到本地,这是你埋坑的第一步,有空再解释
            [self testForServer:dic];//和后台去二次验证,这个很必要
        }
    else
        {
            DLog(@"漏单流程从本地取凭证去验证");
            [self checkUnTestReceipt];//从本地取凭证验证去,下篇分解
        }//不是点击cell 进来的,也就是说上次订单没结束,日狗去吧
    }
    

    服务器交互,只要服务器有反应咱就把本地的信息移除,因为验证成功删了正常,验证不成功说明凭证有假(当然也可能苹果出问题,这咱管不了了)删,咱这边服务器出毛病(我们后台说这个状态是后台验证过了但是没有发商品,用户找客服),删.
    只有没和自己后台交互上的时候,我们就在本地找村过的凭证,有的话说明有没验证过的,咱继续验证.没的话(第一次也不会没,因为存过一次但是就没删过)说明都验证过了.总之这个过程不管怎么样都要结束订单.

    
    - (void)testForServer:(NSDictionary *)dic
    {
         WEAKSELF;
        [[NetWorkManager sharedInstance]postJsonData:dic url:url successBlock:^(id responseBody) {
            DLog(@"支付成功%@",responseBody);
            if ([responseBody[@"code"]isEqualToNumber:@(200)]) {
                DLog(@"1验证成功完成交易OKOKOKOK");
                if (weakSelf.paySuccessBlock){
                    weakSelf.paySuccessBlock();//告诉外边UI做处理
                }
                [weakSelf removeDicFromPayAry:dic];//从本地移除凭证等信息dic
            }
            if (验证receipt-data失败) {
                DLog(@"1验证receipt-data失败");
                [weakSelf removeDicFromPayAry:dic];//移除
            }
            if (我们服务器有毛病) {
                [weakSelf removeDicFromPayAry:dic];
                DLog(@"1你去找客服吧我们服务器有毛病");
            }
            //        DLog(@"我结束订单了-----");
            
        } failureBlock:^(NSString *error) {
            DLog(@"%@和自己服务器交互失败11",error);
            [weakSelf checkUnTestReceipt];//和自己后台没联系上,所以要检查本地有没有存过的凭证,有的话继续验证.下篇详聊
        }];
    }
    

    篇幅问题,存凭证删凭证和再次验证凭证下次说.
    还有订单没结束的坑(用户买成功了苹果服务器不给力,用户急脾气关应用了,订单还没结束)等等,下回分解.

    8.25更,来埋坑了,点进来↓
    iOS内购第二篇

    感觉还是不太完美啊,先这样吧

    相关文章

      网友评论

      • c96787cb33a7:NSData *data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]]; 请问这里的交易凭证数据有没可能获取失败的呢?如果失败了要怎么查?
      • OC笔记:楼主,内购到银行的钱,怎么取出来呢?银行好像要发票和合同
        超_iOS:@OC笔记 不然你打客服问问吧。这不是会计的活么?
        OC笔记:@_超 我们是公司账号,银行要发票和合同,你知道从哪里获取发票和合同吗?
        超_iOS:@OC笔记 难道不是定期转到你们银行卡么
      • 8d95719e3241:楼主你好,你这种方式可能会有一个bug,在 [[SKPaymentQueue defaultQueue] addPayment:payment];//发起购买之后,可能网络加载比较慢,但是就在这时候,用户强制关闭了APP,当用户强制关闭APP后,网络加载完成,然后弹出付款弹框,然后用户付款,但是这时候就没有办法想自己服务器验证了,同时这个订单相关信息你都没有存储下来
        8d95719e3241:@_超
        概率:
        我做的这个项目概率还是不小的,用户点击下单之后,app出现"正在加载"界面,现在的状态是只能和苹果进行支付交易,其他的是不能交互,正常来说请求网络和整个支付流程很开就完成了,但是遇到苹果服务器响应慢的时候,用户可能就要关闭这个交易,那用户就可能会关闭app.
        方法:
        说实话,我也没有太好的解决方法,
        一.我现在是在 [[SKPaymentQueue defaultQueue] addPayment:payment];//发起购买之前 和 case SKPaymentTransactionStatePurchasing: DLog(@"已经在商品列表中");//菊花这步都保存数据(要通过orderId区分一下,不能存入同一个订单),然后1.如果苹果那边支付成功并且向自己的服务器验证成功的时候删除数据,2.如果用户取消支付的时候删除数据
        二.在app 即将关闭的时候另一保存一份当前正在操作的这个订单数据, 另保存的这个数据用于- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction 中 transaction中取消支付或者支付失败的队列时候,从在[[SKPaymentQueue defaultQueue] addPayment:payment];这步之前保存数据中删除用
        超_iOS:@理想的小彪彪 那你有什么好办法?你说这概率挺小的。。
      • 8d95719e3241:本地保存数据的时候,用的是NSUserDefaults(每次使用都及时使用了synchronize)保存一个字典,这个字典里面对应的 是用户的uid 和 该用户的数据数组(数组是mutArr,是可变的),存取上会有问题吗? 在沙箱测试的时候没有问题,但是上线之后除了问题,我怀疑这里会有问题? 你觉得可能吗? 楼主

      • 8d95719e3241:楼主你好,我遇到坑了,帮我看看
        情况描述:在上线之前通过沙箱测试是没有问题的,但是上线之后,出现了问题,出现的问题是这样的,如果这个用户在购买之前apple ID 账号绑定这付款信息(支付宝或者是银行卡)是可以够买成功的,但是如果这个用户在购买之前没有绑定付款信息,他点击购买,会跳入iOS系统的绑定付款信息页面,然后支付成功,但是好像没有监听到这个状态 SKPaymentTransactionStatePurchased,
        超_iOS:@理想的小彪彪 没遇到过,应该不会吧
      • somesOne:我想问下,你的判断漏单是根据什么判断的啊
        echo海猫:同问
      • AllenZYQ:我想问下 ,正式上线后apple订单支付的信息 能和服务端与apple对接 返回的json数据 对应上吗?或者某些字段对应上
      • 携一两本单色书来:作者你好,我测试凭据验证时用的客户端,但是每次返回都是21002,我上传的凭据就是将 [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]] 64为加密,遇到过这个问题吗?
        雪花啤酒雪花:用系统的NSURLSession 发post请求验证,不要用AF
        超_iOS:@携一两本单色书来 没遇到过.你查查21002 代表什么
      • 青青子衿卍:当用户第一次内购后,删除app重新下载,怎么判断用户已经付费了呢?
      • TRACER_:麻烦问一下你的判断漏单是根据什么判断的啊,还有你有没有处理越狱手机呢?
        超_iOS:@__Memory 没处理越狱的.
      • 1cdb19c230be:你好 你这个从本地去取存凭证的数组 是怎么取的啊 不是个data编码吗
        超_iOS:@superWWWWWWW 数组里放的是字典啊
      • 丁桥人在外地:在呢 有没有个列子 或者写个单利呀工具类啊
      • pinglife:你好!我想问下把信息存到本地干嘛用啊?有点儿不太明白!
        超_iOS:@pinglife 传一半是什么意思?
        pinglife:@_超 对了我这个给后台总是传一半这个怎么解决啊?
        NSData *receiptData = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];

        NSString *transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
        超_iOS:@pinglife 可以看第二篇.防止丢单的
      • mysteryemm:看你的还不够啊 ,返回的商品ID是无效的,楼主遇到过没
        超_iOS:@心董儿 这个没有关系
        mysteryemm:@_超 后台没问题,id是昨天申请的,现在还不行,前几天填写的银行税务合同等还未生效,会跟这个有关系吗?
        超_iOS:@心董儿 看后台有没有配错,另外刚申请的id有延迟
      • 最后还是个农:请教下,我在内购监听方法中一直提示交易失败-购买失败,大概是什么原因
        最后还是个农:@_超 找到原因了,我脑子有坑,把icode改为了测试账号,iTunes没改
        超_iOS:@最后还是个农 如果是刚申请的商品ID需要等会儿
        超_iOS:@最后还是个农 你可以看一下返回的错误码
      • 377986ae35d5:你好那个cash 怎么记录漏单的
        超_iOS: @111啊啊啊 他是判断是否是漏单的。你不用管他
      • listen_to:楼主,来个demo嘛!~
      • 卟师:挺好的,我能转载分享吗.我会标注上出处和作者的
        超_iOS: @卟师 可以,希望标注在显眼地方😄

      本文标题:iOS内购你看我就够了(一)

      本文链接:https://www.haomeiwen.com/subject/lxznsttx.html