一:內(nèi)購(gòu)流程
二:代碼實(shí)現(xiàn):內(nèi)購(gòu)工具類的集成
1.導(dǎo)入庫(kù)
#import <StoreKit/StoreKit.h>
2.遵守協(xié)議
<SKPaymentTransactionObserver, SKProductsRequestDelegate>
3.內(nèi)購(gòu)工具類的啟動(dòng)與注銷
程序啟動(dòng)就開(kāi)啟工具的原因: 簡(jiǎn)單來(lái)說(shuō)是為了防漏單馁痴,詳情在下面配合代碼來(lái)解釋筋栋。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/**啟動(dòng)IAP工具類*/
[[IAPManager shared] startManager];
return YES;
}
//程序推出的時(shí)候關(guān)閉工具
- (void)applicationWillTerminate:(UIApplication *)application {
/**結(jié)束IAP工具類*/
[[IAPManager shared] stopManager];
}
4.內(nèi)購(gòu)工具類的啟動(dòng)與注銷
內(nèi)購(gòu)支付兩個(gè)階段:
- 階段一: app直接向蘋(píng)果服務(wù)器請(qǐng)求商品,支付階段裸扶;
- 階段二: 蘋(píng)果服務(wù)器返回憑證,app向公司服務(wù)器發(fā)送驗(yàn)證凌蔬,公司再向蘋(píng)果服務(wù)器驗(yàn)證階段闲礼。
- (void)startManager { //開(kāi)啟監(jiān)聽(tīng)
/*
階段一正在進(jìn)中,app退出。
在程序啟動(dòng)時(shí)秤茅,設(shè)置監(jiān)聽(tīng)稚补,監(jiān)聽(tīng)是否有未完成訂單,有的話恢復(fù)訂單框喳。
*/
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
/*
階段二正在進(jìn)行中,app退出课幕。
在程序啟動(dòng)時(shí),檢測(cè)本地是否有receipt文件帖努,有的話,去二次驗(yàn)證粪般。
*/
[self checkIAPFiles];
}
- (void)stopManager{ //移除監(jiān)聽(tīng)
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
5.通過(guò)產(chǎn)品ID發(fā)起查詢商品請(qǐng)求
- (void)requestProductWithId:(NSString *)productId {
if ([SKPaymentQueue canMakePayments]) { //用戶允許app內(nèi)購(gòu)
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(@"沒(méi)有權(quán)限");
}
}
6.查詢成功
#pragma mark SKProductsRequestDelegate 查詢成功后的回調(diào)
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *product = response.products;
if (product.count == 0) {
NSLog(@"無(wú)法獲取商品信息拼余,請(qǐng)重試");
} else {
//發(fā)起購(gòu)買(mǎi)請(qǐng)求
SKPayment * payment = [SKPayment paymentWithProduct:product[0]];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
}
7.查詢失敗
#pragma mark SKProductsRequestDelegate 查詢失敗后的回調(diào)
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(@"查詢失敗:%@",[error localizedDescription]);
}
8.步驟6中查詢成功后發(fā)起了購(gòu)買(mǎi)請(qǐng)求,用戶操作付款后的回調(diào)
#pragma Mark 購(gòu)買(mǎi)操作后的回調(diào)
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing://正在交易
break;
case SKPaymentTransactionStatePurchased://交易完成
//獲取蘋(píng)果訂單號(hào)
//self.transaction_id = transaction.transactionIdentifier;
[self getReceipt]; //獲取交易成功后的購(gòu)買(mǎi)憑證
[self saveReceipt]; //存儲(chǔ)交易憑證
[self checkIAPFiles];//把self.receipt發(fā)送到服務(wù)器驗(yàn)證是否有效
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed://交易失敗
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored://已經(jīng)購(gòu)買(mǎi)過(guò)該商品
[self restoreTransaction:transaction];
break;
default:
break;
}
}
}
9.獲取交易成功后的購(gòu)買(mǎi)憑證
注:驗(yàn)證用的receipt亩歹,不管是你處理匙监,還是讓服務(wù)器處理,發(fā)給蘋(píng)果驗(yàn)證的時(shí)候小作,必須是一個(gè)base64編碼的字符串亭姥。
- (void)getReceipt {
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
self.receipt = [receiptData base64EncodedStringWithOptions:0];
}
10.先將購(gòu)買(mǎi)憑證存到本地
目的:防止用戶付款拿到receipt后,app發(fā)送給公司服務(wù)器的過(guò)程中顾稀,程序閃退等原因致使憑證丟失达罗。
#pragma mark 持久化存儲(chǔ)用戶購(gòu)買(mǎi)憑證(這里最好還要存儲(chǔ)當(dāng)前日期,用戶id等信息,用于區(qū)分不同的憑證)
-(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中將憑證存到了本地粮揉,下面的方法就是查詢本地找到憑證巡李,發(fā)送給服務(wù)器;
- 同時(shí)這個(gè)方法也會(huì)在程序啟動(dòng)即:內(nèi)購(gòu)工具類啟動(dòng)的時(shí)候調(diào)用扶认,如果能找到本地文件侨拦,說(shuō)明上次因?yàn)殚W退等原因?qū)е聭{證沒(méi)發(fā)送給服務(wù)器, 將會(huì)再次發(fā)送辐宾。(后面有驗(yàn)證成功后憑證的處理方式)
- (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后綴的文件狱从,說(shuō)明就是存儲(chǔ)的購(gòu)買(mǎi)憑證
NSString *filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];
[self sendAppStoreRequestBuyPlist:filePath];
}
}
} else {
NSLog(@"AppStoreInfoLocalFilePath error:%@", [error domain]);
}
}
12.將購(gòu)買(mǎi)憑證發(fā)送到公司服務(wù)器,根據(jù)服務(wù)器向蘋(píng)果驗(yàn)證返回的結(jié)果做相應(yīng)處理
- 如果憑證有效叠纹,及此次交易完成季研,刪除本地的此次憑證。
-(void)sendAppStoreRequestBuyPlist:(NSString *)plistPath {
NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:plistPath];
//這里的參數(shù)請(qǐng)根據(jù)自己公司后臺(tái)服務(wù)器接口定制吊洼,但是必須發(fā)送的是持久化保存購(gòu)買(mǎi)憑證
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[dic objectForKey:receiptKey], receiptKey,
[dic objectForKey:dateKey], dateKey,
[dic objectForKey:userIdKey], userIdKey,
nil];
#warning 在這里將憑證發(fā)送給服務(wù)器
if(@"憑證有效"){
[self removeReceipt];
} else {//憑證無(wú)效
//做你想做的
}
}
13.刪除憑證
-(void)removeReceipt{
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:[SandBoxHelper iapReceiptPath]]) {
[fileManager removeItemAtPath:[SandBoxHelper iapReceiptPath] error:nil];
}
}
14.結(jié)束交易
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
15.如果交易失敗训貌,做相應(yīng)的提示,并在將交易結(jié)束
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
if(transaction.error.code != SKErrorPaymentCancelled) {
NSLog(@"購(gòu)買(mǎi)失敗");
} else {
NSLog(@"用戶取消了交易");
}
//將交易結(jié)束
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
16.恢復(fù)已經(jīng)購(gòu)買(mǎi)過(guò)的產(chǎn)品
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
17.封裝成工具后的使用方法
一句代碼搞定
- (void)payClick {
[[IAPManager shared] requestProductWithId:productId];
}
以上便為內(nèi)購(gòu)的全部流程冒窍,這里為代碼地址:
GitHub:https://github.com/YZQ-Nine/IAPDemo