蘋果內(nèi)購流程及后臺(tái)配置

第一部分:在Apple后臺(tái)添加一個(gè)內(nèi)購產(chǎn)品

1、登錄appStoreConnect,如下圖所示,添加一個(gè)商品

增加內(nèi)購.png
IAP類型類型主要有4種:

1、Consumable products 適用于可多次購買的消耗型項(xiàng)目,如游戲道具、虛擬幣等。

2、Non-consumable products 適用于一次購買永久有效的項(xiàng)目,如電子書、游戲關(guān)卡等。該類型項(xiàng)目支持跨設(shè)備同步和本地restore,比如說,用戶在某個(gè)App中購買了一本書,可在所有相同Apple ID設(shè)備的App中免費(fèi)獲取這本書。

3、Auto-renewable subscriptions 適用于自動(dòng)續(xù)費(fèi)的訂閱項(xiàng)目,如Apple Music的按月訂閱,用戶購買后會(huì)每月自動(dòng)續(xù)費(fèi),直到用戶手動(dòng)取消或者開發(fā)者下架IAP項(xiàng)目。

4、Non-renewable subscriptions 適用于固定有效期的非自動(dòng)續(xù)費(fèi)項(xiàng)目,如云音樂的會(huì)員和一些視頻App的會(huì)員。

由于我們是充值虛擬幣學(xué)點(diǎn),所以選擇了Consumable類型。

2、填寫商品名稱、Product ID(Product ID一旦創(chuàng)建不可修改),選擇價(jià)格,然后拉到最下面添加商品購買時(shí)的截圖,最后保存

創(chuàng)建一個(gè)學(xué)點(diǎn).jpeg
點(diǎn)擊④,會(huì)進(jìn)入價(jià)格列表,對(duì)照下圖中的價(jià)格,選擇商品想要賣的價(jià)格在③中進(jìn)行選擇
價(jià)格參考.jpg

3、記住要添加截圖,不然狀態(tài)會(huì)變成Miss Metadata,添加截圖數(shù)據(jù)沒有問題后會(huì)變成Ready to Submit狀態(tài)

MissMetaData.jpeg

4、在App提交審核時(shí),把App當(dāng)前版本用到的內(nèi)購商品添加到App中,不添加的話,蘋果審核會(huì)被拒絕,報(bào)你有一個(gè)或多個(gè)內(nèi)購商品沒有提交審核的Issue

添加內(nèi)購商品.png

第二部分:蘋果支付流程

支付流程簡(jiǎn)單來講就是在App中點(diǎn)擊購買商品按鈕的時(shí)候,把在Apple后臺(tái)設(shè)置的Product ID通過SKProductsRequest傳給Apple后臺(tái),Apple后臺(tái)會(huì)把商品對(duì)象SKProduct回調(diào)給App,然后再把SKProduct加到支付隊(duì)列中,這個(gè)時(shí)候就會(huì)有彈框顯示支付金額讓你輸密碼了,支付成功后蘋果會(huì)把交易憑證返回,拿著憑證去公司服務(wù)器驗(yàn)證,驗(yàn)證為有效憑證發(fā)學(xué)點(diǎn),交易完成

