一:內購流程
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 {
//發起購買請求
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