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

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

应用内支付流程展示学习.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

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

推荐阅读更多精彩内容