1.點(diǎn)擊購買商品按鈕時(shí)傳入在Apple后臺(tái)的In-App Purchases中設(shè)置的相應(yīng)的Product ID
2、傳入Product ID參數(shù),通過SKProductsRequest發(fā)起請(qǐng)求,監(jiān)聽回調(diào)結(jié)果
-(void)starBuyToAppStoreWithGoodsId:(NSString *)goodsID cannotPayment:(void(^)(void))cannotPayment{
    //判斷app是否允許apple支付
    if (![SKPaymentQueue canMakePayments]) {
        if (cannotPayment) {
            cannotPayment();
        }
        return;
    }
    //1.點(diǎn)擊購買商品時(shí)傳入在Apple后臺(tái)的In-App Purchases中設(shè)置的相應(yīng)的Product ID
    //goodsID 就是在蘋果后臺(tái)設(shè)置的商品ID
    self.goodsId = goodsID; //比如Product ID可以是 com.example.example_LevelA
    NSArray *product = [[NSArray alloc] initWithObjects:goodsID,nil];
    NSSet *nsset = [NSSet setWithArray:product];
    
    //2、傳入Product ID參數(shù),通過SKProductsRequest發(fā)起請(qǐng)求,監(jiān)聽回調(diào)結(jié)果
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
    request.delegate = self;
    [request start];
}
3、在需要監(jiān)聽商品請(qǐng)求回調(diào)的地方,實(shí)現(xiàn)SKProductsRequestDelegate,確保Apple后臺(tái)返回的Product ID與步驟2中請(qǐng)求的一樣
4、把SKProduct加到支付隊(duì)列之前,創(chuàng)建一個(gè)訂單持久化到本地,用戶可以查看訂單狀態(tài),在支付流程中會(huì)遇到各種情況導(dǎo)致支付失敗,至少可以列舉5種可能的狀態(tài):0=待充值,1=充值完成,2=充值中,3=充值取消,4=充值失敗,可以根據(jù)各種狀態(tài)去更新訂單狀態(tài)
5、把SKProduct加入到支付隊(duì)列中,并給SKMutablePayment對(duì)象添加唯一標(biāo)識(shí)用于交易結(jié)束后獲取相應(yīng)的訂單改變訂單狀態(tài),當(dāng)被成功添加到支付隊(duì)列后這個(gè)時(shí)候就會(huì)有彈框了
#pragma mark -SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSArray *products = response.products;
    
    if([products count] == 0){
        if (self.delegate && [self.delegate respondsToSelector:@selector(appStorePayFailed)]) {
            [self.delegate appStorePayFailed];
        }
        return;
    }
    
    //3、在需要監(jiān)聽商品請(qǐng)求回調(diào)的地方,實(shí)現(xiàn)SKProductsRequestDelegate,確保Apple后臺(tái)返回的Product ID與步驟2中請(qǐng)求的一樣
    SKProduct *requestProduct = nil;
    for (SKProduct *product in products) {
        if([product.productIdentifier isEqualToString:self.goodsId]){
            requestProduct = product;
            break;
        }
    }
    
    if (!requestProduct) {
        return;
    }
    
