• iOS内购总结


    内购流程:

     1. 用户先拿到购买产品的单子,

      2. 拿着单子去苹果那里交钱,交完钱让苹果在单子上盖个章

       3.拿着盖了章的单子传给自己的服务器来验证是否真的支付成功,服务器是跟苹果验证(我们客户端也是可以跟苹果验证的,只是这样安全性不高)

       4.根据服务器返回的信息做具体的处理

     先上代码干货

    * 设置这个监听对象,会在该界面时时监测支付的状态变化

    ```
    -(void)viewWillAppear:(BOOL)animated{
        [super viewWillAppear:animated];
        // 添加观察者
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    -(void)viewWillDisappear:(BOOL)animated{
        [super viewWillDisappear:animated];
        // 移除观察者
        [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
    }
    ```
    

     * 点击某个商品,开始购买流程

    #pragma Mark -- 内购功能模块
    -(void)buyYuBiWithId:(NSString *)ProductID{
        if([SKPaymentQueue canMakePayments]){
            // productID就是你在创建购买项目时所填写的产品ID
            self.baseTableView.userInteractionEnabled = NO;
            selectProductID = [NSString stringWithFormat:@"%@",ProductID];
            [self requestProduct];
            
        }else{
            _isBuying = NO;
            self.baseTableView.userInteractionEnabled = YES;
            DebugLog(@"不允许程序内付费");
            UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"温馨提示"
                                                                 message:@"请先开启应用内付费购买功能。"
                                                                delegate:nil
                                                       cancelButtonTitle:@"确定"
                                                       otherButtonTitles: nil];
            [alertError show];
        }
    }
    
    #pragma mark 1.请求所有的商品ID
    -(void)requestProduct{
        
        // 1.拿到所有可卖商品的ID数组
       
        NSSet *sets = [[NSSet alloc]initWithArray:_productIdArray];
        
        // 2.向苹果发送请求,请求所有可买的商品
        // 2.1.创建请求对象
        SKProductsRequest *sKProductsRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:sets];
        // 2.2.设置代理(在代理方法里面获取所有的可卖的商品)
        sKProductsRequest.delegate = self;
        // 2.3.开始请求
        [sKProductsRequest start];
        
    }
    #pragma mark 2.苹果那边的内购监听
    -(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
        
        NSArray *product = response.products;
        if([product count] == 0){
            
            NSLog(@"没有商品");
            return;
        }
        
        for (SKProduct *sKProduct in product) {
            
            NSLog(@"pro info");
            NSLog(@"SKProduct 描述信息:%@", sKProduct.description);
            NSLog(@"localizedTitle 产品标题:%@", sKProduct.localizedTitle);
            NSLog(@"localizedDescription 产品描述信息:%@",sKProduct.localizedDescription);
            NSLog(@"price 价格:%@",sKProduct.price);
            NSLog(@"productIdentifier Product id:%@",sKProduct.productIdentifier);
            
            if([sKProduct.productIdentifier isEqualToString:selectProductID]){
                
                [self buyProduct:sKProduct];
                
                break;
                
            }else{
                
                //NSLog(@"没有这个商品");
            }
        }
        
    }
    
    #pragma mark 内购的代码调用
    -(void)buyProduct:(SKProduct *)product{
        
        // 1.创建票据
        SKPayment *skpayment = [SKPayment paymentWithProduct:product];
        
        // 2.将票据加入到交易队列
        [[SKPaymentQueue defaultQueue] addPayment:skpayment];
        
    }
    

     * 下面这个方法就功能很多了
    *  如果内购流程中的苹果支付完成,但是由于某些原因,没有结束该交易,苹果是会在页面出现的时候,回调这个交易的票据数据的,所以要有一个变量,标志SKPaymentTransactionStatePurchased是充值的时候,还是页面启动的时候回调的_isBuying

    #pragma mark 4.实现观察者监听付钱的代理方法,只要交易发生变化就会走下面的方法
    -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
        
        /*
         SKPaymentTransactionStatePurchasing,    正在购买
         SKPaymentTransactionStatePurchased,     已经购买
         SKPaymentTransactionStateFailed,        购买失败
         SKPaymentTransactionStateRestored,      回复购买中
         SKPaymentTransactionStateDeferred       交易还在队列里面,但最终状态还没有决定
         */
        
        for (SKPaymentTransaction *transaction in transactions) {
            switch (transaction.transactionState) {
                case SKPaymentTransactionStatePurchasing:{
                    NSLog(@"正在购买");
                    //存储购买的人ID
                    [[NSUserDefaults standardUserDefaults] setObject:[UserModel uid] forKey:@"applePayMan"];
                    [[NSUserDefaults standardUserDefaults] synchronize];
                }break;
                case SKPaymentTransactionStatePurchased:{
                    //[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                    if (_isBuying) {//主动发起的回调
                        //存储支付成功的单子
                        [[StroeObserver shareStoreObserver] saveApplePayHisTransactionId:transaction.transactionIdentifier andState:@"0" isUpdate:NO];
                        [self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction];
                        
                    }else{//这是启动的回调
                       
                        if (_dbArr.count>0) {//数据库有数据,判断是否有记录
                            //[SDIndicator showInfoWithMessage:@"数据库有数据"];
                            for (NSDictionary *dic in _dbArr) {
                                if ([[dic objectForKey:@"transactionIdentifier"] isEqualToString:transaction.transactionIdentifier]) {//这是数据库中的单子
                                    if ([[dic objectForKey:@"state"] isEqualToString:@"2"]) {//单子流程已经结束
                                        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                                        //删除数据库中流程结束的单子
                                        [[StroeObserver shareStoreObserver] deleteSuc:transaction.transactionIdentifier];
                                        break;
                                    }else{//状态是0,1的都是支付成功了
                                         //[SDIndicator showInfoWithMessage:@"数据库数据充值去"];
                                        //购买后告诉交易队列,把这个成功的交易移除掉-- [queue finishTransaction:transaction]
                                        [self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction];
                                        break;
                                    }
                                }
                            }
                        }
                        
                        if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"applePayMan"] isEqualToString:[UserModel uid]]) {
                             //[SDIndicator showInfoWithMessage:@"数据库无数据充值去"];
                             [self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction];
                        }else{
                            //[SDIndicator showInfoWithMessage:@"结束交易"];
                             [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                        }
                       
                    }
                   
                }break;
                case SKPaymentTransactionStateFailed:{
                     [self hideHud];
                    NSLog(@"购买失败");
                    _isBuying = NO;
                    self.baseTableView.userInteractionEnabled = YES;
                    // 购买失败也要把这个交易移除掉
                   [queue finishTransaction:transaction];
                }break;
                case SKPaymentTransactionStateRestored:{
                     [self hideHud];
                     _isBuying = NO;
                    self.baseTableView.userInteractionEnabled = YES;
                    NSLog(@"恢复购买中,也叫做已经购买");
                    // 恢复购买中也要把这个交易移除掉
                    [queue finishTransaction:transaction];
                }break;
                case SKPaymentTransactionStateDeferred:{
                    
                    NSLog(@"交易还在队列里面,但最终状态还没有决定");
                }break;
                default:
                    break;
            }
        }
    }
    

     我们可以看到,使用数据库了原因是
    * 在苹果返回票据,支付成功的时候,之后的流程,我们走的是自己的服务器去验证,这样的话,中间流程一旦中断,这个交易就可能没有结束,然后呢,苹果的监听,就会给回调回来之前支付成功但是并未结束交易的票据数组。
    * 但是并不能直接调用充值接口,因为我支付成功,立马杀掉进程,重新启动,换个账号,进入充值界面的话,就会充值给另一个用户,所以需要判断这个交易的用户属性,采用了数据库保存,发起的订单,当然成功的单子就删除了【当然也有个问题,及时极限操作,苹果的支付票据还没回来,我就杀掉app,自然也就不能本地保存了,虽然没人这么做,所以存了一下,发起交易的人的uid,方便处理漏掉的单子的时候,不要给另外的人存了钱,自然最后剩余的情况就让找客服吧,无解了】

    下面是要拿苹果返回的交易票据,转成base64去验证(客户端,服务器验证两种)

    // 苹果内购支付成功
    - (void)buyAppleStoreProductSucceedWithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp {
        
        NSString * productIdentifier = paymentTransactionp.payment.productIdentifier;
        DebugLog(@"productIdentifier Product id:%@", productIdentifier);
        NSString *transactionReceiptString= nil;
        
        //系统IOS7.0以上获取支付验证凭证的方式应该改变,切验证返回的数据结构也不一样了。
        NSString *version = [UIDevice currentDevice].systemVersion;
        if([version intValue] >= 7.0){
            // 验证凭据,获取到苹果返回的交易凭据
            // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
            NSURLRequest * appstoreRequest = [NSURLRequest requestWithURL:[[NSBundle mainBundle]appStoreReceiptURL]];
            NSError *error = nil;
            NSData * receiptData = [NSURLConnection sendSynchronousRequest:appstoreRequest returningResponse:nil error:&error];
            transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
        }else{
            
            NSData * receiptData = paymentTransactionp.transactionReceipt;
            //  transactionReceiptString = [receiptData base64EncodedString];
            transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
        }
        // 去验证是否真正的支付成功了
        [self checkAppStorePayResultWithBase64String:transactionReceiptString WithPaymentTransactionp:paymentTransactionp];
        
    }
    
    • 下面方法,接口调用成功的话,因为状态值是0的话,原因是已经充值成功,或者票据不对,这两种情况也必须直接结束交易,所以只要接口调用成功,一样结束交易

    • 接口调用失败的话,再次调用,直到成功

    - (void)checkAppStorePayResultWithBase64String:(NSString *)base64String WithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp{
        
       NSMutableDictionary *prgam = [[NSMutableDictionary alloc] init];
        [prgam setValue:base64String forKey:@"receipt-data"];
        
        __weak typeof(self)WS = self;
        [Api requestWithMethod:nil withPath:API_URL_Apple_pay withParams:prgam withSuccess:^(id responseObject) {
             [WS hideHud];
             _isBuying = NO;
            WS.baseTableView.userInteractionEnabled = YES;
            
            //返回1成功充值,返回0,该单已经充值过了,没结束交易 或者票据错误---都结束交易
            
            //更新充值成功的单子
            [[StroeObserver shareStoreObserver] saveApplePayHisTransactionId:paymentTransactionp.transactionIdentifier andState:@"2" isUpdate:YES];
            //结束苹果pay的交易队列,不然会出现--(您已购买此 App 内购买项目。此项目将免费恢复)提示
            [[SKPaymentQueue defaultQueue] finishTransaction:paymentTransactionp];
            //删除数据库中流程结束的单子
            [[StroeObserver shareStoreObserver] deleteSuc:paymentTransactionp.transactionIdentifier];
            
            
            if ([responseObject[@"status"] integerValue] == 1) {
        
                if(_giftRequestBlock){
                    WS.giftRequestBlock(@"1");
                }
                 [SDIndicator showSuccessWithMessage:@"充值成功"];
                 [WS getData];
            }
        } withError:^(NSError *error) {
             _isBuying = YES;
            WS.baseTableView.userInteractionEnabled = NO;
             [WS checkAppStorePayResultWithBase64String:base64String WithPaymentTransactionp:paymentTransactionp];
        }];
        
    

     下面在说说,数据库存储的逻辑

    #import <UIKit/UIKit.h>
    #import "StroeObserver.h"
    
    @implementation StroeObserver
    
    + (StroeObserver *)shareStoreObserver
    {
        static StroeObserver *_storeOb = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _storeOb = [[StroeObserver alloc] init];
        });
        
        return _storeOb;
    }
    
    -(void)saveApplePayHisTransactionId:(NSString *)transactionId andState:(NSString *)state isUpdate:(BOOL)isUpdate
    {
        dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            /*将数据存入数据库*/
            /*打开数据库*/
            DB_INIT(db);
            NSString *str = DB_PATH;
            DebugLog("%@",str);
            
            DB_OPEN;
            /*创建数据库*/
            NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID];
            //字段有:transactionIdentifier、productIdentifier、uid、transactionDate(支付时间)、rechargeDate(充值到账时间)、state([未支付]「已支付」和「已充值」)
            NSString *tableSql = [NSString stringWithFormat:@"create table if not exists %@(transactionIdentifier,state,uid)",tableName];
            DB_CREATE(tableSql)
           
            BOOL INSERT;
            
            if (isUpdate) {
                 INSERT = [db executeUpdate:[NSString stringWithFormat:@"update %@ set state = ? where transactionIdentifier = ?",tableName],state,transactionId];
            }else{
                 INSERT = [db executeUpdate:[NSString stringWithFormat:@"insert into %@ values(?,?,?)",tableName],transactionId,state,[UserModel uid]];
            }
            
            DB_INSERT;
            DB_CLOSE;
        });
    }
    
    //删除成功的交易
    -(void)deleteSuc:(NSString*)transactionIdentifier{
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            DB_INIT(db);
            DB_OPEN;
            NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID];
            BOOL ret = [db executeUpdate:[NSString stringWithFormat:@"delete from %@ where transactionIdentifier='%@'", tableName,transactionIdentifier]];
            if (!ret) {
                DebugLog(@"删除失败");
            }else{
                DebugLog(@"删除成功");
            }
            DB_CLOSE;
        });
    }
    
    //获取数据库中的支付成功,充值不成功订单
    - (void)initApplePayHisDataFromDB
    {
        _dataArray = [[NSMutableArray alloc]init];
        
        dispatch_queue_t queue = dispatch_queue_create("initApplePayHisDataFromDB", DISPATCH_QUEUE_SERIAL);
        dispatch_sync(queue, ^{
            /*读取数据库旧数据*/
            DB_INIT(db);
            DB_OPEN;
            NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID];
            FMResultSet *rs = [db executeQuery:[NSString stringWithFormat:@"select * from %@ where uid=?",tableName],SWUID];
            while ([rs next]) {
             
                NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [rs stringForColumn:@"state"],@"state",
                                     [rs stringForColumn:@"uid"],@"uid",
                                     [rs stringForColumn:@"transactionIdentifier"],@"transactionIdentifier",nil];
                
                [_dataArray addObject:dic];
            }
            DB_CLOSE;
        });
    }
    
    @end
    

     就看效果了。。。

    BYZqk

    [https://www.jianshu.com/u/c6866a2b7547]

    2018-04-2516:10:44

  • 相关阅读:
    用于爬取知乎某个话题下的精华问题中所有回答的爬虫
    BSP -- 图书共享系统(Book Sharing Platform)
    【已解决】WPS2018 从第三页开始插入页眉页码(即前两页不要页眉页码)
    【编译原理】大白话讲解 First 集和 Follow 集的构造算法
    如果
    HTTP协议(1)------->资源和URL
    JavaWeb框架_Struts2_(八)----->Struts2的国际化
    深入理解java虚拟机----->垃圾收集器与内存分配策略(下)
    深入理解java虚拟机----->垃圾收集器与内存分配策略(上)
    JavaWeb框架_Struts2_(七)----->文件的上传和下载
  • 原文地址:https://www.cnblogs.com/widgetbox/p/8945401.html
Copyright © 2020-2023  润新知