iOS開發支付篇——內購(IAP)詳解

內購所須要的資料整理總結,史上最完整的,哈哈哈哈哈哈

思惟導圖前端

重點總結:ios

1.獲取內購列表(從App內讀取或從本身服務器讀取)
2.App Store請求可用的內購列表
3.向用戶展現內購列表
4.用戶選擇了內購列表,再發個購買請求,收到購買完成的回調(購買完成後會把錢打給申請內購的銀行卡內)
5.購買流程結束後, 向服務器發起驗證憑證以及支付結果的請求
6.本身的服務器將支付結果信息返回給前端併發放虛擬產品
7.服務端的工做比較簡單,分4步:
  7.1.接收ios端發過來的購買憑證。
  7.2.判斷憑證是否已經存在或驗證過,而後存儲該憑證。
  7.3.將該憑證發送到蘋果的服務器驗證,並將驗證結果返回給客戶端。
   7.4.若是須要,修改用戶相應的會員權限。
   7.5.考慮到網絡異常狀況,服務器的驗證應該是一個可恢復的隊列,若是網絡失敗了,應該進行重試。
簡單來講就是將該購買憑證用Base64編碼,而後POST給蘋果的驗證服務器,蘋果將驗證結果以JSON形式返回。

1、使用注意事項及遇到的坑json

  1.使用注意服務器

1. 代碼中的_currentProId所填寫的是你的購買項目的的ID,這個和第二步建立的內購的productID要一致,產品id與_currentProId一致。
2. 在監聽購買結果後,必定要調用[[SKPaymentQueue defaultQueue] finishTransaction:tran];來容許你從支付隊列中移除交易。 
3. 真機測試的時候,必定要退出原來的帳號(app store 登陸的帳號退出),才能用沙盒測試帳號。
4. 請務必使用真機來測試,一切以真機爲準。 
5. 項目的Bundle identifier須要與您申請AppID時填寫的bundleID一致,否則會沒法請求到商品信息。 
6. 沙盒環境測試appStore內購流程的時候,請使用沒越獄的設備。 
7. 二次驗證,請注意區分宏, 測試用沙盒驗證,App Store審覈的時候也使用的是沙盒購買,因此驗證購買憑證的時候須要判斷返回Status Code決定是否去沙盒進行二次驗證,爲了線上用戶的使用,驗證的順序確定是先驗證正式環境,此時若返回值爲21007,就須要去沙盒二次驗證,由於此購買的是在沙盒進行的。
8.貨幣類型(Bank Account Currency) :填CNY(若是你的app在中國使用的話)。

  2.獲取不到商品信息微信

1.肯定配置環節正確。
2.肯定是真機測試且手機沒有越獄。
3.肯定內購商品添加到了須要內購功能的App中。
4.肯定當前運行的App的Bundle ID和後臺配置的App的Bundle ID是一致的。
5.能夠嘗試先刪除舊App,再從新編譯生成新的,避免新App未覆蓋錯誤。
6.這裏要提一點,沙盒的測試帳號和你請求商品信息沒有關係。請求商品信息的流程是,你在後臺配置好了內購商品,而且將其添加到了須要集成內購功能的App中,而後你請求商品。請求到商品後的流程是這樣的,蘋果系統會自動彈出登陸框讓你登陸帳號。而後根據提示操做進行購買,這裏的帳號就是你配置的沙盒測試帳號。

2、爲何要使用內購?(why)和內購是什麼?(what)網絡

  1.若是你購買的商品,是在本app中使用和消耗的,就必定要用內購,不然會被拒絕上線,例如:遊戲幣,在線書籍
app中使用的道具等。本例中,就是直播中你用來打賞用的金幣,那東西可就屬於消耗型的。
  2.若是是直接購買商城之類的快遞包郵的那些東東,那就直接調用支付寶,微信啦,之類的三方支付就行了,淘寶,京東都玩過哈!
