1. 程式人生 > >iOS開發蘋果內購的介紹與實現

iOS開發蘋果內購的介紹與實現

ORC form col cas sda nds 發送 獨立開發 添加

1、iOS開發蘋果內購的介紹

  • 1.1 介紹

    • 蘋果規定,凡是虛擬的物品(例如:QQ音樂的樂幣)進行交易時,都必須走蘋果的內購通道,蘋果要收取大約30%的抽成,所以不允許接入第三方的支付方式(微信、支付寶等),當然開發者可以設置後門,在審核時避開審核人員。這個是有風險的,一旦發現,app會被立即下架,還是老老實實接入內購吧。
  • 1.2 註意

    • 內購接入還是比較簡單的,蘋果提供了專門的框架<StoreKit/StoreKit.h>,只要按照它提供的api進行開發就行。然而,接入的過程還是有需要註意的地方,分別是:漏單處理、二次驗證、移除交易、遊客模式。
    • 漏單處理:
      • 這個是一定會存在的,因為用戶的一些誤操作,造成的漏單基本無法避免,針對這種情況,最終的處理方式就是人工客服。當然,這個過程是可以優化的,開發者可以進行存儲訂單票據,server存儲訂單號,本地存儲票據。如果用戶啟動app後,檢測到用戶上次付了款,但是需要的商品沒有給到用戶,此時可以自動進行驗證並處理,驗證通過,就將商品補給用戶。
    • 二次驗證:
      • 這個步驟必不可少,首先正式環境驗證,如果驗證通過,說明是線上環境,可以正常操作。如果驗證不通過,說明是沙盒環境,需要在沙盒環境下再次驗證,沙盒環境下的驗證結果會有一個統一的彈框標識[Environment : Sandbox],只要內購沒有上線,驗證時都是沙盒環境彈框。 二次驗證的這個過程可以避免在審核app時,因為沒有驗證通過直接被拒的風險。二次驗證放在server端實現,更加安全。
    • 移除交易:
      • 用戶再次交易時,如果上次的交易沒有被移除,那麽此次的交易會一直在隊列中等候,無法被提交,所以一定要在上次交易完成時移除交易。
    • 遊客模式:
      • 如果我們的app支持遊客使用,那麽這個內購就必須要求對遊客進行開放,否則審核會被拒絕。
  • 1.3 實現步驟

    技術分享圖片

