Apple 內(nèi)購(gòu)流程(代碼開(kāi)發(fā)級(jí)別)

Apple 內(nèi)購(gòu)流程:
1、向蘋(píng)果服務(wù)器,發(fā)送請(qǐng)求,獲取可購(gòu)買商品信息。
2、SKProductsRequest 協(xié)議 。獲取商品數(shù)據(jù)列表。
3、確定購(gòu)買商品(或回復(fù)商品)。
4、監(jiān)測(cè)交易過(guò)程。
5、驗(yàn)證交易收據(jù)(receipt)。
1、向蘋(píng)果服務(wù)器,發(fā)送請(qǐng)求,獲取可購(gòu)買商品信息。

//提交商品信息請(qǐng)求
- (void)requestProductWithProductArray:(NSArray *)productIdAry {
     NSSet *set = [[NSSet alloc] initWithArray:productIdAry];
    
    //“異步”請(qǐng)求有哪些可售商品
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
    //啟動(dòng)服務(wù)
    [request start];
}

2、SKProductsRequest 協(xié)議 。獲取商品數(shù)據(jù)列表。

#pragma mark - SKProductsRequest Delegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    //獲取商品數(shù)據(jù)
    NSArray *productAry = response.products;
    if ([productAry count] <= 0) {
        //暫無(wú)商品信息
        return;
    }
    
    //初始化本地商品信息個(gè)數(shù)
    if (self.productDic == nil) {
        self.productDic = [NSMutableDictionary dictionaryWithCapacity:response.products.count];
    }

    //遍歷商品數(shù)據(jù)
    for (SKproduct *product in response.products) {
        [self.productDis setObject:product forKey:product.productIdentifier];
        
        //打印產(chǎn)品列表信息
        NSLog(@"product ID : %@",product.productIdentifier);
        NSLog(@"product Description : %@",product.description);
        NSLog(@"product Title :%@",product.localizedTitle);
    }

#warning - Todo 可以將商品信息列表傳給ViewController以供顯示商品列表。
#warning - Todo 但不建議使用此信息顯示商品列表,可以用此信息進(jìn)行產(chǎn)品ID的驗(yàn)證。
}

#pragma mark - SKProductsRequest Delegate
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
    NSLog(@"---------------- 錯(cuò)誤 ---------------");
}

#pragma mark - SKProductsRequest Delegate
- (void)requestDidFinish:(SKPRequest *)request {
    NSLog(@"--------------- 反饋信息結(jié)束 ---------------");
}

3、確定購(gòu)買商品