比較坑的一點就是,內購的話,還要和蘋果3/7分紅,那就能夠說,充值相同的錢,相對來講,iOS是比安卓虧的!

3、怎樣使用內購?(how)併發

  1.使用內購須要哪些資料
   1張visa銀行卡,appid,1張銀行卡與蘋果三七分打錢用     (1)協議、稅務和銀行業務        聯繫人信息:(appid帳號人)姓名,郵箱,電話號碼,地址(城市、具體街道分行寫)        visa銀行卡信息:開戶行,開戶行所在地址,開戶行的郵政編碼,開戶行持有人卡號,開戶行持有人姓名        稅務信息:1.會問你是否是美國居民選擇NO. 2. 有沒有在美國從事商業性活動,選擇NO. 以後填寫我的或組織名稱,所在國家,受益方式(獨立開發者選擇我的),居住地址,郵寄地址,聲明人,頭銜 (2)內購產品id的配置 (開發人員配置) 若是是金幣或其它消耗品的產品的話用消耗性型項目,參考名稱(內購項目,好比金幣100),產品id,訂價信息,使用內購的快照,顯示名稱,描述。     (3)用戶職能       測試員:添加水箱測試員及沙箱帳號,水箱測試帳號不能是正常使用的appid帳號,直接使用一個沒有註冊過的郵箱帳號便可。 姓名,測試帳號密碼,appstore地區(必須填對)。

4、操做流程圖解與代碼app

  1.建立app後填寫用戶信息ide

功能簡介 :
    1.個人App主要用於管理本身的App應用,例如編輯資料,上架,下架等。
    2.銷售和趨勢主要是來查看App在各個平臺的下載量,收入等方面數據,裏面有曲線圖等圖文結合的方式給咱們參考。
    3.付款和財務報告顯示的是你的收入以及付款等相關信息。
    4.iAd主要是跟廣告有關,開發者能夠登陸到Workbench,經過iAd對應用的廣告進行控制。
    5.用戶和職能用於生成相應帳號,例如蘋果沙盒測試帳號。
    6.協議,稅務和銀行業務則是你銀行相關帳戶的信息設置。
流程
 1.註冊app,填寫協議、稅務和銀行業務
   註冊app,須要設置Bundle identifier,此個步驟這裏就不在寫了
  填寫協議、稅務和銀行業務

  

選擇申請合同類型
頁面內容:
Request Contracts(申請合同)
Contracts In Effect(已生效合同)。
合同類型:
iOS Free Application(免費應用合同)
iOS Paid Application(付費應用合同)
iAd App NetNetwork(廣告合同)

  1.申請iOS Paid Application合同測試

 2. 設置協議稅務、銀行卡信息

當咱們點擊申請iOS Paid Application合同後,該合同的狀態會變成以下的樣子,咱們能夠看到其中Status爲Contact, Bank, Pending Tax,
意思是聯繫方式、銀行和稅務信息沒有填寫。

 

 2.1設置聯繫人信息

若是你沒有添加過聯繫人,你須要經過Add New Contact按鈕來添加一個新的聯繫人。而後指定聯繫人的職務,職務以下:
Senior Management:高管
Financial:財務
Technical:技術支持
Legal:法務
Marketing:市場推廣
若是你是獨立開發者,能夠所有填你本身一我的。

  新增聯繫人

  經過新增或以前增長的聯繫人設置高管等信息

  

待完成後點擊Done,返回後狀態會變成Edit狀態

 2.2設置銀行卡信息(能夠經過銀行名稱和地址直接上網查詢CNAPS Code號,不要問我上那查)

 

  

確認銀行卡信息

 

 2.3設置稅務信息(1.是美國稅務,只須要這個就行,後面的澳大利亞和日本的和咱們沒的關係)

選擇U.S Tax Forms,選擇後會問你兩個問題,第一個問題以下:詢問你是不是美國居民,有沒有美國夥伴關係或者美國公司,若是沒有直接選擇No。