//4、把SKProduct加到支付隊(duì)列之前,創(chuàng)建一個(gè)訂單持久化到本地,用戶可以查看訂單狀態(tài),在支付流程中會(huì)遇到各種情況導(dǎo)致支付失敗,至少可以列舉5種可能的狀態(tài):0=待充值,1=充值完成,2=充值中,3=充值取消,4=充值失敗,可以根據(jù)各種狀態(tài)去更新訂單狀態(tài)
    NSString *startTime = [NSString stringWithFormat:@"%.0f", [NSDate date].timeIntervalSince1970];
    NSString *applicationUsername = [NSString stringWithFormat:@"%@/%@/%@",[ZGAppInfoUtil appID], startTime, requestProduct.price];
    NSDictionary *dict = @{
        @"price" : requestProduct.price,
        @"startTime" : startTime,
        @"status" : @(InAppPurchaseStatusPrepare),
        @"receiptData" : @"",
        @"transactionID" : @"",
        @"applicationUserName" : applicationUsername
    };
    [self.chargeManager makeLearnPointOrderWithInfo:dict];
    
    
    //5、把SKProduct加入到支付隊(duì)列中,并給SKMutablePayment對(duì)象添加唯一標(biāo)識(shí)用于交易結(jié)束后獲取相應(yīng)的訂單改變訂單狀態(tài),當(dāng)被成功添加到支付隊(duì)列后這個(gè)時(shí)候就會(huì)有彈框了
    SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestProduct];
    payment.applicationUsername = applicationUsername;
    [[SKPaymentQueue defaultQueue] addPayment:payment];//將票據(jù)加入到交易隊(duì)列
}
6、監(jiān)聽購買結(jié)果 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions,根據(jù)不同的交易狀態(tài)去處理相應(yīng)的邏輯
#pragma mark -SKPaymentTransactionObserver
//監(jiān)聽購買結(jié)果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
     //6、支付結(jié)果的回調(diào),根據(jù)不同的交易狀態(tài)去處理相應(yīng)的邏輯
    
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                NSLog(@"交易已經(jīng)添加到服務(wù)隊(duì)列中");
                break;
            case SKPaymentTransactionStatePurchased:
                NSLog(@"已經(jīng)付費(fèi)了,交易完成");
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                NSLog(@"交易被取消或者添加到交易隊(duì)列失敗");
                break;
            case SKPaymentTransactionStateRestored:
                NSLog(@"交易從購買歷史列表中被恢復(fù),客戶端應(yīng)該完成交易");
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateDeferred:
                NSLog(@"在交易隊(duì)列中,還沒有最終的結(jié)果");
                break;
            default:
                break;
        }
    }
}
7、當(dāng)交易狀態(tài)為SKPaymentTransactionStatePurchased,意味著支付完成了,從沙盒中獲取交易憑證
8、拿到交易憑證后,在去后臺(tái)服務(wù)器后臺(tái)驗(yàn)證之前,需要把訂單狀態(tài)改為充值中,在持久化的訂單列表中對(duì)狀態(tài)為充值中的訂單可以發(fā)起補(bǔ)充值請(qǐng)求
9、用戶蘋果支付完成了,需要拿著蘋果返回的憑證去服務(wù)器校驗(yàn),校驗(yàn)成功發(fā)放學(xué)點(diǎn),并且該條訂單設(shè)置為完成狀態(tài),可能因?yàn)榫W(wǎng)絡(luò)原因校驗(yàn)訂單失敗,該條訂單狀態(tài)保持充值中狀態(tài)
//支付完成
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
    if (transaction.payment.productIdentifier && transaction.transactionIdentifier) {
        // 7、從沙盒中拿到交易憑證
        NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
        NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
        if (!receiptData) {
            return;
        }
        NSString *receiptString = [receiptData base64EncodedStringWithOptions:0];
        if (!receiptData) {
            return;
        }
        
        //8、拿到交易憑證后,在去后臺(tái)服務(wù)器后臺(tái)驗(yàn)證之前,需要把訂單狀態(tài)改為充值中,在持久化的訂單列表中對(duì)狀態(tài)為充值中的訂單可以發(fā)起補(bǔ)充值請(qǐng)求
        NSMutableDictionary *orderDic = [self.chargeManager getLearnPointOrderWithApplicationUsername:transaction.payment.applicationUsername].mutableCopy;
        [orderDic setObject:@(InAppPurchaseStatusOngoing) forKey:@"status"];
        [orderDic setObject:receiptString forKey:@"receiptData"];
        [orderDic setObject:transaction.transactionIdentifier forKey:@"transactionID"];
        
        //9、用戶蘋果支付完成了,需要拿著蘋果返回的憑證去服務(wù)器校驗(yàn),校驗(yàn)成功發(fā)放學(xué)點(diǎn),并且該條訂單設(shè)置為完成狀態(tài),可能因?yàn)榫W(wǎng)絡(luò)原因校驗(yàn)訂單失敗,該條訂單狀態(tài)保持充值中狀態(tài)
        NSMutableDictionary *params = [NSMutableDictionary dictionary];
        params[@"apple_receipt"] = receiptString ?: @"";
        __weak typeof(self) weakSelf = self;
        [OrderChargeManager verifyToServerWithReceipt:params success:^(NSDictionary * _Nonnull result) {
            if ([result[@"flag"] integerValue] == 1) { //校驗(yàn)成功
                if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(appStoreDidPaySuccess)]) {
                    [weakSelf.delegate appStoreDidPaySuccess];
                }
                [orderDic setObject:@(InAppPurchaseStatusCompleted) forKey:@"status"];
            } else {
                if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(appStoreWillPaySuccess)]) {
                    [weakSelf.delegate appStoreWillPaySuccess];
                }
            }
            
            [self.chargeManager makeLearnPointOrderWithInfo:orderDic];
        } fail:^(NSError * _Nonnull error) {
            if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(appStoreWillPaySuccessNetError)]) {
                [weakSelf.delegate appStoreWillPaySuccessNetError];
            }
            [weakSelf.chargeManager makeLearnPointOrderWithInfo:orderDic];
        }];
    }
    
    //不管憑證驗(yàn)證結(jié)果,最后完成交易
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
10、如果交易失敗,把訂單狀態(tài)更新為相應(yīng)的狀態(tài)
//交易失敗
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
   // 10、如果交易失敗,把訂單狀態(tài)更新為相應(yīng)的狀態(tài)
    NSMutableDictionary *orderDic = [self.chargeManager getLearnPointOrderWithApplicationUsername:transaction.payment.applicationUsername].mutableCopy;
    if (transaction.error.code == SKErrorPaymentCancelled) {
        if (self.delegate && [self.delegate respondsToSelector:@selector(appStorePayCancel)]) {
            [self.delegate appStorePayCancel];
        }
        [orderDic setObject:@(InAppPurchaseStatusCanceled) forKey:@"status"];
    } else {//其他錯(cuò)誤
        if (self.delegate && [self.delegate respondsToSelector:@selector(appStorePayFailed)]) {
            [self.delegate appStorePayFailed];
        }
        [orderDic setObject:@(InAppPurchaseStatusFailed) forKey:@"status"];
    }
    
    [self.chargeManager makeLearnPointOrderWithInfo:orderDic];
    
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