#pragma mark - Buy Product
- (void)buyProduct:(NSString *)productID {
    //購(gòu)買商品
    SKProduct *buyProduct = self.productDic[productID];

    SKPayment *payment = [SKPayment paymentWithProduct:buyProduct];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

4、回復(fù)商品

#pragma mark - Restore Product
- (void)restorePurchase {
    //回復(fù)已經(jīng)完成的所有交易(僅限永久有效商品)。
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

5、監(jiān)測(cè)交易過(guò)程

#pragma mark - SKPaymentTransaction Observer
- (void)paymentQueue:(SKPaymentQueue *)queue updateTransactions:(NSArray<SKPaymentTransaction *> *)transaction {
    for (SKPaymentTransaction *transaction in transactions) {
        NSLog(@"交易隊(duì)列狀態(tài) :%@",transactions);
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:    //購(gòu)買成功
                //  向蘋(píng)果服務(wù)器進(jìn)行驗(yàn)證
                if (self.checkAfterPay) {
                    //支付成功了,并開(kāi)始向蘋(píng)果服務(wù)器進(jìn)行驗(yàn)證(若CheckAfterPay為NO,則不會(huì)經(jīng)過(guò)此步驟)
                }else {
                    //商品完全購(gòu)買成功且驗(yàn)證成功了。(若CheckAfterPay為NO,則會(huì)在購(gòu)買成功后直接觸發(fā)此方法)
                }
                //  目前都是全部進(jìn)行驗(yàn)證。
                [self verifyPruchaseWithPaymentTransaction:transaction isTestServer:NO];
                break;
                
            case SKPaymentTransactionStatePurchasing:   //  正在購(gòu)買
                NSLog(@"正在購(gòu)買...");
                break;
                
            case SKPaymentTransactionStateRestored:     //  恢復(fù)成功
                //  將交易從交易隊(duì)列中刪除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
                
            case SKPaymentTransactionStateFailed:       //  購(gòu)買取消或失敗
                if (transaction.error.code != SKErrorPaymentCancelled) {
                    NSLog(@"購(gòu)買失??!");
                }else{
                    NSLog(@"購(gòu)買取消!");
                }
                //  將交易從交易隊(duì)列中刪除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            
            default:
                //  將交易從交易隊(duì)列中刪除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
        }
    }
}

6、驗(yàn)證交易收據(jù)(receipt)

#pragma mark - Verify Receipt
- (void)verifyPruchaseWithPaymentTransaction:(SKPaymentTransaction *)transaction isTestServer:(BOOL)flag {
    //  驗(yàn)證憑據(jù),獲取到蘋(píng)果返回的交易憑據(jù)
    //  appStore Receipt URL iOS7.0增加的,購(gòu)買交易完成后,會(huì)將憑據(jù)存放在該地址
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    //  從沙盒中獲取到購(gòu)買憑據(jù)receipte
    NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
    
    
    if (!receipt) {
        //  交易憑證為空,驗(yàn)證失敗
        //  KIAPPurchVerFailed
        return;
    }else {
        //  購(gòu)買成功將交易憑證發(fā)送給服務(wù)端進(jìn)行再次校驗(yàn)
        //  kIAppurchSuccess
    }
    
    // 在網(wǎng)絡(luò)中傳輸數(shù)據(jù),大多情況下是傳輸?shù)淖址皇嵌M(jìn)制數(shù)據(jù)
    // 傳輸?shù)氖荁ASE64編碼的字符串
    /**
     BASE64 常用的編碼方案,通常用于數(shù)據(jù)傳輸,以及加密算法的基礎(chǔ)算法,傳輸過(guò)程中能夠保證數(shù)據(jù)傳輸?shù)姆€(wěn)定性
     BASE64是可以編碼和解碼的
     */
    NSString *encodeStr = [receipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    NSString *encodeLoad = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
    NSData *receiptData = [encodeLoad dataUsingEncoding:NSUTF8StringEncoding];
    
    //  發(fā)送網(wǎng)絡(luò)POST請(qǐng)求,對(duì)購(gòu)買憑證進(jìn)行驗(yàn)證
    //  In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
    //  In the real environment, use https://buy.itunes.apple.com/verifyReceipt
    NSString *serverStr = @"https://buy.itunes.apple.com/verifyReceipt";
    if (flag) {
        serverStr = @"https://sandbox.itunes.apple.com/verifyReceipt";
    }
    
    NSURL *storeURL = [NSURL URLWithString:serverStr];
    
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:receiptData];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [NSURLConnection sendAsynchronousRequest:storeRequest
                                       queue:queue
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                               if (connectionError) {
                                   // 無(wú)法連接服務(wù)器,購(gòu)買校驗(yàn)失敗
                                   // KIAPPurchVerFailed
                                   
                               } else {
                                   NSError *error;
                                   NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data
                                                                                                options:0
                                                                                                  error:&error];
                                   NSLog(@"----驗(yàn)證結(jié)果 %@",jsonResponse);
                                   if (!jsonResponse) {
                                       // 蘋(píng)果服務(wù)器校驗(yàn)數(shù)據(jù)返回為空校驗(yàn)失敗
                                       // KIAPPurchVerFailed
                                   }
                                   // 先驗(yàn)證正式服務(wù)器,如果正式服務(wù)器返回21007再去蘋(píng)果測(cè)試服務(wù)器驗(yàn)證,沙盒測(cè)試環(huán)境蘋(píng)果用的是測(cè)試服務(wù)器
                                   NSString *status = [NSString stringWithFormat:@"%@",jsonResponse[@"status"]];
                                   if (status && [status isEqualToString:@"21007"]) {
#warning - Todo 給ViewController提示
                                       [self verifyPruchaseWithPaymentTransaction:transaction isTestServer:YES];
                                       
                                   }else if(status && [status isEqualToString:@"0"]){
#warning - Todo 給ViewController提示
                                       //   kIAPPurchVerSuccwss 購(gòu)買成功切驗(yàn)證成功
                                   }
                               }
                           }];
    
    // 驗(yàn)證成功與否都注銷交易,否則會(huì)出現(xiàn)虛假憑證信息一直驗(yàn)證不通過(guò),每次進(jìn)程序都得輸入蘋(píng)果賬號(hào)
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    
}