接下來第二個問題以下:詢問你有沒有在美國的商業性活動,沒有也直接選No。

而後填寫稅務信息

而後填寫你的稅務信息,包括如下幾點:

Individual or Organization Name:我的或者組織名稱
Country of incorporation: 所在國家
Type of Beneficial Owner:受益方式,獨立開發者選我的
Permanent Residence:居住地址
Mailing address:郵寄地址
Name of Person Making this Declaration:聲明人
Title:頭銜
當你填寫完全部資料後,合同狀態就會變成Processing,筆者凌晨1點左右提交,下午就經過了。

  具體填寫見下圖(如下是確認稅務信息圖)

  填寫完成後效果

 3.配置內購產品ID

完成以上操做,而且蘋果審覈完畢以後,就能夠配置內購產品了。
登陸 iTunesConnect -->個人App 模塊找到須要內購的App,最後找到頁面以下:

  

填寫沙箱測試員和添加內購產品注意事項
一、郵箱必須是沒有註冊或者說關聯過appstore的郵箱。
二、密碼必須有一個是大寫字母有一個是小寫字母(蘋果規定的,理解)。
三、內購屏幕截圖規格必須是312*290,且最低分辨率是72ppi。
四、內購的價格是蘋果規定的不能自定義(坑啊)。

4.增長內購測試帳號

     4.1 內購測試以前準備

一、什麼是內購測試帳號(what)及爲何使用內購測試帳號(why)?
    iOS應用裏面用到了蘋果應用內付費(IAP)功能,在項目上線前必定要進行功能測試。測試確定是須要的,況且這個跟money有關。。。開發完成了以後,如何進行測試呢?難道我測試個內購功能要本身掏錢?就算是也是公司掏錢,可是蘋果要吃掉3成的啊,想一想若是是99刀的商品,點下購買的時候內心都有點發慌。。。
蘋果固然沒這麼坑了,測試內購,蘋果提供了沙盒帳號(也叫沙箱帳號)的方式。這個沙箱帳號實際上是虛擬的AppleID,在開發者帳號後臺的iTune Connect上配置了以後就能使用沙盒帳號測試內購,有了沙盒帳號,就能體驗一把土豪的感受了,遊戲鑽石什麼的隨便充,反正不用個人錢。
    注意:你能夠把沙盒帳號看作是一個虛擬的AppleID,這個AppleID只有進行內購測試的功能。重要,重要,重要,這個虛擬的帳號只能在本身的測試號中使用,若是在其它地方如appstore使用的話會提示帳號無效之類的話。    

二、如何使用內購測試帳號(how)?
    2.1做用內購帳號的前提
1)內購的商品ID,價格等相關信息已經錄入到開發者後臺了(否則那你買什麼)
2)開發者後臺已經建立好沙盒測試帳號了(下面咱們會將如何建立)
3)你要有一部真機(iPhone或iPad都行,別用模擬器就好。並且不能是越獄機)
4)bundleID別搞錯了,開發者帳號、證書、bundleID要一致
5)若是你是第一次在這個開發者帳號上集成內購功能,
請先將iTune Connect上的稅務協議都填寫好,不然內購時會發現商品ID無效。
 重要,若是不添加稅務協議會報錯,找不到商品。

  選擇用戶和職能就是在協議、稅務和銀行業務左側

   4.2內購測試開始

