一:内购流程
二:代码实现:内购工具类的集成
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 {
//发起购买请求
SKPayment * payment = [SKPayment paymentWithProduct:product[0]];
[[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://交易完成
//获取苹果订单号
//self.transaction_id = transaction.transactionIdentifier;
[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