前述:最近剛剛和后臺的同事完成了IOS的內(nèi)購項目功能開發(fā),用以替換之前的支付寶、微信支付功能。這里,梳理出大體的步驟,已經(jīng)其中踩過的坑。我只梳理了什么事IAP、為什么要用IAP、IAP功能的架構(gòu)設(shè)計、IAP的具體實(shí)現(xiàn)代碼以及IAP的一些問題
一》 In-App Purchase的相關(guān)知識
這里我就不羅列大量的理論了,只談?wù)勎易约旱恼J(rèn)識。首先,In-App Purchase功能是用來在IOS生態(tài)內(nèi),購買App相關(guān)產(chǎn)品的功能。每一筆交易,它都會從中提取30%的手續(xù)費(fèi),也就是說別人為你內(nèi)購項目支付1元,它要收取0.3元錢。其實(shí),將App Store理解為一個百貨商場,那么各家App就是一個個品牌的柜臺,而我們的In-App Purchase Products就是柜臺里的商品了。這也就解釋了,為什么每一筆交易App Store會收取30%的資金:商場提供給你場地、支付渠道,那最終他肯定會有“手續(xù)費(fèi)”要收。個人猜測,最近鬧的沸沸揚(yáng)揚(yáng)的微信“打賞”功能,估計也與此有關(guān)吧,那么龐大的流動資金,無限制的抽取30%,誰也受不了啊。
In-App Purchase功能的開發(fā),既費(fèi)神也要蒙受收入損失,那么所有涉及支付功能的都需要它么?不盡然。我只說,完全不能繞開它的情況:那就是你的產(chǎn)品是虛擬的,并購買該產(chǎn)品是在使用你的App的一定情境下的必然環(huán)節(jié),或者說購買的產(chǎn)品是App環(huán)境內(nèi)使用的,那么你就必須使用In-App Purchase功能。舉個例子:我的App里有一篇付費(fèi)文章,那么我就必須花錢才能在App內(nèi)看這篇文章,那么這個商品就是必須使用In-App Purchase功能來支付的。那么反過來說,比如“百度外賣”、“膜拜單車”等一系列產(chǎn)品,為什么可以使用非In-App Purchase功能來付費(fèi)、充值呢?因?yàn)橥赓u也好、自行車也好、金融理財類產(chǎn)品也好,他們或?qū)嶓w商品、或購買的商品,所使用的情景等是在App環(huán)境外的,所產(chǎn)生的資金不在平臺內(nèi),那么這時也就可以使用支付寶、微信、銀行卡等第三方API直接開發(fā)支付功能了。
更為官方性的內(nèi)容可以在這里查看:https://developer.apple.com/in-app-purchase/
二》 In-App Purchase開發(fā)的準(zhǔn)備工作
這里我只說明全過程,重點(diǎn)在架構(gòu)的設(shè)計和開發(fā)部分,政策性的過程可以參考以下文章:
http://www.lxweimin.com/p/86ac7d3b593a
簡要來說答題步驟如下:
第一步:創(chuàng)建一個APP ID,注意需要勾選In-App Purchase功能。
第二步:在iTunes Connect的“我的App”里,創(chuàng)建一個App,所使用的APP ID就是剛剛創(chuàng)建的APP ID。
第二步:完善開發(fā)者賬號的協(xié)議、稅務(wù)和銀行業(yè)務(wù)相關(guān)資料。這里網(wǎng)上有很多資料,不再贅述,唯一提醒:把所有資料都要填寫,包括聯(lián)系方式等等。只為什么,后邊會說。
第三步:在iTunes Connect的“我的App”里,創(chuàng)建幾項App的內(nèi)購項目,注意地區(qū)選擇:中國。
第四步:在剛剛創(chuàng)建的App中,內(nèi)購項目中添加剛剛創(chuàng)建的幾項購買項目。
--------------至此,開發(fā)前的準(zhǔn)備工作就差不多了------------
三》 In-App Purchase功能的架構(gòu)設(shè)計
首先看看Xcode給出的一個開發(fā)過程的流程圖:
下來,看看Xcode里給出的功能框架圖:
大體的過程就是:從我們的Service獲取到商品ID ——> 用商品ID向蘋果市場請求產(chǎn)品相關(guān)信息 ——> 用獲取到的商品信息購買商品——>購買成功后獲取購買憑證——>講憑證發(fā)回Service驗(yàn)證——>購買成功
總體來說,可歸結(jié)為下圖的詳細(xì)流程:
*** 如上圖所示,已經(jīng)是一個相當(dāng)完善的IAP支付流程圖了。只是在這里我希望做一點(diǎn)補(bǔ)充:在第9步至14步返回結(jié)果的中間,應(yīng)該先講App Store返回給客戶端的支付憑證做本地保存,然后待14步完成服務(wù)端的校驗(yàn)后,再將本地保存的改憑證刪除。這樣做的好處是,10~13步中間任何校驗(yàn)的環(huán)節(jié)出現(xiàn)問題,可以重新發(fā)送未校驗(yàn)的憑證,這樣可更大化的保證用戶資金憑證的安全,避免出現(xiàn)誤差。至于保存的方式,可以使用單例、本地化持久等等。我的方式是本地單例存儲了一個設(shè)計的隊列,驗(yàn)證成功一條,隊列出一條。至于二次校驗(yàn)觸發(fā)的環(huán)節(jié),因人而異,自行設(shè)計。如下圖:
另注:就像集成AVPlayer一樣,我還是傾向于功能模塊化,封裝起來,做成單獨(dú)的功能類。這樣做的益處非常大,利于日后的維護(hù)、擴(kuò)展等等。加上相應(yīng)的注解,日后也好維護(hù),自己看著整齊的代碼也很舒服啊。我的主要功能如下:
@protocol IAPManagerDelegate <NSObject>
-(void)IAPFailedWithWrongInfor:(NSString *)informationStr;
-(void)IAPPaySuccessFunctionWithBase64:(NSString *)base64Str;
@end
@interface IAPManager : NSObject
@property(nonatomic ,weak) id<IAPManagerDelegate> IAPDelegate;
+(instancetype)sharedManager;
/**
* @brief 檢查本地是否具有未成功校驗(yàn)的IAP訂單
*
* @parameter 無
*
* @returning 無
*/
+(void)checkTheIAPStatusFunction;
/**
* @brief 添加IAP觀察者
*
* @parameter 無
*
* @returning 無
*/
-(void)addTheIAPObserver;
/**
* @brief 刪除IAP觀察者
*
* @parameter 無
*
* @returning 無
*/
-(void)removeTheIAPOberver;
/**
* @brief 從appleStore獲取商品信息
*
* @parameter productIdentifier 商品編號(服務(wù)器獲取)
*
* @returning 無
*/
- (void)getProductInfo:(NSString *)productIdentifier;
四》代碼實(shí)現(xiàn)
首先,我們需要在類里引入<StoreKit/StoreKit.h>,并且執(zhí)行該類的代理
#import <StoreKit/StoreKit.h>
@interface IAPManager()<SKProductsRequestDelegate, SKPaymentTransactionObserver>
然后集成的步驟就像上邊我們梳理的那樣,首先我們要根據(jù)商品ID向App Store發(fā)送請求,用來驗(yàn)證商品是否存在已經(jīng)它的詳細(xì)信息
/*
從Apple查詢用戶點(diǎn)擊購買的產(chǎn)品的信息
獲取到信息以后,根據(jù)獲取的商品詳細(xì)信息
*/
- (void)getProductInfo:(NSString *)productIdentifier
{
if (![SKPaymentQueue canMakePayments])
{
if (_IAPDelegate && [_IAPDelegate respondsToSelector:@selector(IAPFailedWithWrongInfor:)])
{
[_IAPDelegate IAPFailedWithWrongInfor:@"不允許程序內(nèi)付費(fèi)購買"];
}
return;
}
if (productIdentifier.length > 0)
{
NSArray * product = [[NSArray alloc] initWithObjects:productIdentifier, nil];
NSSet *set = [NSSet setWithArray:product];
SKProductsRequest * request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
request.delegate = self;
[request start];
}
else
{
if (_IAPDelegate && [_IAPDelegate respondsToSelector:@selector(IAPFailedWithWrongInfor:)])
{
[_IAPDelegate IAPFailedWithWrongInfor:@"商品ID為空"];
}
}
}
返回結(jié)果會呈現(xiàn)在StoreKit代理的函數(shù)里
/*
查詢成功后的回調(diào)
經(jīng)由getProductInfo函數(shù)發(fā)起的產(chǎn)品信息查詢,成功后返回執(zhí)行的回調(diào)。再更具回調(diào)內(nèi)容發(fā)起購買請求
*/
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *myProduct = response.products;
if (myProduct.count == 0)
{
if (_IAPDelegate && [_IAPDelegate respondsToSelector:@selector(IAPFailedWithWrongInfor:)])
{
[_IAPDelegate IAPFailedWithWrongInfor:@"無法獲取商品信息"];
}
return;
}
//發(fā)起購買操作,下邊的代碼
}
獲取到了商品的詳細(xì)信息,就可以根據(jù)該詳細(xì)信息發(fā)起對商品的購買請求了
SKPayment * payment = [SKPayment paymentWithProduct:myProduct[0]];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
同樣的,查詢不存在或網(wǎng)絡(luò)通信失敗等一系列查詢失敗的函數(shù)執(zhí)行如下代理
/*
查詢失敗后的回調(diào)
*/
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
if (_IAPDelegate && [_IAPDelegate respondsToSelector:@selector(IAPFailedWithWrongInfor:)])
{
[_IAPDelegate IAPFailedWithWrongInfor:@"購買失敗"];
}
NSLog(@"打印錯誤信息:%@",[error localizedDescription]);
}
發(fā)起購買請求,就會開始客戶端與App Store之間的往來通信,此時在測試階段需要使用沙箱測試賬號來測試!
購買的結(jié)果,Ios會統(tǒng)一在下邊的函數(shù)中反饋,狀態(tài)通過枚舉獲得:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
//交易完成
case SKPaymentTransactionStatePurchased:
//發(fā)送購買憑證到服務(wù)器驗(yàn)證是否有效
break;
//交易失敗
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
//已經(jīng)購買過該商品
case SKPaymentTransactionStateRestored:
break;
//商品添加進(jìn)列表
case SKPaymentTransactionStatePurchasing:
break;
default:
break;
}
}
}
接下來就是最終交易憑證的驗(yàn)證了,我們的步驟是:獲取憑證——>保存——>校驗(yàn)——>刪除
//交易成功,與服務(wù)器比對傳輸貨單號
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
//目前蘋果公司提倡的獲取購買憑證的方法
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
//base64位的產(chǎn)品驗(yàn)證碼單,base64是服務(wù)端和蘋果進(jìn)行校驗(yàn)所必須的,蘋果的文檔要求憑證經(jīng)過Base64加密
NSString * transactionReceiptString = [receiptData base64EncodedStringWithOptions:0];
//將加密后的transactionReceiptString發(fā)送給后臺服務(wù)端進(jìn)行校驗(yàn),在此之前,記得先保存購買憑證
//完整結(jié)束此次在App Store的交易,沒有這句代碼的調(diào)用,下次購買會提示已經(jīng)購買該商品
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
接下來的服務(wù)端與客戶端的校驗(yàn),就是我們本地的事情了。蘋果的功能集成大致如此了。
五》所遇見的問題以及解決辦法
1.沙箱測試賬號無法登陸App Store的問題
解決方案:
a.手機(jī)操作系統(tǒng)不可以是越獄版本的
b.手機(jī)退出原有賬號以后,在測試的過程中直至點(diǎn)擊IAP內(nèi)購按鈕以后,等它自己彈出提示框登陸
c.刪除測試App,重啟手機(jī)后重新安裝,發(fā)起購買請求,填寫沙箱賬號登陸
d.沙箱賬號在創(chuàng)建時的購買區(qū)域選中國
e.銀行稅務(wù)賬戶信息未填寫完全
f.沙箱賬號是在稅務(wù)信息填寫完整前創(chuàng)建的,無法登陸鏈接。在完善稅務(wù)信息后重新創(chuàng)建一個沙箱賬號登陸(這一條,很詭異,但是我創(chuàng)建的10個賬號,確實(shí)是信息完善前的兩個沒用,其他都可以)。
g.沙箱賬號和真實(shí)賬號沖突
2.調(diào)用- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response時查不到商品信息,或者說產(chǎn)品標(biāo)識符在invalidProductIdentifiers數(shù)組中被退返
解決方案:
a.App的App ID和內(nèi)購項目的App的App ID不對應(yīng),請檢查
b.App ID沒有開啟IAP功能。登陸IOS開發(fā)者后臺,找到改App ID,重新edit,選擇上IAP功能后保存
c.在iTunes Connect中,蘋果拒絕了你最新向iTunes Connect提交的二進(jìn)制碼。
d.你沒有清除iTunes Connect中在售的IAP產(chǎn)品。
e.可能修改了商品,但是這些修改沒有在所有App Store的服務(wù)器中生效。有時候會有延時,等等再說
f.你的商品由蘋果托管上,內(nèi)容尚未上傳至iTunes Connect上。
g.商品的標(biāo)識符不對。檢查傳給蘋果的標(biāo)識符和創(chuàng)建的是否完全一致。
h.沒有向即將提交的新版本的內(nèi)購項目中添加已經(jīng)創(chuàng)建的內(nèi)購項目。
i.沒有填完稅務(wù)信息。這一條重點(diǎn)說明下,稅務(wù)信息中,所有的信息都要填寫,包括聯(lián)系方式等等。只要你的信息有一點(diǎn)不完善,IAP的功能就無法測試,你也獲取不到商品的信息。
以上就是這次開發(fā)的心得了,還是歡迎一起討論,共同進(jìn)步漲姿勢哈~