1.在iPhone上安裝測試包(必須是打包簽名證書或者develop簽名證書打的包,不能是從App Store上下載的)
2.退出iPhone的App Store帳號(由於咱們須要使用沙盒帳號登陸)。
操做方法一:打開App Store應用首頁滑到最下方--選中AppleID--註銷
操做方法二:設置--iTunes Store與App Store--選中AppleID--註銷
3.不能用沙盒測試賬號來登陸appstore官網或去其它已上線平臺去支付詳見圖4.21
4.運行下面代碼的demo,傳入你建立的產品id(就是在app iTunes Connect ->個人app ->功能 ->app內購買項目添加的商品),點擊充值跳轉開始購買詳見圖4.22
5.再次購買時須要輸入測試沙盒帳號密碼(在用戶和職能->沙箱技術測試員建立的測試帳號)詳見圖4.23
6.購買成功反饋詳見圖4.24

  4.21 圖

  4.22 圖

  4.23 圖

  4.24 圖

 

5.代碼及業務邏輯

   業務邏輯

  1. 獲取內購列表(從App內讀取或從本身服務器讀取)
  2. App Store請求可用的內購列表
  3. 向用戶展現內購列表
  4. 用戶選擇了內購列表,再發個購買請求,收到購買完成的回調(購買完成後會把錢打給申請內購的銀行卡內)
  5. 購買流程結束後, 向服務器發起驗證憑證以及支付結果的請求
  6. 本身的服務器將支付結果信息返回給前端併發放虛擬產品
  7. 服務端的工做比較簡單,分4步:

    1. 接收ios端發過來的購買憑證。
    2. 判斷憑證是否已經存在或驗證過,而後存儲該憑證。
    3. 將該憑證發送到蘋果的服務器驗證,並將驗證結果返回給客戶端。
    4. 若是須要,修改用戶相應的會員權限。

    考慮到網絡異常狀況,服務器的驗證應該是一個可恢復的隊列,若是網絡失敗了,應該進行重試。

    簡單來講就是將該購買憑證用Base64編碼,而後POST給蘋果的驗證服務器,蘋果將驗證結果以JSON形式返回。

 代碼以下 :
/*注意事項:
1.沙盒環境測試appStore內購流程的時候,請使用沒越獄的設備。
2.請務必使用真機來測試,一切以真機爲準。
3.項目的Bundle identifier須要與您申請AppID時填寫的bundleID一致,否則會沒法請求到商品信息。
4.若是是你本身的設備上已經綁定了本身的AppleID帳號請先註銷掉,不然你哭爹喊娘都不知道是怎麼回事。
5.訂單校驗 蘋果審覈app時,仍然在沙盒環境下測試,因此須要先進行正式環境驗證,若是發現是沙盒環境則轉到沙盒驗證。
識別沙盒環境訂單方法:
 1.根據字段 environment = sandbox。
 2.根據驗證接口返回的狀態碼,若是status=21007,則表示當前爲沙盒環境。
 蘋果反饋的狀態碼:
 21000App Store沒法讀取你提供的JSON數據
 21002 訂單數據不符合格式
 21003 訂單沒法被驗證
 21004 你提供的共享密鑰和帳戶的共享密鑰不一致
 21005 訂單服務器當前不可用
 21006 訂單是有效的,但訂閱服務已通過期。當收到這個信息時,解碼後的收據信息也包含在返回內容中
 21007 訂單信息是測試用(sandbox),但卻被髮送到產品環境中驗證
 21008 訂單信息是產品環境中使用,但卻被髮送到測試環境中驗證
 */

#import <Foundation/Foundation.h>

typedef enum {
    SIAPPurchSuccess = 0,       // 購買成功
    SIAPPurchFailed = 1,        // 購買失敗
    SIAPPurchCancle = 2,        // 取消購買
    SIAPPurchVerFailed = 3,     // 訂單校驗失敗
    SIAPPurchVerSuccess = 4,    // 訂單校驗成功
    SIAPPurchNotArrow = 5,      // 不容許內購
}SIAPPurchType;

typedef void (^IAPCompletionHandle)(SIAPPurchType type,NSData *data);


