App内购通关:(二)代码篇
2016-12-01 17:47
435 查看
一:内购流程
二:代码实现:内购工具类的集成
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退出。 在程序启动时,设置监听,监听是否有未完成订单,有的话恢 4000 复订单。 */ [[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/IAPDemoApp内购通关:(一)非代码准备篇
相关文章推荐
- App内购通关:(一)非代码准备篇
- [问]VS2005,C#winform程序,代码修改app.config的结果保存到哪里了?
- Oracle开放Oracle App Server与Spring Framework的集成代码
- 用Wing IDE来调试Google App Engine代码
- 如何在自己的程序中添加appWidget(附简单代码)
- android java代码的启动:app_process
- App-V 错误代码总结
- android java代码的启动:app_process
- App加载时,检测其它程序是否在发声的代码
- C#winform 程序,代码修改app.config
- 开发ASP.NET 2.0 Web应用程序时如何将App_Code文件夹中的共享代码配置生成多个Dll
- Oracle开放Oracle App Server与Spring Framework的集成代码
- 开发ASP.NET 2.0 Web应用程序时如何将App_Code文件夹中的共享代码配置生成多个Dll
- DNN错误:代码子目录“/Do/App_Code/HTML/”不存在
- Oracle开放Oracle App Server与Spring Framework的集成代码
- 代码目录 (App_Code 目录)及namespace的理解
- 常用C#代码:WinApp
- Oracle开放Oracle App Server与Spring Framework的集成代码
- 090919(星期六):MFC消息路由4, Frame8代码分析3CWinApp的聚合情况
- 自定义协议从一个App打开另一个App的代码