美文网首页
iOS 苹果内购

iOS 苹果内购

作者: XieHenry | 来源:发表于2020-03-13 19:40 被阅读0次

    公司性质乃是少儿英语在线教育培训。app内需要上课。因此苹果规定需要集成内部购买功能,做之前一直不了解怎么形成一个好的闭环。参阅网上多个博客以及自己的思考。记录一下,希望对大家有一个帮助,以及对自己的总结。

    本文章的重点问题是:

    1.iTunes Connect的配置(网上自行搜索,很详细)
    2.常用的购买代码
    3.漏单问题处理
    4.后台交互

    2.常用的购买代码

    首选需要引入头文件以及添加代理。

    #import <StoreKit/StoreKit.h>
    <SKProductsRequestDelegate,SKPaymentTransactionObserver>
    #define AppStore_BuyClassHours @"appstore_buyclasshours"  //购买课时
    
    

    1.在viewDidLoad内需要添加监听。

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.title = @"购买课时";
    
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    

    2.通过购买事件触发 购买。

    -(void)buyHourClassClick {
        if([SKPaymentQueue canMakePayments]){
            [self requestProductData:self.currentProId];   //self.currentProId是当前购买产品的 产品Id(在itunes connect配置的)
        } else { 
            UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"内购未开启" message:@"进入【设置】-【屏幕使用时间】-【内容和隐私访问限制】-【iTunes Store 与 App Store 购买项目】-【App内购买项目】- 选择“允许”,将该功能开启" preferredStyle:UIAlertControllerStyleAlert];
            
            UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                [self dismissViewControllerAnimated:YES completion:nil];
            }];
            [alert addAction:defaultAction];
            [self presentViewController:alert animated:YES completion:nil];
        }
    }
    

    3.客户端向苹果发起请求,获取内购的商品

    - (void)requestProductData:(NSString *)type{
        dispatch_async(dispatch_get_main_queue(), ^{
            [MBProgressHUD showLoadingAndMessage:@"开始请求商品列表" toView:self.view];
        });
        NSArray *product = [[NSArray alloc] initWithObjects:type,nil];
        NSSet *nsset = [NSSet setWithArray:product];
        SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
        request.delegate = self;
        [request start];
    }
    

    4.客户端获取全部商品成功,根据你选择的产品id去苹果服务器发起支付请求

    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
        
        NSLog(@"--------------收到产品反馈消息---------------------");
        NSArray *product = response.products;
        if([product count] == 0){
            dispatch_async(dispatch_get_main_queue(), ^{
                [MBProgressHUD hideHUDForView:self.view];
            });
            NSLog(@"--------------没有商品------------------");
            return;
        }
        
        dispatch_async(dispatch_get_main_queue(), ^{
            [MBProgressHUD showLoadingAndMessage:@"商品添加进列表" toView:self.view];
        });
        for (SKProduct *pro in product) {
            NSLog(@"SKProduct 描述信息:%@", [pro description]);
            NSLog(@"localizedTitle 产品标题:%@", [pro localizedTitle]);
            NSLog(@"localizedDescription 产品描述信息:%@", [pro localizedDescription]);
            NSLog(@"price 价格:%@", [pro price]);
            NSLog(@"productIdentifier Product id:%@", [pro productIdentifier]);
            
            if([pro.productIdentifier isEqualToString:_currentProId]){
                // 1.创建票据
                SKPayment *skpayment = [SKPayment paymentWithProduct:pro];
                // 2.将票据加入到交易队列
                [[SKPaymentQueue defaultQueue] addPayment:skpayment];
            }
        }
    }
    

    5.监听购买结果

    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
        /*
         SKPaymentTransactionStatePurchasing,    正在购买
         SKPaymentTransactionStatePurchased,     已经购买
         SKPaymentTransactionStateFailed,        购买失败
         SKPaymentTransactionStateRestored,      回复购买中
         SKPaymentTransactionStateDeferred       交易还在队列里面,但最终状态还没有决定
         */
        
        
        for(SKPaymentTransaction *tran in transactions) {
            
            switch (tran.transactionState) {
                case SKPaymentTransactionStatePurchasing: {
                    NSLog(@"商品正在购买...");
                    
                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                        [MBProgressHUD hideHUDForView:self.view];
                        [MBProgressHUD showLoadingAndMessage:@"商品正在购买..." toView:self.view];
                    });
                }
                    break;
                    
                case SKPaymentTransactionStatePurchased:{
                    if (!IsStrEmpty(self.orderIDStr)) {
                        NSLog(@"购买成功之后才保存订单id,否则有订单,没购买成功,也会在首页弹窗");
                        
                        //保存凭证--自定义操作
                        NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
                        NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
                        /*1.传输的是BASE64编码的字符串
                         2.BASE64常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
                         3.BASE64是可以编码和解码的
                         */
                        NSString *receiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
                        
                        NSDictionary *buyClassHourSaveDic = @{@"orderID":self.orderIDStr,@"appleReceipt":receiptString};
                        [UserDefaults() setObject:buyClassHourSaveDic forKey:AppStore_BuyClassHours];
                        [UserDefaults() synchronize];
                    }
                    
                    [MBProgressHUD hideHUDForView:self.view];
                    [self verifyPurchaseWithPaymentTransaction:tran];
                }
                    
                    break;
                    
                case SKPaymentTransactionStateRestored:{
                    NSLog(@"已经购买过商品");
                    [MBProgressHUD showMessage:@"已经购买过商品" toView:self.view];
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }
                    break;
                case SKPaymentTransactionStateFailed:{//只有自己取消购买,才会弹窗。
                    NSLog(@"交易失败");
                    [MBProgressHUD hideHUDForView:self.view];
    
                    if (!IsStrEmpty(self.orderIDStr)) {
                        [self buyErrorAlert:tran];
                    } else {
                        [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                        [MBProgressHUD showMessage:@"交易失败" toView:self.view];
                    }
                    
                }
                    break;
                case SKPaymentTransactionStateDeferred://交易延迟
                    break;
                default:
                    break;
            }
        }
    }
    
    -(void)buyErrorAlert:(SKPaymentTransaction *)transaction {
        
        self.alert = [UIAlertController alertControllerWithTitle:@"交易提醒" message:@"支付遇到问题?别着急,一键获取支付帮助" preferredStyle:UIAlertControllerStyleAlert];
        
        UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"取消支付" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            //置空。
            [UserDefaults() setObject:@{} forKey:AppStore_BuyClassHours];
            [UserDefaults() synchronize];
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            
            [self dismissViewControllerAnimated:YES completion:nil];
        }];
        [self.alert addAction:defaultAction];
        
        UIAlertAction *successAction = [UIAlertAction actionWithTitle:@"继续支付" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
          
            [self dismissViewControllerAnimated:YES completion:nil];
            
            [self buyHourClassClick];
        }];
        [self.alert addAction:successAction];
        
        [self presentViewController:self.alert animated:YES completion:nil];
    }
    
    //请求失败
    - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
        [MBProgressHUD showMessage:@"支付失败" toView:self.view];
        NSLog(@"------------------错误-----------------:%@", error);
    }
    
    - (void)requestDidFinish:(SKRequest *)request{
        dispatch_async(dispatch_get_main_queue(), ^{
            [MBProgressHUD hideHUDForView:self.view];
        });
        NSLog(@"------------反馈信息结束-----------------");
    }
    
    //交易结束
    - (void)completeTransaction:(SKPaymentTransaction *)transaction{
        NSLog(@"交易结束");
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    }
    
    
    - (void)dealloc{
        NSLog(@"控制器--%@--销毁了", [self class]);
        [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
    }
    
    

    6.购买成功后的二次验证

    //MARK:2.2支付成功后,苹果返回给你一个receipt收据,收据包含你这次交易的全部信息,产品id,交易号,时间等。
    -(void)verifyPurchaseWithPaymentTransaction:(SKPaymentTransaction *)transaction {
        if (self.alert) {
            [self dismissViewControllerAnimated:YES completion:nil];//进入之后,走了失败,会dissmiss
        }
        
        [MBProgressHUD showLoading:self.view];
        
        
        NSString *orderIdStr = @"";    //预下订单id
        NSString *receiptString = @""; //苹果返回的凭证
        NSDictionary *buyClassHourSaveDic = [UserDefaults() objectForKey:AppStore_BuyClassHours];
        if ([buyClassHourSaveDic isKindOfClass:[NSDictionary class]] && [buyClassHourSaveDic count] > 0) {
            orderIdStr = [NSString stringWithFormat:@"%@",[buyClassHourSaveDic objectForKey:@"orderID"]];
            receiptString = [NSString stringWithFormat:@"%@",[buyClassHourSaveDic objectForKey:@"appleReceipt"]];
        } else {
            NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
            NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
            receiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串
            orderIdStr = self.orderIDStr;
        }
        
        
        
        if (!IsStrEmpty(receiptString) && !IsStrEmpty(orderIdStr)) {
            NSDictionary *postDic = @{@"appleReceipt":receiptString,@"AP":orderIdStr};
            
            [[BaseService share] sendPostRequestWithPath:URL_ApplePurchaseNotify parameters:postDic token:YES viewController:self showMBProgress:YES success:^(id responseObject) {
                [MBProgressHUD hideHUDForView:self.view];
                
                
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                //购买成功之后,置空。并删除本地的凭证。 在首页对这个字段进行判断。如果不为空,证明有未完成的订单。
                [UserDefaults() setObject:@{} forKey:AppStore_BuyClassHours];
                [UserDefaults() synchronize];
                
                [self getProductData];
                
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    [MBProgressHUD showMessage:responseObject[@"msg"] toView:self.view];
                });
                
            } failure:^(NSError *error) {
                
                //其他状态置空---只要走这个接口,不管失败成功,后台都可以将这两个字段对应保存。
                [UserDefaults() setObject:@{} forKey:AppStore_BuyClassHours];
                [UserDefaults() synchronize];
                
                [MBProgressHUD hideHUDForView:self.view];
                
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                
                [self getProductData];
                
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    [MBProgressHUD showMessage:error.userInfo[xc_returnMsg] toView:self.view];
                });
            }];
            
            
        } else { //如果为空,取消队列,重新购买、
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [MBProgressHUD showMessage:@"支付遇到问题,请联系客服" toView:self.view];
            });
        }
        
    }
    
    
    
    3.漏单问题处理

    因为苹果是先扣款再进行交易验证,因此部分情况下会造成用户付款后,没有验证,造成未发道具给用户的情况。就造成了漏单问题。
    我们是后台自己先做了一个预下订单,即代码中的self.orderIDStr。如果我购买成功,我就将这个【订单号和苹果购买凭证】保存到本地。二次验证成功,就置空。
    1.如果苹果扣款成功,2次验证失败,可以在本页面根据自己的需要在6中操作。
    2.如果苹果扣款成功,还未做验证就退出了app。可以在首页判断本地plist中AppStore_BuyClassHours是否为空,做一个弹窗,点击跳转到购买页,自动执行代理。然后二次验证。
    3.如果苹果扣款成功,还未做验证就卸载了app。然后还换了手机或者重新安装了,就只能联系客服发付款截图给课时了。

    ps:我的逻辑也不是太严谨,根据自己的逻辑处理吧。

    4.后台交互

    app内可以加二次验证的,但是怕后续出现问题太多。(百度可以查到好多app内验证的博客)比如:
    1.沙盒付款,发送到苹果正式环境
    2.appstore付款,发送到了沙盒环境,等多种问题。

    后台代码博客

    我就直接让后台(如果遇到问题了,后台更新也快)做了这个处理。

    相关文章

      网友评论

          本文标题:iOS 苹果内购

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