@interface STRIAPManager : NSObject
+ (instancetype)shareSIAPManager;
//開始內購
- (void)startPurchWithID:(NSString *)purchID completeHandle:(IAPCompletionHandle)handle;
@end
.m
#import "STRIAPManager.h"
#import <StoreKit/StoreKit.h>
@interface STRIAPManager()<SKPaymentTransactionObserver,SKProductsRequestDelegate>{
   NSString           *_purchID;
   IAPCompletionHandle _handle;
}
@end
@implementation STRIAPManager

#pragma mark - ♻️life cycle
+ (instancetype)shareSIAPManager{
    
    static STRIAPManager *IAPManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,^{
        IAPManager = [[STRIAPManager alloc] init];
    });
    return IAPManager;
}
- (instancetype)init{
    self = [super init];
    if (self) {
        // 購買監聽寫在程序入口,程序掛起時移除監聽,這樣若是有未完成的訂單將會自動執行並回調 paymentQueue:updatedTransactions:方法
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    return self;
}

- (void)dealloc{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}


#pragma mark - 🚪public
- (void)startPurchWithID:(NSString *)purchID completeHandle:(IAPCompletionHandle)handle{
    if (purchID) {
        if ([SKPaymentQueue canMakePayments]) {
            // 開始購買服務
            _purchID = purchID;
            _handle = handle;
            NSSet *nsset = [NSSet setWithArray:@[purchID]];
            SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
            request.delegate = self;
            [request start];
        }else{
            [self handleActionWithType:SIAPPurchNotArrow data:nil];
        }
    }
}
#pragma mark - 🔒private
- (void)handleActionWithType:(SIAPPurchType)type data:(NSData *)data{
#if DEBUG
    switch (type) {
        case SIAPPurchSuccess:
            NSLog(@"購買成功");
            break;
        case SIAPPurchFailed:
            NSLog(@"購買失敗");
            break;
        case SIAPPurchCancle:
            NSLog(@"用戶取消購買");
            break;
        case SIAPPurchVerFailed:
            NSLog(@"訂單校驗失敗");
            break;
        case SIAPPurchVerSuccess:
            NSLog(@"訂單校驗成功");
            break;
        case SIAPPurchNotArrow:
            NSLog(@"不容許程序內付費");
            break;
        default:
            break;
    }
#endif
    if(_handle){
        _handle(type,data);
    }
}
#pragma mark - 🍐delegate
// 交易結束
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
  // Your application should implement these two methods.
    NSString * productIdentifier = transaction.payment.productIdentifier;
    NSString * receipt = [transaction.transactionReceipt base64EncodedString];
    if ([productIdentifier length] > 0) {
        // 向本身的服務器驗證購買憑證
    }

    [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:NO];
}

