1 IAP詳細(xì)規(guī)則
IAP(In-App Purchase),是指蘋果App Store的應(yīng)用內(nèi)購買,是蘋果為APP內(nèi)購買虛擬商品或服務(wù)提供的一套交易系統(tǒng)。
1.1適用范圍
在APP內(nèi)需要付費(fèi)使用的產(chǎn)品功能或虛擬商品、服務(wù)。例如在斗魚上充魚翅、在視頻APP上沖會員、在王者農(nóng)藥里買皮膚等....這些購買的商品或服務(wù)只能在APP內(nèi)消費(fèi)使用的適用IAP。反之,在京東或淘寶買東西、在滴滴上打車等...這些購買的商品或服務(wù)只能在APP外使用的情況是不適用IAP的。
1.2 IAP類型
IAP是一套商品交易系統(tǒng),而非簡單的支付系統(tǒng)。每一個購買項目都需要在App的itunes connect后臺創(chuàng)建一個商品,提交給蘋果審核,審核通過后,購買項目才會生效。
在創(chuàng)建IAP商品時,主要有四中類型:
- 1.2.1 Consumable products (該類型適用于可多次購買的消耗型項目,如游戲道具、虛擬幣等。)
- 1.2.2 Non-consumable products (該類型適用于一次購買永久有效的項目,如電子書、游戲關(guān)卡等。
該類型項目支持跨設(shè)備同步和本地restore,比如說,用戶在某個App中購買了一本書,可在所有相同Apple ID設(shè)備的App中免費(fèi)獲取這本書,而不要需要借助App本身的帳號體系,即使在App中刪除了這本書,也可免費(fèi)重新獲取。) - 1.2.3 Auto-renewable subscriptions (該類型適用于自動續(xù)費(fèi)的訂閱項目,如Apple Music的按月訂閱,用戶購買后會每月自動續(xù)費(fèi),直到用戶手動取消或者開發(fā)者下架IAP項目。
類似Non-consumable products,該類型也支持跨設(shè)備同步和本地restore機(jī)制。) -
1.2.4 Non-renewable subscriptions (該類型適用于固定有效期的非自動續(xù)費(fèi)項目,如云音樂的會員和一些視頻App的會員。沒有跨設(shè)備同步和本地restore機(jī)制,用戶可以多次購買。)
IAPType.png
*********特別說明本文側(cè)重講解 消耗性項目 的流程*********
2 IAP設(shè)計開發(fā)要點
- 2.1 開發(fā)之前需要 先向AppStore提交資料,填寫協(xié)議、稅務(wù)和銀行業(yè)務(wù) 具體流程可參考 http://www.lxweimin.com/p/cb1c8b4ba2c0
- 2.2 然后需要先在itunes connect后臺創(chuàng)建IAP商品,并按規(guī)范填寫product id、商品名稱、價格、截圖等信息。
如果App當(dāng)前版本支持新增的IAP項目,可不用發(fā)版直接提交IAP審核。如果需要App新功能配合,則需要和App版本一起提交。
《In-App Purchase Configuration Guide for iTunes Connect》詳細(xì)介紹了IAP的創(chuàng)建和提交流程:https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnectInAppPurchase_Guide/Chapters/CreatingInAppPurchaseProducts.html#//apple_ref/doc/uid/TP40013727-CH3-SW1
注意點:
2.2.1盡量不要刪除已創(chuàng)建的IAP
已創(chuàng)建的IAP除了product id之外的所有信息都可以修改,如果刪除了一個IAP,將無法再創(chuàng)建一個相同product id的IAP,也意味著該product id永久失效。而product id一般有特定的命名規(guī)則,用來標(biāo)示App內(nèi)的購買項目,如果命名規(guī)則下有某個product id永久失效,可能會導(dǎo)致整個product id命名規(guī)則都要修改,掉進(jìn)坑里~
2.2.2 注意區(qū)分reference name和display name
eference name是給開發(fā)者自己看的,display name會在IAP支付流程的確認(rèn)購買系統(tǒng)彈窗中展示給用戶,而且不能隨意修改(修改需要重新提交IAP審核),所以命名的時候要弄清楚。 - 2.3 IAP支付流程
IAP的支付模式分為客戶端校驗和服務(wù)端校驗兩種模式,客戶端校驗?zāi)J揭驗槿菀讉卧熘Ц稇{證,安全性比較低,一般只有簡單的單機(jī)APP才會使用,大部分APP都會采用服務(wù)端校驗?zāi)J健?br> 不同的IAP支付流程也會有一些小差異,主要是因為restore機(jī)制,下面是最常用的Consumable products和Non-renewable subscriptions類型的支付流程(服務(wù)端校驗?zāi)J剑?br> 1.用戶準(zhǔn)備購買某個項目時,App客戶端通過product id向蘋果API請求支付信息
2.手機(jī)系統(tǒng)彈窗驗證用戶的Apple ID(可能需要輸入Apple ID密碼或驗證touch ID)
3.Apple ID驗證完成后,蘋果API向App客戶端返回用戶將要支付的價格和貨幣單位
4.手機(jī)系統(tǒng)彈窗提示用戶確認(rèn)將要購買的內(nèi)容和價格,用戶點擊確認(rèn)購買
5.App客戶端獲得蘋果API返回的支付成功通知以及支付憑據(jù),向App服務(wù)端請求校驗支付憑據(jù)
6.App服務(wù)端拿到客戶端的支付憑據(jù),再向蘋果服務(wù)器請求校驗支付憑據(jù)(避免一些越獄插件偽造客戶端支付憑據(jù))
7.App服務(wù)端校驗支付憑據(jù)成功,通知App客戶端
8.App收到支付憑據(jù)校驗成功通知,代表用戶付費(fèi)成功,再處理后續(xù)業(yè)務(wù)邏輯
3 具體的代碼實現(xiàn)
- 3.1 想要讓用戶購買我們的商品,首先我們得有個商品的展示界面。上面我們已經(jīng)在itunes connect后臺創(chuàng)建過我們的商品。創(chuàng)建過的商品都有一個唯一的產(chǎn)品標(biāo)識 ProductID。通過ProductID我們可以拿到商品的具體信息。
3.1.1 商品的請求
- (void)fetchProductInformationForIds:(NSArray *)productIds{
//產(chǎn)品ID可以以Plist的方式放在本地APP,也可以放在本地的服務(wù)器上。不過最好是放在本地服務(wù)器上,當(dāng)后的產(chǎn)品有變化時,就不用升級我們的APP了
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIds]];
request.delegate = self;
[request start];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIds]];
request.delegate = self;
[request start];
}
//蘋果的服務(wù)器通過此方法向我們返回商品信息
#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{`
if (response.products.count > 0) { //有效產(chǎn)品
self.availableProducts = [NSMutableArray arrayWithArray:response.products];
}
if (response.invalidProductIdentifiers.count > 0) {//無效產(chǎn)品標(biāo)示
self.invalidProductIds = [NSMutableArray arrayWithArray:response.invalidProductIdentifiers];
}
self.status = IAPProductRequestResponse;
[[NSNotificationCenter defaultCenter] postNotificationName:IAPProductRequestNotification object:self];
}
//產(chǎn)品請求失敗會調(diào)用此方法
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
self.status = IAPRequestFailed;
[[NSNotificationCenter defaultCenter] postNotificationName:IAPProductRequestNotification object:self];
NSLog(@"Product Request Status: %@",error.localizedDescription);
}
//然后就是產(chǎn)品的展示了
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if (self.itemArr.count > 0) {
SKProduct *product = self.itemArr[indexPath.row];
cell.textLabel.text = product.localizedTitle;
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@元",product.price];
}
}
3.2.2 商品的購買
- (void)buy:(SKProduct *)product{
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
// 記錄購買者ID 后面取到傳給服務(wù)器
NSString *userID = @"user";
payment.applicationUsername =userID;
payment.quantity = 1;//購買一次
//將商品添加到購買隊列
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
//當(dāng)用戶點擊完購買,此時付款隊列中有了交易,會調(diào)用下面的方法。
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing: //交易正在被添加到付款隊列
NSLog(@"交易正在被添加到付款隊列");
break;
case SKPaymentTransactionStateDeferred: //最終狀態(tài)未確定
[self completeTransaction:transaction forStatus:IAPPurchaseFailed];
NSLog(@"最終狀態(tài)未確定");
break;
case SKPaymentTransactionStatePurchased: //購買成功
NSLog(@"購買成功");
[self completeTransaction:transaction forStatus:IAPPurchaseSucceeded];
break;
case SKPaymentTransactionStateRestored: //已經(jīng)購買過該商品
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];//消耗型不支持恢復(fù)
NSLog(@"已經(jīng)購買過該商品");
break;
case SKPaymentTransactionStateFailed: //交易失敗
NSLog(@"交易失敗");
[self completeTransaction:transaction forStatus:IAPPurchaseFailed];
break;
default:
break;
}
}
}
//檢查交易狀態(tài),做出相應(yīng)操作
- (void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(NSInteger)status{
self.status = status;
NSString *detail = nil;
if (transaction.error != nil) {
switch (transaction.error.code) {
case SKErrorUnknown:
NSLog(@"SKErrorUnknown");
detail = @"未知的錯誤,請稍后重試。";
break;
case SKErrorClientInvalid:
NSLog(@"SKErrorClientInvalid");
detail = @"當(dāng)前蘋果賬戶無法購買商品(如有疑問,可以詢問蘋果客服)";
break;
case SKErrorPaymentCancelled:
NSLog(@"SKErrorPaymentCancelled");
detail = @"訂單已取消";
break;
case SKErrorPaymentInvalid:
NSLog(@"SKErrorPaymentInvalid");
detail = @"訂單無效(如有疑問,可以詢問蘋果客服)";
break;
case SKErrorPaymentNotAllowed:
NSLog(@"SKErrorPaymentNotAllowed");
detail = @"當(dāng)前蘋果設(shè)備無法購買商品(如有疑問,可以詢問蘋果客服)";
break;
case SKErrorStoreProductNotAvailable:
NSLog(@"SKErrorStoreProductNotAvailable");
detail = @"當(dāng)前商品不可用";
break;
default:
NSLog(@"No Match Found for error");
detail = @"未知錯誤";
break;
}
NSLog(@"detail == %@",transaction.error.localizedDescription);
}
if (status == IAPPurchaseSucceeded) {
/***************此處有坑,需特別注意*****************/
//由于網(wǎng)絡(luò)問題等種種原因,即使用戶已經(jīng)付款成功,客戶端也可能一時半會收不到蘋果API的支付成功通知,也無法主動向蘋果API請求查詢支付狀態(tài),只能被動等待通知。
//因此有些情況下,客戶端會延遲收到支付成功的通知(可能是過了幾分鐘,也有可能是下次打開App的時候),針對這種情況,需要做好兩件事:
//1. 客戶端本地保存所有支付結(jié)果未確認(rèn)的交易信息,并設(shè)置一個監(jiān)聽進(jìn)程,在收到支付成功的信息后,繼續(xù)處理這筆交易的后續(xù)流程。極端情況下,用戶在交易結(jié)果未確認(rèn)的情況下刪除App,保存在App本地數(shù)據(jù)庫中的交易信息也會丟失,因此,更好的方案是把交易信息存到iOS系統(tǒng)的keychain里面
//2.當(dāng)本地存在支付結(jié)果未確認(rèn)的交易信息時,在交互上提示用戶可能需要等待支付結(jié)果,避免用戶重復(fù)付款
//獲得交易 憑證
NSURL *receipturl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receipturl];
NSLog(@"receiptData == %@",receiptData);
//獲取購買者標(biāo)識
NSLog(@"payment.applicationUsername == %@",transaction.payment.applicationUsername);
//本次交易的唯一標(biāo)識符
MyLog(@"transaction.transactionIdentifier == %@",transaction.transactionIdentifier);
//將 1、交易憑證 2、購買者標(biāo)識 3、購買的產(chǎn)品類型(ProductID)4、本次交易的唯一標(biāo)識符 保存到本地(keyChain)
// [LGJKeyChainTools setObject:receiptStr forService:@"user" account:transaction.transactionIdentifier ];
//向本地服務(wù)器發(fā)送請求 傳送 1、交易憑證 2、購買者標(biāo)識 3、購買的產(chǎn)品類型(ProductID)4、本次交易的唯一標(biāo)識符,本地服務(wù)器向蘋果服務(wù)器發(fā)送驗證驗證交易憑證請求。如果憑證有效,則發(fā)放產(chǎn)品,刪除保存到本地的相應(yīng)信息,如果無效則提示相應(yīng)的錯誤提示,也要刪除保存到本地的相應(yīng)信息。
}
//無論什么狀態(tài)都應(yīng)該將本次交易做結(jié)束處理,否則下次購買會出現(xiàn)問題。
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
//發(fā)送通知
[[NSNotificationCenter defaultCenter] postNotificationName:IAPPurchaseNotification object:self];
//在客戶端向服務(wù)端輪詢結(jié)果時,為了避免用戶在支付結(jié)果頁面等待過久,交互層面上可以先結(jié)束支付流程(經(jīng)過一定超時時間),同時提示用戶需要等待支付結(jié)果,避免用戶重復(fù)付款
}