PS:驗(yàn)證流程

訂單正確性的驗(yàn)證:
1.iOS客戶端(購(gòu)買成功)→ 到蘋(píng)果服務(wù)器驗(yàn)證 → 蘋(píng)果服務(wù)器返回驗(yàn)證結(jié)果,做相應(yīng)處理
2.iOS客戶端(購(gòu)買成功)→ 后臺(tái) → 蘋(píng)果服務(wù)器驗(yàn)證 → 蘋(píng)果服務(wù)器返回驗(yàn)證結(jié)果,做相應(yīng)處理

服務(wù)器要做的是:

1.接收iOS前端發(fā)過(guò)來(lái)的購(gòu)買憑證。

2.判斷憑證是否已經(jīng)存在或驗(yàn)證過(guò),然后存儲(chǔ)該憑證。

3.將該憑證發(fā)送到對(duì)應(yīng)環(huán)境下的蘋(píng)果服務(wù)器驗(yàn)證,并將驗(yàn)證結(jié)果返回給客戶端。

4.根據(jù)需求,是否修改用戶相應(yīng)信息。

注意事項(xiàng)

1.bundleID要與iTunes Connect上你App的相同,不然是請(qǐng)求不到產(chǎn)品信息的

2.在沙盒環(huán)境進(jìn)行測(cè)試內(nèi)購(gòu)的時(shí)候,要使用沒(méi)有越獄的蘋(píng)果手機(jī)。

3.在沙盒環(huán)境下真機(jī)測(cè)試內(nèi)購(gòu)時(shí),請(qǐng)去app store中注銷你的apple ID,不然發(fā)起支付購(gòu)買請(qǐng)求后會(huì)直接case:SKPaymentTransactionStateFailed。使用沙盒測(cè)試員的賬號(hào)時(shí)不需要真正花錢的。

4.如果只添加了一個(gè)沙盒測(cè)試員賬號(hào),當(dāng)一個(gè)真機(jī)已經(jīng)使用了這個(gè)賬號(hào),另一個(gè)真機(jī)再使用這個(gè)賬號(hào)支付也是會(huì)發(fā)生錯(cuò)誤的。那就去多建幾個(gè)沙盒測(cè)試員賬號(hào)使用不同的,反正也是免費(fèi)的,填寫(xiě)也很快。

5.監(jiān)聽(tīng)購(gòu)買結(jié)果,當(dāng)失敗和成功時(shí)代碼中要調(diào)用:

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

該方法通知蘋(píng)果支付隊(duì)列該交易已完成,不然就會(huì)已發(fā)起相同 ID 的商品購(gòu)買就會(huì)有此項(xiàng)目將免費(fèi)恢復(fù)的提示。

