美文网首页iOS Developer程序员iOS开发
2018最新研究应用内支付2:代码封装学习

2018最新研究应用内支付2:代码封装学习

作者: 小白哥有话说 | 来源:发表于2018-06-28 18:01 被阅读74次

    衣带渐宽终不悔,为伊消得人憔悴,经过近几天的刻苦研读和高效整理,终有成果!

    应用内支付流程展示学习.gif

    目录:

    一.应用内支付Code展示学习
    
    二.支付深度理解层次
    
    三.支付遇到的一些坑
    
    四.手把手集成我的Demo
    

    一.应用内支付Code展示学习

    接着上篇文章最后对应用内支付流程梳理步骤进行深化;本篇同样分为支付前,支付中,支付后流程。

    1.支付前Code展示

    1.1注册通知以下3个通知:

    -(void)addAllNoti {
        
        // 获取商品信息通知
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(responseToOrderDatas:)
                                                     name:IAPZWGoodsRequestNotification
                                                   object:[IAPZWGoodsManager sharedInstance]];
        
        // 是否下单成功的通知
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(responseToAllGoodsIsSuccess:)
                                                     name:IAPZWBuyResultNotification
                                                   object:[IAPZWGoodsNoti sharedInstance]];
        // 取消支付的通知
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(responseToAllGoodsCancle:)
                                                     name:IAPZWBuyResultNotificationCancle
                                                   object:[IAPZWGoodsNoti sharedInstance]];
    }
    
    

    当然也要在dealloc方法中注销通知

    - (void)dealloc
    {
        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                        name:IAPZWGoodsRequestNotification
                                                      object:[IAPZWGoodsNoti sharedInstance]];
        
        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                        name:IAPZWBuyResultNotification
                                                      object:[IAPZWGoodsNoti sharedInstance]];
        
        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                        name:IAPZWBuyResultNotificationCancle
                                                      object:[IAPZWGoodsNoti sharedInstance]];
    }
    

    1.2获取商品信息的方法:

    // 获取所有的商品信息
    -(void)getAllAppleProductInfoDatas
    {
        // Query the App Store for product information if the user is is allowed to make purchases.
        // Display an alert, otherwise.
        if([SKPaymentQueue canMakePayments])
        {
            [[DisplayHelper shareDisplayHelper]showLoading:self.view noteText:@"加载商品中..."];
            // 正确方式为从后台获取,这里本地写死商品id
            NSArray *productIds = @[@"1008612345",@"1008611111"];
            [[IAPZWGoodsManager sharedInstance] requestGetAllGoodsProductIds:productIds];
    //        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    //            [[DisplayHelper shareDisplayHelper]hideLoading:self.view];
    //
    //        });
            
        }
        
        else
        {
            
        }
    
    }
    
    

    1.3在刚才的通知中回调对应的数据如下方法:

    -(void)responseToOrderDatas:(NSNotification *)notification
    {
        IAPZWGoodsManager *goodsManager = (IAPZWGoodsManager*)notification.object;
        IAPGoodsRequestStatus status = goodsManager.status;
        
        [[DisplayHelper shareDisplayHelper]hideLoading:self.view];
        
        if (status == IAPGoodsRequestResponse)
        {
    
            
            self.dataArr = goodsManager.availableProducts;
            [self.tableView reloadData];
        }
    }
    

    2.支付中Code展示(类似微信支付和支付宝支付的下单流程)

    2.1用户点击Cell发起下单请求:

    // 下单商品
    -(void)buyAllGood:(SKProduct *)product
    {
        SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
    

    注意如果发现上次还有未验证的订单可通过下面方法重新验证:

    // 如果存在的话即为上次未来的及验证的订单重新验证(这里没有做相关逻辑)
    -(void)restoreAllGoods
    {
        [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
    }
    

    2.2在调起苹果支付页面后回调到以下这个方法

    // 支付过程中的回调
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
    {
        for(SKPaymentTransaction * transaction in transactions)
        {
            switch (transaction.transactionState )
            {
                // 1.第一次调起苹果支付框时走这里
                case SKPaymentTransactionStatePurchasing:
                    break;
                    
                case SKPaymentTransactionStateDeferred:
    
                    break;
                    // 2.这里有时会走2次,其中比较关心的一次是 IAPZWBuyGoodsSucceeded 这一次
                case SKPaymentTransactionStatePurchased:
                {
                    self.purchasedID = transaction.payment.productIdentifier;
                    [self.goodProductIds addObject:transaction];
                    
    
                    if(transaction.downloads && transaction.downloads.count > 0)
                    {
                        [self completeTransaction:transaction forStatus:IAPZWDownOrderStarted];
                    }
                    else
                    {
                        [self completeTransaction:transaction forStatus:IAPZWBuyGoodsSucceeded];
                    }
                }
                    break;
                case SKPaymentTransactionStateRestored:
                {
                    self.purchasedID = transaction.payment.productIdentifier;
                    [self.goodRestoredIds addObject:transaction];
                    
                    NSLog(@"SKPaymentTransactionStateRestored %@",transaction.payment.productIdentifier);
                    if(transaction.downloads && transaction.downloads.count > 0)
                    {
                        [self completeTransaction:transaction forStatus:IAPZWDownOrderStarted];
                    }
                    else
                    {
                        [self completeTransaction:transaction forStatus:IAPZWRestoredSucceeded];
                    }
                }
                    break;
                    // 3.测试中会发现有时走这个方法
                case SKPaymentTransactionStateFailed:
                {
                    self.errorMsg = [NSString stringWithFormat:@"SKPaymentTransactionStateFailed %@ ",transaction.payment.productIdentifier];
                    [self completeTransaction:transaction forStatus:IAPZWBuyGoodsFailed];
                }
                    break;
                default:
                    break;
            }
        }
    }
    
    

    注意:在调用第一次弹出支付框,如下所示

    支付流程输入账号和密码等.png

    输入账号和密码后,且密码正确的情况下,点击好后会调用第二次该方法,之后可能调用第三次该方法

    2.3在成功调用上面方法之后,会通过以下方式发通知

    // 完成支付
    -(void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(NSInteger)status
    {
        self.status = status;
        if (transaction.error.code != SKErrorPaymentCancelled)
        {
            [[NSNotificationCenter defaultCenter] postNotificationName:IAPZWBuyResultNotification object:self];
        }
        
        if (status == IAPZWDownOrderStarted) // 开始下单
        {
            [[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
        }
        else // 已经下单购买完成
        {
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        }
    }
    

    之后回调到下面的方法中

    // 完成支付
    -(void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(NSInteger)status
    {
        self.status = status;
        if (transaction.error.code != SKErrorPaymentCancelled)
        {
            [[NSNotificationCenter defaultCenter] postNotificationName:IAPZWBuyResultNotification object:self];
        }
        
        if (status == IAPZWDownOrderStarted) // 开始下单
        {
            [[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
        }
        else // 已经下单购买完成
        {
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        }
    }
    

    3.支付后Code展示(重点在双重认证

    根据是否支付成功,若支付成功的话,开始双重认证

    3.1先本地认证是否可以获得到receipt

    - (void)completeAllDownOrderIsSuccess:(NSString *)productIdentifier {
    
        NSData *receiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
        NSString *receipt = [receiptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
        [[DisplayHelper shareDisplayHelper]hideLoading:self.view];
        if ([receipt length] > 0 && [productIdentifier length] > 0) {
    
            /**
             可以将receipt发给服务器进行购买验证
             */
            
            [self sendRequestReceiptToAppStore:receipt];
            
            
            
        }
        //    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    }
    

    3.2客户端去苹果服务器认证

    -(void)sendRequestReceiptToAppStore:(NSString *)receipt
    {
        NSError *error;
        NSDictionary *requestContents = @{@"receipt-data": receipt};
        NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
        
        if (!requestData) {
            
        }else{
            
        }
        //    NSURL *storeURL;
        //#ifdef DEBUG
        //    storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
        //#else
        //    storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
        //#endif
        
        NSURL *storeURL;
        if (kIsProduction) {
            storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
        }else {
            storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
        }
        
        NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
        [storeRequest setHTTPMethod:@"POST"];
        [storeRequest setHTTPBody:requestData];
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        WS(ws);
        [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
                               completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                                   if (connectionError) {
                                       /* 处理error */
                                   } else {
                                       NSError *error;
                                       NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
                                       if (!jsonResponse) { // 出错的情况
                                           /* 处理error */
                                           dispatch_async(dispatch_get_main_queue(), ^{
                                               [DisplayHelper displayWarningAlert:@"支付失败!"];
                                           });
    
                                       }else{
                                           /* 处理验证结果 */
                                           if ([[jsonResponse allKeys]containsObject:@"status"]) {
                                               NSString *statusStr =jsonResponse[@"status"];
                                               if ([statusStr intValue] ==0) { // 成功后发服务器进行第二层验证
                                                   // 重发我们自己服务器的验证
                                                   [ws requestPayIsSuccess:receipt];
                                               }else {
                                                   dispatch_async(dispatch_get_main_queue(), ^{
                                                       if ([statusStr intValue] ==21002) { // 请求过于频繁,有刷单嫌疑,需重发服务器
                                                           // 重发我们自己服务器的验证
                                                           [ws requestPayIsSuccess:receipt];
                                                       }else { // 其他失败的情况
                                                           [DisplayHelper displayWarningAlert:@"支付失败!"];
                                                       }
                                                       
                                                   });
                                                   
                                               }
                                               
                                           }
                                           
                                       }
                                   }
                               }];
        
    }
    

    3.3客户端让我们的服务端去苹果服务器认证
    3.4服务端修改对应的商品字段,有必要的话,客户端也要进行修改

    二.支付深度理解层次

    1.区分应用内支付中内和外的关系

    一般的微信支付宝支付时,是调用支付接口时直接跳转到对应的App,可以很明显的感觉到支付前后在App的内外关系,而对于苹果应用内支付呢,则必须区分开逻辑的内外,才算是真正的明白该支付的特点。

    以本次的Demo为例:

    当支付的输入输出框展示在您的面前时,就证明在应用外开始支付了,而当该框消失或者支付成功后如Demo中展示“支付成功!”时,就证明该进入应用内完成支付了!

    所以处理好 支付:应用内到应用外;支付完成:应用外回调到应用内

    2.商品列表请求和处理流程注意

    2.1请求商品列表最好为接口通过接口返回而不是本地写死的情况

    2.2支付过程中

    • (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions

    中的状态为SKPaymentTransactionStatePurchased 时回调2次的区分为第一次为开始,第二次为成功请注意区分,本人当时就是爬的这里的坑造成的

    2.3支付成功后不要忘了双重认证服务端

    三.支付遇到的一些坑

    1.双重校验的问题

    什么是双重验证?
    第一层认证为客户端和苹果那边认证是否支付成功,第二层认证为服务端和苹果那边认证是否支付成功

    注意:客户端和苹果那边验证完成返回0为正常支付成功,但是返回status为21002时,官方解释是请求太频繁,有刷单嫌疑。故而这种情况也要把对应的数据发给服务端验证。

    2.关于沙盒账户的使用

    测试前需要先把AppStore中真实AppID给退出,之后再进行测试,避免调用失败;实际测试中发现一个沙盒账户只能绑定一个手机,请各位自己测试告知。

    3.在类线上支付测试时充值问题

    对真实AppID进行充值支付时需要验证以前的密码以及密保问题等

    4.测试账号添加问题

    测试账号添加时,密码不要设置过于简单,不然就会出现添加测试账号失败的问题;同样沙盒模式下登录前请退出以前的登录账户才能登录沙盒账户。另外沙盒模式验证完毕后请记得退出原来的沙盒账户,之后登录自己正式的账户,不然后续下载软件就会下载不了,总之,验证沙河模式后及时重新替换成自己的账户,避免来回使用麻烦。

    5. SKPaymentTransactionStatePurchased 方法回调

    下单完成后回调到此地时,一般会走二次,第一次为开始下订单;第二次为下订单成功的情况,详见Demo中区分情况。

    四.手把手集成我的Demo

    1.导入的类库及介绍

    #import "IAPZWGoodsNoti.h"
    #import "IAPZWGoodsManager.h"
    #import <StoreKit/StoreKit.h>
    

    2.Demo中类的介绍

    IAPZWGoodsNoti 主要为获取商品列表的类
    IAPZWGoodsManager 主要为支付过程中监听的帮助类

    3.其他注意细节

    按照以上的步骤执行后发现,咦,怎么还是无法支付成功呢?嘿嘿干货肯定要在最后展示哈!

    3.1AppDeleage注册监听和取消监听问题

    #pragma mark - UIApplication Methods
    
    - (void)applicationWillTerminate:(UIApplication *)application
    {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
        
        // Remove the observer
        [[SKPaymentQueue defaultQueue] removeTransactionObserver: [IAPZWGoodsNoti sharedInstance]];
    }
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        [[UINavigationBar appearance]setBarTintColor:RGBCOLOR(129, 188, 53)];
        [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
        
        self.window =[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
        [self initRootViewController];
        [self.window makeKeyAndVisible];
    #warning 记得调用初始化分享或支付SDK的代码
    //    // 初始化ShareSDK
    //    [self initSharedKey];
    //        
    //    // 初始化微信支付
    //    [self initWpay];
        
        [[SKPaymentQueue defaultQueue] addTransactionObserver:[IAPZWGoodsNoti sharedInstance]];
    
        
    
        return YES;
    }
    

    3.2如BoundId问题
    更换 BoundId为您的可上线项目的BoundId

    3.3是否是生产环境问题
    全局搜索 kIsProduction 修改为0表示沙盒模式,为1为线上环境,注意!

    3.4请求商品列表问题
    从后台获取对应的商品列表之后再为下单处理,而不是本地写死,避免上线后无法更改。

    3.5双重认证再次强调问题
    保险起见,一定要做双重认证,另外返回status为21002时,也要发给后台进行是否支付成功的验证。

    参考资料:

    1.iOS之支付:https://www.jianshu.com/p/a9e17f50df9e
    2.iOS开发内购全套图文教程:https://www.jianshu.com/p/86ac7d3b593a
    3.iOS 内购支付两种模式:https://www.jianshu.com/p/15086493768a
    4.谈谈苹果应用内支付(IAP)的坑:https://www.jianshu.com/p/c3c0ed5af309
    5.【iOS】苹果IAP(内购)中沙盒账号使用注意事项:https://www.jianshu.com/p/1ef61a785508
    

    最后附上:
    2018最新研究应用内支付1:准备工作:https://www.jianshu.com/p/f090740991dd
    最终的Demo地址:https://github.com/zxwIsCode/IAPZWDemo

    最后期待下一篇,应用内支付上线注意的问题等

    相关文章

      网友评论

        本文标题:2018最新研究应用内支付2:代码封装学习

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