2、操作步驟

  • 2.1 協議、稅務和銀行業務 信息填寫

    • 1) 打開itunes Connect,選擇協議,稅務和銀行業務
      技術分享圖片
    • 2) 點擊Request Contracts(申請合同)下面的,request,點了幾個確定和下一步後回到主界面。
      技術分享圖片
      • Contact info:聯系人信息
      • Bank info:銀行信息
      • Tax info:稅務信息
        技術分享圖片
    • 3) 首先設置聯系人信息,點擊Contact info下面的 Set up(設置),點擊Add New Contract(增加先的聯系方式)
      技術分享圖片
    • 4) 填寫詳情
      • 填寫完成後點擊save(保存)
        技術分享圖片
    • 5) 在下面的所有項目中都選擇剛剛填寫的信息,選擇後點擊右下角的done(完成),你可以創建很多聯系人,在不同的職務選擇不同的聯系人。因為我是獨立開發,所以我全部填寫的我自己。
      • Senior Management:高管
      • Financial:財務
      • Technical:技術支持
      • Legal:法務
      • Marketing:市場推廣
        技術分享圖片
    • 6) 設置銀行信息,點擊Back info下面的Set up,彈出頁面
      • 點擊Add Bank Account(添加銀行賬號)
        技術分享圖片
      • 選擇china,後點擊next。
        技術分享圖片
      • 填寫了CNAPS Code後點擊Next
        技術分享圖片
      • 會彈出你的銀行卡開戶地的信息,確認一下點擊next
        技術分享圖片
      • 填寫銀行卡信息,註意:戶主名只能寫拼音,比如:李三(Li San)。填完後點擊Next
        技術分享圖片
      • 彈出確定信息頁面,在下面打鉤後點擊Save
        技術分享圖片
      • 點擊了save後就可以在彈出的頁面中選擇剛剛填寫的卡了。選擇後點擊Save
        技術分享圖片
    • 7) 設置稅務信息,點擊Tax info下面的Set up,此時聯系人信息已經變成可以編輯狀態,銀行信息為瀏覽狀態。
      技術分享圖片
      • 彈出的界面中,稅務分為三種
        • U.S Tax Forms: 美國稅務
        • Australia Tax Forms:澳大利亞稅務
        • Canada Tax Forms: 加拿大稅務
      • 這裏我選擇的美國稅務,就是第一個
        技術分享圖片
      • 彈出第一個選擇,點擊submit(提交)後,彈出第二個選擇
        技術分享圖片
      • 彈出第二個選擇,選擇後點擊submit
        技術分享圖片
      • 彈出第三個頁面,填寫的資料後點擊提交,記得勾選頁面上的幾個復選框
        技術分享圖片
      • 在提交成功後,狀態就變成processing成功
        技術分享圖片
    • 到這裏設置的協議就已經設置完了。
  • 2.2 內購商品的添加

    • 1)進入到項目的APP信息頁面,點擊功能,在彈出的頁面點擊App內購買項目後面的
      技術分享圖片
    • 2)在彈出的新對話框中選擇你需要哪一種服務,由於我的項目需要兌換成消耗的金幣,所以我選擇第一個。選擇後點擊創建。
      技術分享圖片
    • 3)開始填寫內購項目信息。填完後點擊右上角的存儲(所有信息必須填寫完整)。
      技術分享圖片
    • 4)點擊存儲後,內購列表就會有剛剛創建的內購條目。
      技術分享圖片
    • 你app有幾個內購級別就需要依次創建幾個條目。
  • 2.3 添加沙盒測試賬號

    • 添加測試賬號,用來測試支付功能
    • 1)點擊圖中用戶和職能
      技術分享圖片
    • 2)點擊沙盒測試員,然後點擊左邊的?按鈕。
      技術分享圖片
    • 3)設置好信息點擊右上角存儲就可以,記住裏面的郵箱和密碼用於支付的時候登陸Apple id
      技術分享圖片
  • 2.4 內購代碼的具體實現

    #import "TestPayController.h"
    
    // 1.首先導入支付包
    #import <StoreKit/StoreKit.h>
    
    // 2.設置代理服務
    @interface TestPayController ()<SKPaymentTransactionObserver,SKProductsRequestDelegate>
    
    @end
    
    @implementation TestPayController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.view.backgroundColor = [UIColor whiteColor];
    
        // 3.創建測試按鈕
        UIButton *testBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
        testBtn.backgroundColor = [UIColor redColor];
        [testBtn addTarget:self action:@selector(clickTestBtnAction) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:testBtn];
    
        // 4.添加觀察者:設置支付服務
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    
    }
    
    // 結束後一定要銷毀
    - (void)dealloc {
        [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
    }
    
    // 點擊測試按鈕
    - (void)clickTestBtnAction {
    
        // 5.點擊按鈕的時候判斷app是否允許apple支付
    
        // 如果app允許applepay
        if ([SKPaymentQueue canMakePayments]) {
            NSLog(@"yes");
    
            // 6.請求蘋果後臺商品
            [self getRequestAppleProduct];
        }
        else {
            NSLog(@"not");
        }
    }
    
    // 請求蘋果商品
    - (void)getRequestAppleProduct {
    
        // 7.這裏的com.czchat.CZChat01就對應著蘋果後臺的商品ID,他們是通過這個ID進行聯系的。
        NSArray *product = [[NSArray alloc] initWithObjects:@"com.czchat.CZChat01",nil];
    
        NSSet *nsset = [NSSet setWithArray:product];
    
        // 8.初始化請求
        SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
        request.delegate = self;
    
        // 9.開始請求
        [request start];
    }
    
    // 10.接收到產品的返回信息,然後用返回的商品信息進行發起購買請求
    - (void) productsRequest:(SKProductsRequest *)request 
          didReceiveResponse:(SKProductsResponse *)response {
    
        NSArray *product = response.products;
        // 如果服務器沒有產品
        if([product count] == 0){
            NSLog(@"nothing");
            return;
        }
    
        SKProduct *requestProduct = nil;
        for (SKProduct *pro in product) {
    
            NSLog(@"%@", [pro description]);
            NSLog(@"%@", [pro localizedTitle]);
            NSLog(@"%@", [pro localizedDescription]);
            NSLog(@"%@", [pro price]);
            NSLog(@"%@", [pro productIdentifier]);
    
            // 11.如果後臺消費條目的ID與我這裏需要請求的一樣(用於確保訂單的正確性)
            if([pro.productIdentifier isEqualToString:@"com.czchat.CZChat01"]){
                requestProduct = pro;
            }
        }
    
        // 12.發送購買請求
        SKPayment *payment = [SKPayment paymentWithProduct:requestProduct];
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
    
    // 請求失敗
    - (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
        NSLog(@"error:%@", error);
    }
    
    // 反饋請求的產品信息結束後
    - (void)requestDidFinish:(SKRequest *)request {
        NSLog(@"信息反饋結束");
    }
    
    // 13.監聽購買結果
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction {
    
        for(SKPaymentTransaction *tran in transaction) {
    
            switch (tran.transactionState) {
                case SKPaymentTransactionStatePurchased:
                    NSLog(@"交易完成");
    
                    break;
                case SKPaymentTransactionStatePurchasing:
                    NSLog(@"商品添加進列表");
    
                    break;
                case SKPaymentTransactionStateRestored:
                    NSLog(@"已經購買過商品");
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                    break;
                case SKPaymentTransactionStateFailed:
                    NSLog(@"交易失敗");
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                    break;
                default:
                    break;
            }
        }
    }
    
    // 14.交易結束,當交易結束後還要去appstore上驗證支付信息是否都正確,只有所有都正確後,我們就可以給用戶方法我們的虛擬物品了。
    - (void)completeTransaction:(SKPaymentTransaction *)transaction {
    
        NSString *str = [[NSString alloc]initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
    
        NSString *environment = [self environmentForReceipt:str];
    
        NSLog(@"----- 完成交易調用的方法completeTransaction 1--------%@", environment);
    
    
        // 驗證憑據,獲取到蘋果返回的交易憑據
        // appStoreReceiptURL iOS7.0增加的,購買交易完成後,會將憑據存放在該地址
        NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
        // 從沙盒中獲取到購買憑據
        NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
        /**
         BASE64 常用的編碼方案,通常用於數據傳輸,以及加密算法的基礎算法,傳輸過程中能夠保證數據傳輸的穩定性
         BASE64是可以編碼和解碼的
        */
        NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    
        NSString *sendString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
        NSLog(@"_____%@", sendString);
    
        NSURL *StoreURL = nil;
        if ([environment isEqualToString:@"environment=Sandbox"]) {
            StoreURL = [[NSURL alloc] initWithString: @"https://sandbox.itunes.apple.com/verifyReceipt"];
        }
        else {
            StoreURL= [[NSURL alloc] initWithString: @"https://buy.itunes.apple.com/verifyReceipt"];
        }
        // 這個二進制數據由服務器進行驗證;zl
        NSData * postData = [NSData dataWithBytes:[sendString UTF8String] length:[sendString length]];
    
        NSLog(@"++++++%@", postData);
        NSMutableURLRequest *connectionRequest = [NSMutableURLRequest requestWithURL:StoreURL];
    
        [connectionRequest setHTTPMethod:@"POST"];
        [connectionRequest setTimeoutInterval:50.0];
        [connectionRequest setCachePolicy:NSURLRequestUseProtocolCachePolicy];
        [connectionRequest setHTTPBody:postData];
    
        // 開始請求
        NSError *error=nil;
        NSData *responseData=[NSURLConnection sendSynchronousRequest:connectionRequest returningResponse:nil error:&error];
        if (error) {
            NSLog(@"驗證購買過程中發生錯誤,錯誤信息:%@", error.localizedDescription);
            return;
        }
        NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
        NSLog(@"請求成功後的數據:%@", dic);
        //這裏可以等待上面請求的數據完成後並且state = 0 驗證憑據成功來判斷後進入自己服務器邏輯的判斷,也可以直接進行服務器邏輯的判斷,驗證憑據也就是一個安全的問題。樓主這裏沒有用state = 0 來判斷。
        //  [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    
        NSString *product = transaction.payment.productIdentifier;
    
        NSLog(@"transaction.payment.productIdentifier++++%@", product);
    
        if ([product length] > 0) {
            NSArray *tt = [product componentsSeparatedByString:@"."];
    
            NSString *bookid = [tt lastObject];
    
            if([bookid length] > 0) {
                NSLog(@"打印bookid%@", bookid);
            // 這裏可以做操作吧用戶對應的虛擬物品通過自己服務器進行下發操作,或者在這裏通過判斷得到用戶將會得到多少虛擬物品,在後面([self getApplePayDataToServerRequsetWith:transaction];的地方)上傳上面自己的服務器。
            }
        }
        // 此方法為將這一次操作上傳給我本地服務器,記得在上傳成功過後一定要記得銷毀本次操作。調用[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        [self getApplePayDataToServerRequsetWith:transaction];
    }
    
    - (NSString * )environmentForReceipt:(NSString * )str {
    
        str = [str stringByReplacingOccurrencesOfString:@"\r\n" withString:@""];
        str = [str stringByReplacingOccurrencesOfString:@"\n" withString:@""];
        str = [str stringByReplacingOccurrencesOfString:@"\t" withString:@""];
        str = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
        str = [str stringByReplacingOccurrencesOfString:@"\"" withString:@""];
        NSArray *arr = [str componentsSeparatedByString:@";"];
    
        // 存儲收據環境的變量
        NSString *environment = arr[2];
        return environment;
    }
    
    @end

3、內購的註意事項

  • 1.必須用真機測試。
  • 2.測試的時候必須退出自己的apple ID。彈出頁面後登陸沙盒的測試apple id。
  • 註意:
    • 在需要修改已經上線的內購的時候需要重新創建新的內購條目,然後重新提交。

4、所遇見的問題以及解決辦法

  • 4.1 沙箱測試賬號無法登陸App Store的問題

    • a.手機操作系統不可以是越獄版本的
    • b.手機退出原有賬號以後,在測試的過程中直至點擊IAP內購按鈕以後,等它自己彈出提示框登陸
    • c.刪除測試App,重啟手機後重新安裝,發起購買請求,填寫沙箱賬號登陸
    • d.沙箱賬號在創建時的購買區域選中國
    • e.銀行稅務賬戶信息未填寫完全
    • f.沙箱賬號是在稅務信息填寫完整前創建的,無法登陸鏈接。在完善稅務信息後重新創建一個沙箱賬號登陸(這一條,很詭異,但是我創建的10個賬號,確實是信息完善前的兩個沒用,其他都可以)。
    • g.沙箱賬號和真實賬號沖突
  • 4.2 調用- (void)productsRequest:(SKProductsRequest )request didReceiveResponse:(SKProductsResponse )response時查不到商品信息,或者說產品標識符在invalidProductIdentifiers數組中被退返

    • a.App的App ID和內購項目的App的App ID不對應,請檢查
    • b.App ID沒有開啟IAP功能。登陸IOS開發者後臺,找到改App ID,重新edit,選擇上IAP功能後保存
    • c.在iTunes Connect中,蘋果拒絕了你最新向iTunes Connect提交的二進制碼。
    • d.你沒有清除iTunes Connect中在售的IAP產品。
    • e.可能修改了商品,但是這些修改沒有在所有App Store的服務器中生效。有時候會有延時,等等再說
    • f.你的商品由蘋果托管上,內容尚未上傳至iTunes Connect上。
    • g.商品的標識符不對。檢查傳給蘋果的標識符和創建的是否完全一致。
    • h.沒有向即將提交的新版本的內購項目中添加已經創建的內購項目。
    • i.沒有填完稅務信息。這一條重點說明下,稅務信息中,所有的信息都要填寫,包括聯系方式等等。只要你的信息有一點不完善,IAP的功能就無法測試,你也獲取不到商品的信息。
  • 參考來源
    • iOS 內購最新講解
    • iOS:蘋果內購實踐

iOS開發蘋果內購的介紹與實現