App内购通关:(二)代码篇

一:内购流程

app内购流程图

二:代码实现:内购工具类的集成

1.导入库

#import <StoreKit/StoreKit.h>

2.遵守协议

<SKPaymentTransactionObserver, SKProductsRequestDelegate>

3.内购工具类的启动与注销

程序启动就开启工具的原因: 简单来说是为了防漏单,详情在下面配合代码来解释。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   /**启动IAP工具类*/
    [[IAPManager shared] startManager];
    return YES;
}

//程序推出的时候关闭工具
- (void)applicationWillTerminate:(UIApplication *)application {
     /**结束IAP工具类*/
    [[IAPManager shared] stopManager];
}

4.内购工具类的启动与注销

内购支付两个阶段:
* 阶段一: app直接向苹果服务器请求商品,支付阶段;
* 阶段二: 苹果服务器返回凭证,app向公司服务器发送验证,公司再向苹果服务器验证阶段。

- (void)startManager { //开启监听
    /*
     阶段一正在进中,app退出。
     在程序启动时,设置监听,监听是否有未完成订单,有的话恢复订单。
     */
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    /*
     阶段二正在进行中,app退出。
     在程序启动时,检测本地是否有receipt文件,有的话,去二次验证。
     */
    [self checkIAPFiles];
}

- (void)stopManager{ //移除监听 
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

5.通过产品ID发起查询商品请求

- (void)requestProductWithId:(NSString *)productId {
    if ([SKPaymentQueue canMakePayments]) { //用户允许app内购
        if (productId.length) {
            NSArray *product = [[NSArray alloc] initWithObjects:productId, nil];
            NSSet *set = [NSSet setWithArray:product];
            SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
            productRequest.delegate = self;
            [productRequest start];
        } else {
            NSLog(@"商品为空");   
        }
    } else { 
          NSLog(@"没有权限");
    }
}

6.查询成功

#pragma mark SKProductsRequestDelegate 查询成功后的回调
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSArray *product = response.products; 
    if (product.count == 0) { 
        NSLog(@"无法获取商品信息,请重试"); 
    } else {
        //发起购买请求
        SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product[0]];
        //为了防止串单,你可以为在后来苹果返回的苹果订单号提前起个名字
        payment.applicationUsername = self.pico_order_num;
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
}

7.查询失败

#pragma mark SKProductsRequestDelegate 查询失败后的回调
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
   NSLog(@"查询失败:%@",[error localizedDescription]);
}

8.步骤6中查询成功后发起了购买请求,用户操作付款后的回调

#pragma Mark 购买操作后的回调
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {

    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing://正在交易
                break;

            case SKPaymentTransactionStatePurchased://交易完成

               //如果你做了步骤6中的苹果账单号赋值,此时你就可以拿到你需求格式的账单号
                if (transaction.payment.applicationUsername) {
                    self.transaction_d = transaction.payment.applicationUsername;
                } else {
                    self.transaction_d = @"transaction_d";
                }
                [self getReceipt]; //获取交易成功后的购买凭证
                [self saveReceipt]; //存储交易凭证
                [self checkIAPFiles];//把self.receipt发送到服务器验证是否有效
                [self completeTransaction:transaction];
                break;

            case SKPaymentTransactionStateFailed://交易失败
                [self failedTransaction:transaction];
                break;

            case SKPaymentTransactionStateRestored://已经购买过该商品
                [self restoreTransaction:transaction];
                break;
            default:
            break;
        }
    }
}

9.获取交易成功后的购买凭证

注:验证用的receipt,不管是你处理,还是让服务器处理,发给苹果验证的时候,必须是一个base64编码的字符串。

- (void)getReceipt {
    NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL]; 
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl]; 
    self.receipt = [receiptData base64EncodedStringWithOptions:0];
}

10.先将购买凭证存到本地

目的:防止用户付款拿到receipt后,app发送给公司服务器的过程中,程序闪退等原因致使凭证丢失。

#pragma mark  持久化存储用户购买凭证(这里最好还要存储当前日期,用户id等信息,用于区分不同的凭证)
-(void)saveReceipt {
    self.date = [NSDate chindDateFormate:[NSDate date]];
    NSString *fileName = [NSString uuid];
    self.userId = @"UserID";
    NSString *savedPath = [NSString stringWithFormat:@"%@%@.plist", [SandBoxHelper iapReceiptPath], fileName];
    NSDictionary *dic =[NSDictionary dictionaryWithObjectsAndKeys:
                        self.receipt,                           receiptKey,
                        self.date,                              dateKey,
                        self.userId,                            userIdKey,
                        nil];  
    [dic writeToFile:savedPath atomically:YES];
}

11.检查本地是否存在凭证

  • 步骤10中将凭证存到了本地,下面的方法就是查询本地找到凭证,发送给服务器;
  • 同时这个方法也会在程序启动即:内购工具类启动的时候调用,如果能找到本地文件,说明上次因为闪退等原因导致凭证没发送给服务器, 将会再次发送。(后面有验证成功后凭证的处理方式)
- (void)checkIAPFiles{
    NSFileManager *fileManager = [NSFileManager defaultManager];  
    NSError *error = nil;   
    //搜索该目录下的所有文件和目录
    NSArray *cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];    
     if (error == nil) {
        for (NSString *name in cacheFileNameArray) {    
            if ([name hasSuffix:@".plist"]){ //如果有plist后缀的文件,说明就是存储的购买凭证      
                NSString *filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];      
                [self sendAppStoreRequestBuyPlist:filePath];
            }
        } 
    } else {
        NSLog(@"AppStoreInfoLocalFilePath error:%@", [error domain]);
    }
}

12.将购买凭证发送到公司服务器,根据服务器向苹果验证返回的结果做相应处理

  • 如果凭证有效,及此次交易完成,删除本地的此次凭证。
-(void)sendAppStoreRequestBuyPlist:(NSString *)plistPath {

    NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:plistPath];
    //这里的参数请根据自己公司后台服务器接口定制,但是必须发送的是持久化保存购买凭证
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                   [dic objectForKey:receiptKey],          receiptKey,
                                   [dic objectForKey:dateKey],             dateKey,
                                   [dic objectForKey:userIdKey],           userIdKey,
                                   nil];

#warning 在这里将凭证发送给服务器

    if(@"凭证有效"){    
        [self removeReceipt];    
    } else {//凭证无效     
        //做你想做的
    }
}

13.删除凭证

-(void)removeReceipt{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:[SandBoxHelper iapReceiptPath]]) {
        [fileManager removeItemAtPath:[SandBoxHelper iapReceiptPath] error:nil];
    }
}

14.结束交易

- (void)completeTransaction:(SKPaymentTransaction *)transaction {
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

15.如果交易失败,做相应的提示,并在将交易结束

- (void)failedTransaction:(SKPaymentTransaction *)transaction {
    if(transaction.error.code != SKErrorPaymentCancelled) {
       NSLog(@"购买失败");
    } else {
       NSLog(@"用户取消了交易");
    }
    //将交易结束
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 
}

16.恢复已经购买过的产品

- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

17.封装成工具后的使用方法

一句代码搞定

- (void)payClick {
    [[IAPManager shared] requestProductWithId:productId];
}

以上便为内购的全部流程,这里为代码地址:

GitHub:https://github.com/YZQ-Nine/IAPDemo

App内购通关:(一)非代码准备篇

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值