美文网首页
iOS - 内购IAP

iOS - 内购IAP

作者: 小例子 | 来源:发表于2019-03-28 14:23 被阅读0次

    内购

    内购的概念

    • IAP,即in-App Purchase,是一种智能移动终端应用程序付费的模式,在苹果(Apple)iOS、谷歌安卓(Google Android)、微软WindowsPhone等智能移动终端操作系统中都有相应的实现。-- 百度百科

    • In-App Purchase:

      Offer users additional content and services through purchases made within your app.

    账户请求流程

    App store connect地址:https://appstoreconnect.apple.com/login

    1. 注册app (新建App)


      新建App.png
    1. 填写协议、税务和银行业务
    填写协议、税务和银行业务.png
    1. 配置内购产品ID


      配置内购产品ID.png

      根据产品类型来添加:


      产品类型.png
    2. 添加沙盒测试员


      添加沙盒测试员入口.png
      添加沙盒测试员.png

      添加测试员就好了。此时注册的AppleID是一个虚拟的AppleID

    • 沙盒账号是什么

    iOS应用里面用到了苹果应用内付费(IAP)功能,在项目上线前一定要进行功能测试。测试肯定是需要的,何况这个跟money有关。。。开发完成了之后,如何进行测试呢?难道我测试个内购功能要自己掏钱?就算是公司掏钱,但是苹果要吃掉3成的啊,想想如果是99刀的商品,点下购买的时候心里都有点发慌。。。
    苹果当然没这么坑了,测试内购,苹果提供了沙盒账号(也叫沙箱账号)的方式。这个沙箱账号其实是虚拟的AppleID,在开发者账号后台的iTune Connect上配置了之后就能使用沙盒账号测试内购,有了沙盒账号,就能体验一把土豪的感觉了,游戏钻石什么的随便充,反正不用我的钱。

    App代码集成

    1. 获取商品信息
    #pragma mark - 获取商品ID成功的代理方法
    
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
        //返回的是SKProduct对象数组
        //如果你上面请求的是多个,那么这里返回的也是多个
        if (response) {
            SKProduct *product = [response.products firstObject];
            //查询成功,开始支付
            [self startPaymentWithProduct:product];
        }
    }
    
    1. 拿到商品信息,创建支付对象
    #pragma mark -- 拿到商品信息,创建支付对象
    - (void)startPaymentWithProduct:(SKProduct *)product {
        SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
        payment.applicationUsername = @"myOrderID";
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
    
    1. 把交易添加到队列 (上面的addPayment:方法)

    2. 监听支付结果(paymentQueue:updatedTransactions:),如果支付成功,Apple会把支付成功的凭证(recipt)存到沙盒中

    #pragma mark -  监听的代理方法
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
        [transactions enumerateObjectsUsingBlock:^(SKPaymentTransaction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            SKPaymentTransaction *transation = obj;
            switch (transation.transactionState) {
                case SKPaymentTransactionStatePurchasing:
                {
                    NSLog(@"购买中");
                }
                    break;
                case SKPaymentTransactionStatePurchased:
                {
                    NSLog(@"交易完成");
                    //获取透传字段
                    NSString *orderNo = transation.payment.applicationUsername;
                    //transactionIdentifier:相当于Apple的订单号
                    NSString *transationId = transation.transactionIdentifier;
                    NSLog(@"orderNo = %@, 交易ID = %@", orderNo, transationId);
                    //从沙盒中获取交易凭证
                    NSData *reciptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
                    //转化成Base64字符串(用于校验)             
                    /*
                     //其作用是将生成的Base64字符串按照64个字符长度进行等分换行。
                     NSDataBase64Encoding64CharacterLineLength = 1UL << 0,
                     
                     //其作用是将生成的Base64字符串按照76个字符长度进行等分换行。
                     NSDataBase64Encoding76CharacterLineLength = 1UL << 1,
                     
                     //其作用是将生成的Base64字符串以回车结束。
                     NSDataBase64EncodingEndLineWithCarriageReturn = 1UL << 4,
                     
                     //其作用是将生成的Base64字符串以换行结束。
                     NSDataBase64EncodingEndLineWithLineFeed = 1UL << 5,
                     */              
                    //NSString *reciptString = [reciptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
                    NSString *reciptString = [reciptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
                    //NSLog(@"%@",reciptString);
                    
                    //传给后台做二次验证
                    [self checkReceipt:reciptString];
                    [[SKPaymentQueue defaultQueue] finishTransaction:transation];
                }
                    break;
                case SKPaymentTransactionStateFailed:
                {
                    //localizedDescription可以作为提示信息(交易失败无法连接到 iTunes Store)
                    NSLog(@"交易失败%@", transation.error.localizedDescription);
                    [[SKPaymentQueue defaultQueue] finishTransaction:transation];
                }
                    break;
                case SKPaymentTransactionStateRestored:
                {
                    NSLog(@"恢复购买完成");
                    //恢复完成(对应restoreCompletedTransactions)方法
                }
                    break;
                case SKPaymentTransactionStateDeferred:
                {
                    NSLog(@"交易推迟, 等待外部操作");
                    //交易推迟
                    //官方解释是:交易已经加入队列,但是需要等待外部操作
                    //主要用于儿童模式,需要询问家长同意。这种情况下不能关闭订单(完成交易),否则这类充值将无法处理。
                }
                    break;
                default:
                    break;
            }
        }];
    }
    
    1. 我们从沙盒中取到凭证(recipt),发送给我们自己后台进行二次验证,验证成功表示支付成功.
    #pragma mark -- 从沙盒中取到凭证,发送给我们自己后台进行二次验证,验证成功表示支付成功
    - (void)checkReceipt:(NSString *)receipt {
        //NSLog(@"%@",receipt);
        // 拼接请求数据
        NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\":\"%@\"}", receipt];
        NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
        
        //1.创建会话对象
        NSURLSession *session = [NSURLSession sharedSession];
        //2.根据会话对象创建task
        NSURL *url = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
        
        //3.创建可变的请求对象
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        
        //4.修改请求方法为POST
        request.HTTPMethod = @"POST";
        
        //5.设置请求体
        request.HTTPBody = bodyData;
        
        //6.根据会话对象创建一个Task(发送请求)
        /*
         第一个参数:请求对象
         第二个参数:completionHandler回调(请求完成【成功|失败】的回调)
         data:响应体信息(期望的数据)
         response:响应头信息,主要是对服务器端的描述
         error:错误信息,如果请求失败,则error有值
         */
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            
            //8.解析数据
            //NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
            NSLog(@"%@",[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
            
        }];
        
        //7.执行任务
        [dataTask resume];
    }
    
    - (void)refreshReceiptData
    {
        self.receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] init];
        self.receiptRefreshRequest.delegate = self;
        [self.receiptRefreshRequest start];
    }
    
    • 凭证校验地址:

    开发环境: https://sandbox.itunes.apple.com/verifyReceipt
    生产环境: https://buy.itunes.apple.com/verifyReceipt

    • 凭证校验异常 code 参照码:

    内购验证凭据返回结果状态码说明(status 状态)
    21000 App Store无法读取你提供的JSON数据
    21002 收据数据不符合格式
    21003 收据无法被验证
    21004 你提供的共享密钥和账户的共享密钥不一致
    21005 收据服务器当前不可用
    21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
    21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
    21008 收据信息是产品环境中使用,但却被发送到测试环境中验证

    demo完整代码

    #import "ViewController.h"
    #import <StoreKit/StoreKit.h>
    @interface ViewController () <SKProductsRequestDelegate,SKPaymentTransactionObserver,SKRequestDelegate>
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        UIButton *btn1 = [[UIButton alloc]initWithFrame:CGRectMake(100, 50, 100, 100)];
        [btn1 setTitle:@"60游戏币" forState:UIControlStateNormal];
        [btn1 addTarget:self action:@selector(clickBtn1) forControlEvents:UIControlEventTouchUpInside];
        btn1.backgroundColor = [UIColor blueColor];
        [self.view addSubview:btn1];
        
        // Do any additional setup after loading the view, typically from a nib.
        UIButton *btn2 = [[UIButton alloc]initWithFrame:CGRectMake(100, 180, 100, 100)];
        [btn2 setTitle:@"120游戏币" forState:UIControlStateNormal];
        [btn2 addTarget:self action:@selector(clickBtn2) forControlEvents:UIControlEventTouchUpInside];
        btn2.backgroundColor = [UIColor blueColor];
        [self.view addSubview:btn2];
        
        
    }
    
    - (void)requestDidFinish:(SKRequest *)request {
        NSLog(@"requestDidFinish");
        //从沙盒中获取交易凭证
        NSData *reciptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
        NSString *reciptString = [reciptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
        [self checkReceipt:reciptString];
    }
    
    -(void)clickBtn1 {
        NSSet *sets = [NSSet setWithObjects:@"dollar1", nil];
        SKProductsRequest *productrequest = [[SKProductsRequest alloc] initWithProductIdentifiers:sets];
        productrequest.delegate = self;
        [productrequest start];
        //首先我们要在 viewDidLoad 方法中添加监听对象
        
    }
    -(void)clickBtn2 {
        NSSet *sets = [NSSet setWithObjects:@"dollar2", nil];
        SKProductsRequest *productrequest = [[SKProductsRequest alloc] initWithProductIdentifiers:sets];
        productrequest.delegate = self;
        [productrequest start];
        //首先我们要在 viewDidLoad 方法中添加监听对象
        
    }
    
    #pragma mark - 获取商品ID成功的代理方法
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
        //返回的是SKProduct对象数组
        //如果你上面请求的是多个,那么这里返回的也是多个
        if (response) {
            SKProduct *product = [response.products firstObject];
            //查询成功,开始支付
            [self startPaymentWithProduct:product];
        } 
    }
    
    #pragma mark -- 拿到商品信息,创建支付对象
    - (void)startPaymentWithProduct:(SKProduct *)product {
        SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
        payment.applicationUsername = @"myOrderID";
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
    
    #pragma mark -  监听的代理方法
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
        [transactions enumerateObjectsUsingBlock:^(SKPaymentTransaction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            SKPaymentTransaction *transation = obj;
            switch (transation.transactionState) {
                case SKPaymentTransactionStatePurchasing:
                {
                    NSLog(@"购买中");
                }
                    break;
                case SKPaymentTransactionStatePurchased:
                {
                    NSLog(@"交易完成");
                    //获取透传字段
                    NSString *orderNo = transation.payment.applicationUsername;
                    //transactionIdentifier:相当于Apple的订单号
                    NSString *transationId = transation.transactionIdentifier;
                    NSLog(@"orderNo = %@, 交易ID = %@", orderNo, transationId);
                    //从沙盒中获取交易凭证
                    NSData *reciptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
                    //转化成Base64字符串(用于校验)            
                    /*
                     //其作用是将生成的Base64字符串按照64个字符长度进行等分换行。
                     NSDataBase64Encoding64CharacterLineLength = 1UL << 0,               
                     //其作用是将生成的Base64字符串按照76个字符长度进行等分换行。
                     NSDataBase64Encoding76CharacterLineLength = 1UL << 1,                
                     //其作用是将生成的Base64字符串以回车结束。
                     NSDataBase64EncodingEndLineWithCarriageReturn = 1UL << 4,                 
                     //其作用是将生成的Base64字符串以换行结束。
                     NSDataBase64EncodingEndLineWithLineFeed = 1UL << 5,
                     */              
                    //NSString *reciptString = [reciptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
                    NSString *reciptString = [reciptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
                    //NSLog(@"%@",reciptString);
                    //传给后台做二次验证
                    [self checkReceipt:reciptString];
                    [[SKPaymentQueue defaultQueue] finishTransaction:transation];
                }
                    break;
                case SKPaymentTransactionStateFailed:
                {
                    //localizedDescription可以作为提示信息(交易失败无法连接到 iTunes Store)
                    NSLog(@"交易失败%@", transation.error.localizedDescription);
                    [[SKPaymentQueue defaultQueue] finishTransaction:transation];
                }
                    break;
                case SKPaymentTransactionStateRestored:
                {
                    NSLog(@"恢复购买完成");
                    //恢复完成(对应restoreCompletedTransactions)方法
                }
                    break;
                case SKPaymentTransactionStateDeferred:
                {
                    NSLog(@"交易推迟, 等待外部操作");
                    //交易推迟
                    //官方解释是:交易已经加入队列,但是需要等待外部操作
                    //主要用于儿童模式,需要询问家长同意。这种情况下不能关闭订单(完成交易),否则这类充值将无法处理。
                }
                    break;
                default:
                    break;
            }
        }];
    }
    
    #pragma mark -- 从沙盒中取到凭证,发送给我们自己后台进行二次验证,验证成功表示支付成功
    - (void)checkReceipt:(NSString *)receipt {
        //NSLog(@"%@",receipt);
        // 拼接请求数据
        NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\":\"%@\"}", receipt];
        NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
        
        //1.创建会话对象
        NSURLSession *session = [NSURLSession sharedSession];
        //2.根据会话对象创建task
        NSURL *url = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];    
        //3.创建可变的请求对象
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];    
        //4.修改请求方法为POST
        request.HTTPMethod = @"POST";   
        //5.设置请求体
        request.HTTPBody = bodyData;    
        //6.根据会话对象创建一个Task(发送请求)
        /*
         第一个参数:请求对象
         第二个参数:completionHandler回调(请求完成【成功|失败】的回调)
         data:响应体信息(期望的数据)
         response:响应头信息,主要是对服务器端的描述
         error:错误信息,如果请求失败,则error有值
         */
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {        
            //8.解析数据
            //NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
            NSLog(@"%@",[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);       
        }];  
        //7.执行任务
        [dataTask resume];
    }
    @end
    

    参考文章:

    1. iOS开发支付篇——内购(IAP)详解
    2. IOS内购(IAP)的那些事
    3. 【iOS】苹果IAP(内购)中沙盒账号使用注意事项

    相关文章

      网友评论

          本文标题:iOS - 内购IAP

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