您的位置:首页 > 移动开发

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/IAPDemo

App内购通关:(一)非代码准备篇
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  app内购 支付 iOS开发