請(qǐng)?jiān)诒镜刈鲆幌聭{證存儲(chǔ)

現(xiàn)在訂單正確性的驗(yàn)證是:iOS客戶端(購(gòu)買成功)→ 后臺(tái)→后臺(tái)到蘋(píng)果服務(wù)器驗(yàn)證→處理后臺(tái)返回結(jié)果做相應(yīng)邏輯處理。

注意:
如果Your App 和 Your Server 中斷了鏈接
當(dāng)我們前端購(gòu)買成功后,憑證(receipt)本地保留一份,當(dāng)與后臺(tái)驗(yàn)證成功后,再將本地保留的憑證刪除。
否者一直使用本地已經(jīng)保留的憑證與后臺(tái)交互。

測(cè)試前提條件:
1.在itunesConnect中填寫(xiě)測(cè)試賬號(hào)。
2.在itunesConnect中填寫(xiě)稅務(wù)單(就是銀行賬號(hào),開(kāi)戶名,收款機(jī)構(gòu)等等的稅務(wù)單)。
3.交易收據(jù)內(nèi)容(receipt)

 "receipt":
    {
        "original_purchase_date_pst":"2015-06-22 20:56:34 America/Los_Angeles", //購(gòu)買時(shí)間,太平洋標(biāo)準(zhǔn)時(shí)間
        "purchase_date_ms":"1435031794826", //購(gòu)買時(shí)間毫秒
        "unique_identifier":"5bcc5503dbcc886d10d09bef079dc9ab08ac11bb",//唯一標(biāo)識(shí)符
        "original_transaction_id":"1000000160390314", //原始交易ID
        "bvrs":"1.0",//iPhone程序的版本號(hào)
        "transaction_id":"1000000160390314", //交易的標(biāo)識(shí)
        "quantity":"1", //購(gòu)買商品的數(shù)量
        "unique_vendor_identifier":"AEEC55C0-FA41-426A-B9FC-324128342652", //開(kāi)發(fā)商交易ID
        "item_id":"1008526677",//App Store用來(lái)標(biāo)識(shí)程序的字符串
        "product_id":"cosmosbox.strikehero.gems60",//商品的標(biāo)識(shí) 
        "purchase_date":"2015-06-23 03:56:34 Etc/GMT",//購(gòu)買時(shí)間
        "original_purchase_date":"2015-06-23 03:56:34 Etc/GMT", //原始購(gòu)買時(shí)間
        "purchase_date_pst":"2015-06-22 20:56:34 America/Los_Angeles",//太平洋標(biāo)準(zhǔn)時(shí)間
        "bid":"com.cosmosbox.StrikeHero",//iPhone程序的bundle標(biāo)識(shí)
        "original_purchase_date_ms":"1435031794826"http://毫秒
 }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 花了快10個(gè)工作日,終于完成了內(nèi)購(gòu)(IAP)功能。必須寫(xiě)篇文章來(lái)記錄一下這十天來(lái)的心得體會(huì),更是為了避免后續(xù)的開(kāi)發(fā)...
    flightlessBirdT閱讀 9,698評(píng)論 16 38
  • iOS應(yīng)用如果涉及到支付功能,分為兩類:第三方支付和蘋(píng)果內(nèi)購(gòu)。那么什么情況下選擇使用第三方支付,又在什么情況下選擇...
    ZfRee閱讀 39,205評(píng)論 36 66
  • 參考借鑒了一下幾篇文章(在這里很感謝各位作者大大做出的總結(jié)和方案): http://blog.csdn.net/a...
    輝546閱讀 14,247評(píng)論 3 12
  • 自己開(kāi)發(fā)的視頻直播項(xiàng)目,牽涉到充值金幣,用到了蘋(píng)果公司的內(nèi)購(gòu),趴坑了兩天,這里總結(jié)下實(shí)現(xiàn)蘋(píng)果內(nèi)購(gòu)。 一. 創(chuàng)建測(cè)試...
    Leo丶Dicaprio閱讀 3,500評(píng)論 8 7
  • Web前端框架就是為了節(jié)約開(kāi)發(fā)成本和時(shí)間,一般開(kāi)發(fā)一個(gè)項(xiàng)目都會(huì)用到前端框架(除非自己有前端開(kāi)發(fā)團(tuán)隊(duì)),根據(jù)我經(jīng)驗(yàn)找...
    UIleader閱讀 3,699評(píng)論 0 4

友情鏈接更多精彩內(nèi)容