內購所需要的資料整理總結,史上最完整的,哈哈哈哈哈哈
思維導圖
重點總結:
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.使用注意
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中,然后你請求商品。請求到商品后的流程是這樣的,蘋果系統會自動彈出登錄框讓你登錄賬號。然后根據提示操作進行購買,這里的賬號就是你配置的沙盒測試賬號。
二、為什么要使用內購?(why)和內購是什么?(what)
1.如果你購買的商品,是在本app中使用和消耗的,就一定要用內購,否則會被拒絕上線,例如:游戲幣,在線書籍 app中使用的道具等。本例中,就是直播中你用來打賞用的金幣,那東西可就屬於消耗型的。 2.如果是直接購買商城之類的快遞包郵的那些東東,那就直接調用支付寶,微信啦,之類的三方支付就好了,淘寶,京東都玩過哈! 比較坑的一點就是,內購的話,還要和蘋果3/7分成,那就可以說,充值相同的錢,相對來說,iOS是比安卓虧的!
三、怎樣使用內購?(how)
1.使用內購需要哪些資料
1張visa銀行卡,appid,1張銀行卡與蘋果三七分打錢用 (1)協議、稅務和銀行業務 聯系人信息:(appid賬號人)姓名,郵箱,電話號碼,地址(城市、具體街道分行寫) visa銀行卡信息:開戶行,開戶行所在地址,開戶行的郵政編碼,開戶行持有人卡號,開戶行持有人姓名 稅務信息:1.會問你是不是美國居民選擇NO. 2. 有沒有在美國從事商業性活動,選擇NO. 之后填寫個人或組織名稱,所在國家,受益方式(獨立開發者選擇個人),居住地址,郵寄地址,聲明人,頭銜 (2)內購產品id的配置 (開發人員配置) 如果是金幣或其它消耗品的產品的話用消耗性型項目,參考名稱(內購項目,比如金幣100),產品id,定價信息,使用內購的快照,顯示名稱,描述。 (3)用戶職能 測試員:添加水箱測試員及沙箱賬號,水箱測試賬號不能是正常使用的appid賬號,直接使用一個沒有注冊過的郵箱賬號即可。 姓名,測試賬號密碼,appstore地區(必須填對)。
四、操作流程圖解與代碼
1.創建app后填寫用戶信息
功能簡介 : 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,最后找到頁面如下:
填寫沙箱測試員和添加內購產品注意事項 1、郵箱必須是沒有注冊或者說關聯過appstore的郵箱。 2、密碼必須有一個是大寫字母有一個是小寫字母(蘋果規定的,理解)。 3、內購屏幕截圖規格必須是312*290,且最低分辨率是72ppi。 4、內購的價格是蘋果規定的不能自定義(坑啊)。
4.增加內購測試賬號
4.1 內購測試之前准備
1、什么是內購測試賬號(what)及為什么使用內購測試賬號(why)? iOS應用里面用到了蘋果應用內付費(IAP)功能,在項目上線前一定要進行功能測試。測試肯定是需要的,何況這個跟money有關。。。開發完成了之后,如何進行測試呢?難道我測試個內購功能要自己掏錢?就算是也是公司掏錢,但是蘋果要吃掉3成的啊,想想如果是99刀的商品,點下購買的時候心里都有點發慌。。。 蘋果當然沒這么坑了,測試內購,蘋果提供了沙盒賬號(也叫沙箱賬號)的方式。這個沙箱賬號其實是虛擬的AppleID,在開發者賬號后台的iTune Connect上配置了之后就能使用沙盒賬號測試內購,有了沙盒賬號,就能體驗一把土豪的感覺了,游戲鑽石什么的隨便充,反正不用我的錢。 注意:你可以把沙盒賬號看做是一個虛擬的AppleID,這個AppleID只有進行內購測試的功能。重要,重要,重要,這個虛擬的賬號只能在自己的測試號中使用,如果在其它地方如appstore使用的話會提示賬號無效之類的話。 2、如何使用內購測試賬號(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.代碼及業務邏輯
業務邏輯
- 獲取內購列表(從App內讀取或從自己服務器讀取)
- App Store請求可用的內購列表
- 向用戶展示內購列表
- 用戶選擇了內購列表,再發個購買請求,收到購買完成的回調(購買完成后會把錢打給申請內購的銀行卡內)
- 購買流程結束后, 向服務器發起驗證憑證以及支付結果的請求
- 自己的服務器將支付結果信息返回給前端並發放虛擬產品
-
服務端的工作比較簡單,分4步:
- 接收ios端發過來的購買憑證。
- 判斷憑證是否已經存在或驗證過,然后存儲該憑證。
- 將該憑證發送到蘋果的服務器驗證,並將驗證結果返回給客戶端。
- 如果需要,修改用戶相應的會員權限。
考慮到網絡異常情況,服務器的驗證應該是一個可恢復的隊列,如果網絡失敗了,應該進行重試。
簡單來說就是將該購買憑證用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 沙盒賬號測試