// 交易失敗
- (void)failedTransaction:(SKPaymentTransaction *)transaction{
    if (transaction.error.code != SKErrorPaymentCancelled) {
        [self handleActionWithType:SIAPPurchFailed data:nil];
    }else{
        [self handleActionWithType:SIAPPurchCancle data:nil];
    }
    
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void)verifyPurchaseWithPaymentTransaction:(SKPaymentTransaction *)transaction isTestServer:(BOOL)flag{
    //交易驗證
    NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];
    
    if(!receipt){
        // 交易憑證爲空驗證失敗
        [self handleActionWithType:SIAPPurchVerFailed data:nil];
        return;
    }
    // 購買成功將交易憑證發送給服務端進行再次校驗
    [self handleActionWithType:SIAPPurchSuccess data:receipt];
    
    NSError *error;
    NSDictionary *requestContents = @{
                                      @"receipt-data": [receipt base64EncodedStringWithOptions:0]
                                      };
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                          options:0
                                                            error:&error];
    
    if (!requestData) { // 交易憑證爲空驗證失敗
        [self handleActionWithType:SIAPPurchVerFailed data:nil];
        return;
    }
    
    //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
    //In the real environment, use https://buy.itunes.apple.com/verifyReceipt
    
    NSString *serverString = @"https://buy.itunes.apple.com/verifyReceipt";
    if (flag) {
        serverString = @"https://sandbox.itunes.apple.com/verifyReceipt";
    }
    NSURL *storeURL = [NSURL URLWithString:serverString];
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:requestData];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                               if (connectionError) {
                                   // 沒法鏈接服務器,購買校驗失敗
                                   [self handleActionWithType:SIAPPurchVerFailed data:nil];
                               } else {
                                   NSError *error;
                                   NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
                                   if (!jsonResponse) {
                                       // 蘋果服務器校驗數據返回爲空校驗失敗
                                       [self handleActionWithType:SIAPPurchVerFailed data:nil];
                                   }
                                   
                                   // 先驗證正式服務器,若是正式服務器返回21007再去蘋果測試服務器驗證,沙盒測試環境蘋果用的是測試服務器
                                   NSString *status = [NSString stringWithFormat:@"%@",jsonResponse[@"status"]];
                                   if (status && [status isEqualToString:@"21007"]) {
                                       [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:YES];
                                   }else if(status && [status isEqualToString:@"0"]){
                                       [self handleActionWithType:SIAPPurchVerSuccess data:nil];
                                   }
#if DEBUG
                                   NSLog(@"----驗證結果 %@",jsonResponse);
#endif
                               }
                           }];
    
    
    // 驗證成功與否都註銷交易,不然會出現虛假憑證信息一直驗證不經過,每次進程序都得輸入蘋果帳號
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    NSArray *product = response.products;
    if([product count] <= 0){
#if DEBUG
        NSLog(@"--------------沒有商品------------------");
#endif
        return;
    }
    
    SKProduct *p = nil;
    for(SKProduct *pro in product){
        if([pro.productIdentifier isEqualToString:_purchID]){
            p = pro;
            break;
        }
    }
    
#if DEBUG
    NSLog(@"productID:%@", response.invalidProductIdentifiers);
    NSLog(@"產品付費數量:%lu",(unsigned long)[product count]);
    NSLog(@"%@",[p description]);
    NSLog(@"%@",[p localizedTitle]);
    NSLog(@"%@",[p localizedDescription]);
    NSLog(@"%@",[p price]);
    NSLog(@"%@",[p productIdentifier]);
    NSLog(@"發送購買請求");
#endif
    
    SKPayment *payment = [SKPayment paymentWithProduct:p];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

//請求失敗
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
#if DEBUG
    NSLog(@"------------------錯誤-----------------:%@", error);
#endif
}

- (void)requestDidFinish:(SKRequest *)request{
#if DEBUG
    NSLog(@"------------反饋信息結束-----------------");
#endif
}

#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
    for (SKPaymentTransaction *tran in transactions) {
        switch (tran.transactionState) {
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:tran];
                break;
            case SKPaymentTransactionStatePurchasing:
#if DEBUG
                NSLog(@"商品添加進列表");
#endif
                break;
            case SKPaymentTransactionStateRestored:
#if DEBUG
                NSLog(@"已經購買過商品");
#endif
                // 消耗型不支持恢復購買
                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:tran];
                break;
            default:
                break;
        }
    }
}
@end
在控制器中調用,導入頭文件
調用方法
- (void)purchaseAction{
    
    if (!_IAPManager) {
        _IAPManager = [STRIAPManager shareSIAPManager];
    }
    // iTunesConnect 蘋果後臺配置的產品ID
    [_IAPManager startPurchWithID:@"com.bb.helper_advisory" completeHandle:^(SIAPPurchType type,NSData *data) {
//請求事務回調類型,返回的數據
        
    }];
}
經典文章參考: 1.  http://yimouleng.com/2015/12/17/ios-AppStore/  內購流程
        2.   http://www.jianshu.com/p/b199a4672608   完成交易後和服務器交互
             3.   http://www.jianshu.com/p/1ef61a785508 沙盒帳號測試
相關文章
相關標籤/搜索