iOS 內(nèi)購(IAP) 簡單總結(jié)

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ù)付款
            
}
Demo下載地址 https://github.com/234313037/MyINAppPurchasesDemo
最后 感謝 http://bbs.netease.im/read-tid-730
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容