衣帶漸寬終不悔褐鸥,為伊消得人憔悴,經(jīng)過近幾天的刻苦研讀和高效整理,終有成果锹安!
目錄:
一.應(yīng)用內(nèi)支付Code展示學(xué)習(xí)
二.支付深度理解層次
三.支付遇到的一些坑
四.手把手集成我的Demo
一.應(yīng)用內(nèi)支付Code展示學(xué)習(xí)
接著上篇文章最后對應(yīng)用內(nèi)支付流程梳理步驟進(jìn)行深化;本篇同樣分為支付前倚舀,支付中叹哭,支付后流程。
1.支付前Code展示
1.1注冊通知以下3個通知:
-(void)addAllNoti {
// 獲取商品信息通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseToOrderDatas:)
name:IAPZWGoodsRequestNotification
object:[IAPZWGoodsManager sharedInstance]];
// 是否下單成功的通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseToAllGoodsIsSuccess:)
name:IAPZWBuyResultNotification
object:[IAPZWGoodsNoti sharedInstance]];
// 取消支付的通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseToAllGoodsCancle:)
name:IAPZWBuyResultNotificationCancle
object:[IAPZWGoodsNoti sharedInstance]];
}
當(dāng)然也要在dealloc方法中注銷通知
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:IAPZWGoodsRequestNotification
object:[IAPZWGoodsNoti sharedInstance]];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:IAPZWBuyResultNotification
object:[IAPZWGoodsNoti sharedInstance]];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:IAPZWBuyResultNotificationCancle
object:[IAPZWGoodsNoti sharedInstance]];
}
1.2獲取商品信息的方法:
// 獲取所有的商品信息
-(void)getAllAppleProductInfoDatas
{
// Query the App Store for product information if the user is is allowed to make purchases.
// Display an alert, otherwise.
if([SKPaymentQueue canMakePayments])
{
[[DisplayHelper shareDisplayHelper]showLoading:self.view noteText:@"加載商品中..."];
// 正確方式為從后臺獲取,這里本地寫死商品id
NSArray *productIds = @[@"1008612345",@"1008611111"];
[[IAPZWGoodsManager sharedInstance] requestGetAllGoodsProductIds:productIds];
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// [[DisplayHelper shareDisplayHelper]hideLoading:self.view];
//
// });
}
else
{
}
}
1.3在剛才的通知中回調(diào)對應(yīng)的數(shù)據(jù)如下方法:
-(void)responseToOrderDatas:(NSNotification *)notification
{
IAPZWGoodsManager *goodsManager = (IAPZWGoodsManager*)notification.object;
IAPGoodsRequestStatus status = goodsManager.status;
[[DisplayHelper shareDisplayHelper]hideLoading:self.view];
if (status == IAPGoodsRequestResponse)
{
self.dataArr = goodsManager.availableProducts;
[self.tableView reloadData];
}
}
2.支付中Code展示(類似微信支付和支付寶支付的下單流程)
2.1用戶點(diǎn)擊Cell發(fā)起下單請求:
// 下單商品
-(void)buyAllGood:(SKProduct *)product
{
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
注意如果發(fā)現(xiàn)上次還有未驗(yàn)證的訂單可通過下面方法重新驗(yàn)證:
// 如果存在的話即為上次未來的及驗(yàn)證的訂單重新驗(yàn)證(這里沒有做相關(guān)邏輯)
-(void)restoreAllGoods
{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
2.2在調(diào)起蘋果支付頁面后回調(diào)到以下這個方法
// 支付過程中的回調(diào)
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for(SKPaymentTransaction * transaction in transactions)
{
switch (transaction.transactionState )
{
// 1.第一次調(diào)起蘋果支付框時走這里
case SKPaymentTransactionStatePurchasing:
break;
case SKPaymentTransactionStateDeferred:
break;
// 2.這里有時會走2次痕貌,其中比較關(guān)心的一次是 IAPZWBuyGoodsSucceeded 這一次
case SKPaymentTransactionStatePurchased:
{
self.purchasedID = transaction.payment.productIdentifier;
[self.goodProductIds addObject:transaction];
if(transaction.downloads && transaction.downloads.count > 0)
{
[self completeTransaction:transaction forStatus:IAPZWDownOrderStarted];
}
else
{
[self completeTransaction:transaction forStatus:IAPZWBuyGoodsSucceeded];
}
}
break;
case SKPaymentTransactionStateRestored:
{
self.purchasedID = transaction.payment.productIdentifier;
[self.goodRestoredIds addObject:transaction];
NSLog(@"SKPaymentTransactionStateRestored %@",transaction.payment.productIdentifier);
if(transaction.downloads && transaction.downloads.count > 0)
{
[self completeTransaction:transaction forStatus:IAPZWDownOrderStarted];
}
else
{
[self completeTransaction:transaction forStatus:IAPZWRestoredSucceeded];
}
}
break;
// 3.測試中會發(fā)現(xiàn)有時走這個方法
case SKPaymentTransactionStateFailed:
{
self.errorMsg = [NSString stringWithFormat:@"SKPaymentTransactionStateFailed %@ ",transaction.payment.productIdentifier];
[self completeTransaction:transaction forStatus:IAPZWBuyGoodsFailed];
}
break;
default:
break;
}
}
}
注意:在調(diào)用第一次彈出支付框风罩,如下所示
輸入賬號和密碼后,且密碼正確的情況下舵稠,點(diǎn)擊好后會調(diào)用第二次該方法超升,之后可能調(diào)用第三次該方法
2.3在成功調(diào)用上面方法之后,會通過以下方式發(fā)通知
// 完成支付
-(void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(NSInteger)status
{
self.status = status;
if (transaction.error.code != SKErrorPaymentCancelled)
{
[[NSNotificationCenter defaultCenter] postNotificationName:IAPZWBuyResultNotification object:self];
}
if (status == IAPZWDownOrderStarted) // 開始下單
{
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
}
else // 已經(jīng)下單購買完成
{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
之后回調(diào)到下面的方法中
// 完成支付
-(void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(NSInteger)status
{
self.status = status;
if (transaction.error.code != SKErrorPaymentCancelled)
{
[[NSNotificationCenter defaultCenter] postNotificationName:IAPZWBuyResultNotification object:self];
}
if (status == IAPZWDownOrderStarted) // 開始下單
{
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
}
else // 已經(jīng)下單購買完成
{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
3.支付后Code展示(重點(diǎn)在雙重認(rèn)證)
根據(jù)是否支付成功哺徊,若支付成功的話室琢,開始雙重認(rèn)證
3.1先本地認(rèn)證是否可以獲得到receipt
- (void)completeAllDownOrderIsSuccess:(NSString *)productIdentifier {
NSData *receiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
NSString *receipt = [receiptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
[[DisplayHelper shareDisplayHelper]hideLoading:self.view];
if ([receipt length] > 0 && [productIdentifier length] > 0) {
/**
可以將receipt發(fā)給服務(wù)器進(jìn)行購買驗(yàn)證
*/
[self sendRequestReceiptToAppStore:receipt];
}
// [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
3.2客戶端去蘋果服務(wù)器認(rèn)證
-(void)sendRequestReceiptToAppStore:(NSString *)receipt
{
NSError *error;
NSDictionary *requestContents = @{@"receipt-data": receipt};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
if (!requestData) {
}else{
}
// NSURL *storeURL;
//#ifdef DEBUG
// storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
//#else
// storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
//#endif
NSURL *storeURL;
if (kIsProduction) {
storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
}else {
storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
}
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
WS(ws);
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
/* 處理error */
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse) { // 出錯的情況
/* 處理error */
dispatch_async(dispatch_get_main_queue(), ^{
[DisplayHelper displayWarningAlert:@"支付失敗!"];
});
}else{
/* 處理驗(yàn)證結(jié)果 */
if ([[jsonResponse allKeys]containsObject:@"status"]) {
NSString *statusStr =jsonResponse[@"status"];
if ([statusStr intValue] ==0) { // 成功后發(fā)服務(wù)器進(jìn)行第二層驗(yàn)證
// 重發(fā)我們自己服務(wù)器的驗(yàn)證
[ws requestPayIsSuccess:receipt];
}else {
dispatch_async(dispatch_get_main_queue(), ^{
if ([statusStr intValue] ==21002) { // 請求過于頻繁,有刷單嫌疑落追,需重發(fā)服務(wù)器
// 重發(fā)我們自己服務(wù)器的驗(yàn)證
[ws requestPayIsSuccess:receipt];
}else { // 其他失敗的情況
[DisplayHelper displayWarningAlert:@"支付失敗!"];
}
});
}
}
}
}
}];
}
3.3客戶端讓我們的服務(wù)端去蘋果服務(wù)器認(rèn)證
3.4服務(wù)端修改對應(yīng)的商品字段盈滴,有必要的話,客戶端也要進(jìn)行修改
二.支付深度理解層次
1.區(qū)分應(yīng)用內(nèi)支付中內(nèi)和外的關(guān)系
一般的微信支付寶支付時轿钠,是調(diào)用支付接口時直接跳轉(zhuǎn)到對應(yīng)的App巢钓,可以很明顯的感覺到支付前后在App的內(nèi)外關(guān)系,而對于蘋果應(yīng)用內(nèi)支付呢疗垛,則必須區(qū)分開邏輯的內(nèi)外症汹,才算是真正的明白該支付的特點(diǎn)。
以本次的Demo為例:
當(dāng)支付的輸入輸出框展示在您的面前時继谚,就證明在應(yīng)用外開始支付了烈菌,而當(dāng)該框消失或者支付成功后如Demo中展示“支付成功!”時,就證明該進(jìn)入應(yīng)用內(nèi)完成支付了花履!
所以處理好 支付:應(yīng)用內(nèi)到應(yīng)用外芽世;支付完成:應(yīng)用外回調(diào)到應(yīng)用內(nèi)
2.商品列表請求和處理流程注意
2.1請求商品列表最好為接口通過接口返回而不是本地寫死的情況
2.2支付過程中
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
中的狀態(tài)為SKPaymentTransactionStatePurchased 時回調(diào)2次的區(qū)分為第一次為開始,第二次為成功請注意區(qū)分诡壁,本人當(dāng)時就是爬的這里的坑造成的
2.3支付成功后不要忘了雙重認(rèn)證服務(wù)端
三.支付遇到的一些坑
1.雙重校驗(yàn)的問題
什么是雙重驗(yàn)證济瓢?
第一層認(rèn)證為客戶端和蘋果那邊認(rèn)證是否支付成功,第二層認(rèn)證為服務(wù)端和蘋果那邊認(rèn)證是否支付成功
注意:客戶端和蘋果那邊驗(yàn)證完成返回0為正常支付成功妹卿,但是返回status為21002時旺矾,官方解釋是請求太頻繁蔑鹦,有刷單嫌疑。故而這種情況也要把對應(yīng)的數(shù)據(jù)發(fā)給服務(wù)端驗(yàn)證箕宙。
2.關(guān)于沙盒賬戶的使用
測試前需要先把AppStore中真實(shí)AppID給退出嚎朽,之后再進(jìn)行測試,避免調(diào)用失敿砼痢哟忍;實(shí)際測試中發(fā)現(xiàn)一個沙盒賬戶只能綁定一個手機(jī),請各位自己測試告知陷寝。
3.在類線上支付測試時充值問題
對真實(shí)AppID進(jìn)行充值支付時需要驗(yàn)證以前的密碼以及密保問題等
4.測試賬號添加問題
測試賬號添加時锅很,密碼不要設(shè)置過于簡單,不然就會出現(xiàn)添加測試賬號失敗的問題凤跑;同樣沙盒模式下登錄前請退出以前的登錄賬戶才能登錄沙盒賬戶爆安。另外沙盒模式驗(yàn)證完畢后請記得退出原來的沙盒賬戶,之后登錄自己正式的賬戶仔引,不然后續(xù)下載軟件就會下載不了扔仓,總之,驗(yàn)證沙河模式后及時重新替換成自己的賬戶肤寝,避免來回使用麻煩当辐。
5. SKPaymentTransactionStatePurchased 方法回調(diào)
下單完成后回調(diào)到此地時,一般會走二次鲤看,第一次為開始下訂單缘揪;第二次為下訂單成功的情況,詳見Demo中區(qū)分情況义桂。
四.手把手集成我的Demo
1.導(dǎo)入的類庫及介紹
#import "IAPZWGoodsNoti.h"
#import "IAPZWGoodsManager.h"
#import <StoreKit/StoreKit.h>
2.Demo中類的介紹
IAPZWGoodsNoti 主要為獲取商品列表的類
IAPZWGoodsManager 主要為支付過程中監(jiān)聽的幫助類
3.其他注意細(xì)節(jié)
按照以上的步驟執(zhí)行后發(fā)現(xiàn)找筝,咦,怎么還是無法支付成功呢慷吊?嘿嘿干貨肯定要在最后展示哈袖裕!
3.1AppDeleage注冊監(jiān)聽和取消監(jiān)聽問題
#pragma mark - UIApplication Methods
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Remove the observer
[[SKPaymentQueue defaultQueue] removeTransactionObserver: [IAPZWGoodsNoti sharedInstance]];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[UINavigationBar appearance]setBarTintColor:RGBCOLOR(129, 188, 53)];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
self.window =[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
[self initRootViewController];
[self.window makeKeyAndVisible];
#warning 記得調(diào)用初始化分享或支付SDK的代碼
// // 初始化ShareSDK
// [self initSharedKey];
//
// // 初始化微信支付
// [self initWpay];
[[SKPaymentQueue defaultQueue] addTransactionObserver:[IAPZWGoodsNoti sharedInstance]];
return YES;
}
3.2如BoundId問題
更換 BoundId為您的可上線項目的BoundId
3.3是否是生產(chǎn)環(huán)境問題
全局搜索 kIsProduction 修改為0表示沙盒模式,為1為線上環(huán)境溉瓶,注意急鳄!
3.4請求商品列表問題
從后臺獲取對應(yīng)的商品列表之后再為下單處理,而不是本地寫死堰酿,避免上線后無法更改疾宏。
3.5雙重認(rèn)證再次強(qiáng)調(diào)問題
保險起見,一定要做雙重認(rèn)證触创,另外返回status為21002時坎藐,也要發(fā)給后臺進(jìn)行是否支付成功的驗(yàn)證。
參考資料:
1.iOS之支付:http://www.reibang.com/p/a9e17f50df9e
2.iOS開發(fā)內(nèi)購全套圖文教程:http://www.reibang.com/p/86ac7d3b593a
3.iOS 內(nèi)購支付兩種模式:http://www.reibang.com/p/15086493768a
4.談?wù)勌O果應(yīng)用內(nèi)支付(IAP)的坑:http://www.reibang.com/p/c3c0ed5af309
5.【iOS】蘋果IAP(內(nèi)購)中沙盒賬號使用注意事項:http://www.reibang.com/p/1ef61a785508
最后附上:
2018最新研究應(yīng)用內(nèi)支付1:準(zhǔn)備工作:http://www.reibang.com/p/f090740991dd
最終的Demo地址:https://github.com/zxwIsCode/IAPZWDemo
最后期待下一篇,應(yīng)用內(nèi)支付上線注意的問題等