第三部分:訂單狀態(tài)記錄

在發(fā)起支付的過程中會(huì)遇到各種各樣的問題,比如用戶中途取消、用戶支付完成后拿著交易憑證去公司服務(wù)器驗(yàn)證的時(shí)候網(wǎng)絡(luò)不好或者公司服務(wù)器掛了,為了防止掉單也讓用戶看到自己訂單的支付記錄,有必要在本地使用Plist持久化一個(gè)訂單列表

1、在Document目錄下創(chuàng)建一個(gè)Plist文件
- (NSString *)filePath {
    if (!_filePath) {
        NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
        NSString *path = [document stringByAppendingPathComponent:@"PointCoinOrder.plist"];
        _filePath = path;
    }
    return _filePath;
}
2、創(chuàng)建訂單,更新訂單 (因?yàn)橐粭l訂單信息在支付流程的不同階段,最終的狀態(tài)會(huì)發(fā)生改變(創(chuàng)建、取消、失敗、完成等),需要要把老的訂單刪除,更新訂單狀態(tài)信息之后的訂單追加進(jìn)去)
- (dispatch_queue_t)queue {
    if (!_queue) {
        _queue = dispatch_queue_create("com.example.xxxxx", DISPATCH_QUEUE_SERIAL);
    }
    return _queue;
}

- (void)makeLearnPointOrderWithInfo:(NSDictionary *)dict {
    dispatch_async(self.queue, ^{
        NSMutableArray *resultArray = [NSMutableArray arrayWithArray:[NSArray arrayWithContentsOfFile:self.filePath]];
        //因?yàn)橐粭l訂單信息在支付流程的不同階段,最終的狀態(tài)會(huì)發(fā)生改變(創(chuàng)建、取消、失敗、完成等),需要要把老的訂單刪除,更新狀態(tài)信息之后的訂單追加進(jìn)去
        for (NSDictionary *subDict in resultArray) {
            if ([[subDict objectForKey:@"applicationUserName"] isEqualToString:[dict objectForKey:@"applicationUserName"]]) {
                [resultArray removeObject:subDict];
                break;
            }
        }
        if (dict) {
            [resultArray addObject:dict];
        }
        [resultArray writeToFile:self.filePath atomically:YES];
    });
}

//根據(jù)訂單的ID獲取訂單
- (NSDictionary *)getLearnPointOrderWithApplicationUsername:(NSString *)applicationUsername {
    NSMutableArray *resultArray = [NSMutableArray arrayWithArray:[NSArray arrayWithContentsOfFile:self.filePath]];
    NSMutableDictionary *resultDict;
    for (NSDictionary *subDict in resultArray) {
        if ([[subDict objectForKey:@"applicationUserName"] isEqualToString:applicationUsername]) {
            resultDict = [NSMutableDictionary dictionaryWithDictionary:subDict];
            //移除舊的字典
            [resultArray removeObject:subDict];
            break;
        }
    }
    return resultDict;

}
3、去公司服務(wù)器驗(yàn)證訂單
+ (void)verifyToServerWithReceipt:(NSDictionary *)receiptInfo success:(void(^)(NSDictionary *result))success fail:(void(^)(NSError *error))fail {
    
}

參考資料:

iOS 支付 --蘋果內(nèi)購解讀

iOS內(nèi)購全面實(shí)戰(zhàn)

?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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