iOS內(nèi)購詳解

概述

iOS內(nèi)購是指蘋果 App Store 的應(yīng)用內(nèi)購買盒延,即In-App Purchase,簡稱IAP(以下本文關(guān)于內(nèi)購都簡稱為IAP)鼠冕,是蘋果為 App 內(nèi)購買虛擬商品或服務(wù)提供的一套交易系統(tǒng)添寺。為什么我們需要掌握IAP這套流程呢,因?yàn)锳pp Store審核指南規(guī)定:

如果您想要在 app 內(nèi)解鎖特性或功能 (解鎖方式有:訂閱懈费、游戲內(nèi)貨幣计露、游戲關(guān)卡、優(yōu)質(zhì)內(nèi)容的訪問
限或解鎖完整版等)憎乙,則必須使用 App 內(nèi)購買項(xiàng)目票罐。App 不得使用自身機(jī)制來解鎖內(nèi)容或功能,
如許可證密鑰泞边、增強(qiáng)現(xiàn)實(shí)標(biāo)記该押、二維碼等。App 及其元數(shù)據(jù)不得包含按鈕阵谚、外部鏈接或其他行動(dòng)號(hào)
召用語蚕礼,以指引用戶使用非 App 內(nèi)購買項(xiàng)目機(jī)制進(jìn)行購買。

這段話的大概意思就是APP內(nèi)的虛擬商品或服務(wù)梢什,必須使用 IAP 進(jìn)行購買支付奠蹬,不允許使用支付寶、微信支付等其它第三方支付方式(包括Apple Pay)绳矩,也不允許以任何方式(包括跳出App罩润、提示文案等)引導(dǎo)用戶通過應(yīng)用外部渠道購買。如果違反此規(guī)定翼馆,apple審核人員不會(huì)讓你的APP上架!=鸲取应媚!

內(nèi)購前準(zhǔn)備

APP內(nèi)集成IAP代碼之前需要先去開發(fā)賬號(hào)的ITunes Connect進(jìn)行以下三步操作:

1,后臺(tái)填寫銀行賬戶信息

2猜极,配置商品信息中姜,包括產(chǎn)品ID,產(chǎn)品價(jià)格等

3跟伏,配置用于測(cè)試IAP支付功能的沙箱賬戶丢胚。

填寫銀行賬戶信息一般交由產(chǎn)品管理人員負(fù)責(zé),開發(fā)者不需要關(guān)注受扳,開發(fā)者需要關(guān)注的是第二步和第三步携龟。

配置內(nèi)購商品

IAP 是一套商品交易系統(tǒng),而非簡單的支付系統(tǒng)勘高,每一個(gè)購買項(xiàng)目都需要在開發(fā)者后臺(tái)的Itunes Connect后臺(tái)為 App 創(chuàng)建一個(gè)對(duì)應(yīng)的商品峡蟋,提交給蘋果審核通過后坟桅,購買項(xiàng)目才會(huì)生效。內(nèi)購商品有四種類型:

  • 消耗型項(xiàng)目:只可使用一次的產(chǎn)品蕊蝗,使用之后即失效仅乓,必須再次購買,如:游戲幣蓬戚、一次性虛擬道具等
  • 非消耗型項(xiàng)目:只需購買一次夸楣,不會(huì)過期或隨著使用而減少的產(chǎn)品。如:電子書
  • 自動(dòng)續(xù)期訂閱:允許用戶在固定時(shí)間段內(nèi)購買動(dòng)態(tài)內(nèi)容的產(chǎn)品子漩。除非用戶選擇取消豫喧,否則此類訂閱會(huì)自動(dòng)續(xù)期,如:Apple Music這類按月訂閱的商品(有些雞賊的開發(fā)者以此收割對(duì)IAP商品不熟悉的用戶痛单,參考App Store“流氓”軟件)
  • 非續(xù)期訂閱:允許用戶購買有時(shí)限性服務(wù)的產(chǎn)品嘿棘,此 App 內(nèi)購買項(xiàng)目的內(nèi)容可以是靜態(tài)的。此類訂閱不會(huì)自動(dòng)續(xù)期

配置商品信息需要注意產(chǎn)品ID和產(chǎn)品價(jià)格

1旭绒,產(chǎn)品 ID 具有唯一性鸟妙,建議使用項(xiàng)目的 Bundle Identidier 作為前綴后面拼接自定義的唯一的商品名或者 ID(字母、數(shù)字)挥吵,這里有個(gè)坑:一旦新建一個(gè)內(nèi)購商品重父,它的產(chǎn)品ID將永遠(yuǎn)被占用,即使該商品已經(jīng)被刪除忽匈,已創(chuàng)建的內(nèi)購商品除了產(chǎn)品 ID 之外的所有信息都可以修改房午,如果刪除了一個(gè)內(nèi)購商品,將無法再創(chuàng)建一個(gè)相同產(chǎn)品 ID 的商品丹允,也意味著該產(chǎn)品 ID 永久失效9帷!雕蔽!

2折柠,在創(chuàng)建IAP項(xiàng)目的時(shí)候,需要設(shè)定價(jià)格批狐,產(chǎn)品價(jià)格只能從蘋果提供的價(jià)格等級(jí)去選擇扇售,這個(gè)價(jià)格等級(jí)是固定的,同一價(jià)格等級(jí)會(huì)對(duì)應(yīng)各個(gè)國家的貨幣嚣艇,比如等級(jí)1對(duì)應(yīng)1美元承冰、6元人民幣,等級(jí)2對(duì)應(yīng)2美元食零、12元人民幣……最高等級(jí)87對(duì)應(yīng)999.99美元困乒、6498元人民幣。另外可能是為了照顧某些貨幣區(qū)的開發(fā)者和用戶慌洪,還有一些特殊的等級(jí)顶燕,比如備用等級(jí)A對(duì)應(yīng)1美元凑保、1元人民幣,備用等級(jí)B對(duì)應(yīng)1美元涌攻、3元人民幣這樣欧引。除此之外,IAP項(xiàng)目不能定一個(gè)9.9元人民幣這樣不符合任何等級(jí)的價(jià)格恳谎。詳細(xì)價(jià)格等級(jí)表可以看蘋果的官方價(jià)格等級(jí)文檔

蘋果的價(jià)格等級(jí)表通常是不會(huì)調(diào)整的芝此,但也不排除在某些貨幣匯率發(fā)生巨大變化的情況下,對(duì)該貨幣的定價(jià)進(jìn)行調(diào)整因痛,調(diào)整前蘋果會(huì)發(fā)郵件通知開發(fā)者婚苹。

3,商品分成

App Store上的付費(fèi)App和App內(nèi)購鸵膏,蘋果與開發(fā)者默認(rèn)是3/7分成膊升。但實(shí)際上,在某些地區(qū)蘋果與開發(fā)者分成之前需要先扣除交易稅谭企,開發(fā)者的實(shí)際分成不一定是70%廓译。從2015年10月開始,蘋果對(duì)中國地區(qū)的App Store購買扣除了2%的交易稅债查,對(duì)于中國區(qū)帳號(hào)購買的IAP非区,開發(fā)者的實(shí)際分成在68%~69%之間。而且中國以外不同地區(qū)的交易稅標(biāo)準(zhǔn)也存在差異盹廷,如蘋果的官方價(jià)格等級(jí)文檔

征绸,如果需要嚴(yán)格計(jì)算實(shí)際收入,可能需要把這個(gè)部分也考慮進(jìn)來俄占。

針對(duì)不同地區(qū)的內(nèi)購管怠,內(nèi)購價(jià)格和對(duì)應(yīng)的開發(fā)者實(shí)際收入在蘋果的價(jià)格等級(jí)表中有詳細(xì)列舉。

另外缸榄,根據(jù)蘋果在2016年6月的新規(guī)則排惨,針對(duì)Auto-Renewable Subscription類型的IAP,如果用戶購買的訂閱時(shí)間超過1年碰凶,那么從第二年開始,開發(fā)者可以獲得85%的分成鹿驼。詳情可查看蘋果的訂閱產(chǎn)品價(jià)格說明

沙箱賬戶

新的內(nèi)購產(chǎn)品上線之前欲低,測(cè)試人員一般需要對(duì)內(nèi)購產(chǎn)品進(jìn)行測(cè)試,但是內(nèi)購涉及到錢畜晰,所以蘋果為內(nèi)購測(cè)試提供了 沙箱測(cè)試賬號(hào) 的功能砾莱,Apple Pay 推出之后 沙箱測(cè)試賬號(hào)`也可以用于 Apple Pay 支付的測(cè)試,沙箱測(cè)試賬號(hào) 簡單理解就是:只能用于內(nèi)購和 Apple Pay 測(cè)試功能的 Apple ID凄鼻,它并不是真實(shí)的 Apple ID腊瑟。

填寫沙箱測(cè)試賬號(hào)信息需要注意以下幾點(diǎn):

  • 電子郵件不能是別人已經(jīng)注冊(cè)過 AppleID 的郵箱
  • 電子郵箱可以不是真實(shí)的郵箱聚假,但是必須符合郵箱格式
  • App Store 地區(qū)的選擇,測(cè)試的時(shí)候彈出的提示框以及結(jié)算的價(jià)格會(huì)按照沙箱賬號(hào)選擇的地區(qū)來闰非,建議測(cè)試的時(shí)候新建幾個(gè)不同地區(qū)的賬號(hào)進(jìn)行測(cè)試1旄瘛!财松!

沙箱賬號(hào)測(cè)試的使用:

  • 首先沙箱測(cè)試賬號(hào)必須在真機(jī)環(huán)境下進(jìn)行測(cè)試瘪贱,并且是 adhoc 證書或者 develop 證書簽名的安裝包,沙盒賬號(hào)不支持直接從 App Store 下載的安裝包
  • 去真機(jī)的 App Store 退出真實(shí)的 Apple ID 賬號(hào)辆毡,退出之后并不需要在App Store 里面登錄沙箱測(cè)試賬號(hào)
  • 然后去 App 里面測(cè)試購買商品菜秦,會(huì)彈出登錄框,選擇 使用現(xiàn)有的 Apple ID舶掖,然后登錄沙箱測(cè)試賬號(hào)球昨,登錄成功之后會(huì)彈出購買提示框,點(diǎn)擊 購買眨攘,然后會(huì)彈出提示框完成購買主慰。

內(nèi)購流程

IAP的支付流程分為客戶端和服務(wù)端,客戶端的工作如下:

  • 獲取內(nèi)購產(chǎn)品列表(從App內(nèi)讀取或從自己服務(wù)器讀绕谌)河哑,向用戶展示內(nèi)購列表
  • 用戶選擇某個(gè)內(nèi)購產(chǎn)品后,先請(qǐng)求可用的內(nèi)購產(chǎn)品的本地化信息列表龟虎,此次調(diào)用Apple的StoreKit庫的代碼
  • 得到內(nèi)購產(chǎn)品的本地化信息后璃谨,根據(jù)用戶選擇的內(nèi)購產(chǎn)品的ID得到內(nèi)購產(chǎn)品
  • 根據(jù)內(nèi)購產(chǎn)品發(fā)起IAP購買請(qǐng)求,收到購買完成的回調(diào)
  • 購買流程結(jié)束后, 向服務(wù)器發(fā)起驗(yàn)證憑證以及支付結(jié)果的請(qǐng)求
  • 自己的服務(wù)器將支付結(jié)果信息返回給前端并發(fā)放虛擬產(chǎn)品

前端支付流程圖如下:

image.png
------------------------------ IAPManager.h -----------------------------
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef enum {
    IAPPurchSuccess = 0,       // 購買成功
    IAPPurchFailed = 1,        // 購買失敗
    IAPPurchCancel = 2,        // 取消購買
    IAPPurchVerFailed = 3,     // 訂單校驗(yàn)失敗
    IAPPurchVerSuccess = 4,    // 訂單校驗(yàn)成功
    IAPPurchNotArrow = 5,      // 不允許內(nèi)購
}IAPPurchType;

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

@interface IAPManager : NSObject
+ (instancetype)shareIAPManager;
- (void)startPurchaseWithID:(NSString *)purchID completeHandle:(IAPCompletionHandle)handle;
@end

NS_ASSUME_NONNULL_END



------------------------------ IAPManager.m -----------------------------

#import "IAPManager.h"
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>

@interface IAPManager()<SKPaymentTransactionObserver,SKProductsRequestDelegate>{
   NSString           *_currentPurchasedID;
   IAPCompletionHandle _iAPCompletionHandle;
}
@end

@implementation IAPManager
 
+ (instancetype)shareIAPManager{
     
    static IAPManager *iAPManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,^{
        iAPManager = [[IAPManager alloc] init];
    });
    return iAPManager;
}
- (instancetype)init{
    self = [super init];
    if (self) {
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    return self;
}
 
- (void)dealloc{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
 
 
- (void)startPurchaseWithID:(NSString *)purchID completeHandle:(IAPCompletionHandle)handle{
    if (purchID) {
        if ([SKPaymentQueue canMakePayments]) {
            _currentPurchasedID = purchID;
            _iAPCompletionHandle = handle;
            
            //從App Store中檢索關(guān)于指定產(chǎn)品列表的本地化信息
            NSSet *nsset = [NSSet setWithArray:@[purchID]];
            SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
            request.delegate = self;
            [request start];
        }else{
            [self handleActionWithType:IAPPurchNotArrow data:nil];
        }
    }
}

- (void)handleActionWithType:(IAPPurchType)type data:(NSData *)data{
#if DEBUG
    switch (type) {
        case IAPPurchSuccess:
            NSLog(@"購買成功");
            break;
        case IAPPurchFailed:
            NSLog(@"購買失敗");
            break;
        case IAPPurchCancel:
            NSLog(@"用戶取消購買");
            break;
        case IAPPurchVerFailed:
            NSLog(@"訂單校驗(yàn)失敗");
            break;
        case IAPPurchVerSuccess:
            NSLog(@"訂單校驗(yàn)成功");
            break;
        case IAPPurchNotArrow:
            NSLog(@"不允許程序內(nèi)付費(fèi)");
            break;
        default:
            break;
    }
#endif
    if(_iAPCompletionHandle){
        _iAPCompletionHandle(type,data);
    }
}
 
- (void)verifyPurchaseWithPaymentTransaction:(SKPaymentTransaction *)transaction{
    //交易驗(yàn)證
    NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];
     
    if(!receipt){
        // 交易憑證為空驗(yàn)證失敗
        [self handleActionWithType:IAPPurchVerFailed data:nil];
        return;
    }
    // 購買成功將交易憑證發(fā)送給服務(wù)端進(jìn)行再次校驗(yàn)
    [self handleActionWithType:IAPPurchSuccess data:receipt];
     
    NSError *error;
    NSDictionary *requestContents = @{
                                      @"receipt-data": [receipt base64EncodedStringWithOptions:0]
                                      };
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                          options:0
                                                            error:&error];
     
    if (!requestData) { // 交易憑證為空驗(yàn)證失敗
        [self handleActionWithType:IAPPurchVerFailed data:nil];
        return;
    }
     
    NSString *serverString = @"https:xxxx";
    NSURL *storeURL = [NSURL URLWithString:serverString];
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:requestData];
     
    [[NSURLSession sharedSession] dataTaskWithRequest:storeRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            // 無法連接服務(wù)器,購買校驗(yàn)失敗
            [self handleActionWithType:IAPPurchVerFailed data:nil];
        } else {
            NSError *error;
            NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
            if (!jsonResponse) {
                // 服務(wù)器校驗(yàn)數(shù)據(jù)返回為空校驗(yàn)失敗
                [self handleActionWithType:IAPPurchVerFailed data:nil];
            }
             
            NSString *status = [NSString stringWithFormat:@"%@",jsonResponse[@"status"]];
            if(status && [status isEqualToString:@"0"]){
                [self handleActionWithType:IAPPurchVerSuccess data:nil];
            } else {
                [self handleActionWithType:IAPPurchVerFailed data:nil];
            }
#if DEBUG
            NSLog(@"----驗(yàn)證結(jié)果 %@",jsonResponse);
#endif
        }
    }];
    
    // 驗(yàn)證成功與否都注銷交易,否則會(huì)出現(xiàn)虛假憑證信息一直驗(yàn)證不通過,每次進(jìn)程序都得輸入蘋果賬號(hào)
    [[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:_currentPurchasedID]){
            p = pro;
            break;
        }
    }
     
#if DEBUG
    NSLog(@"productID:%@", response.invalidProductIdentifiers);
    NSLog(@"產(chǎn)品付費(fèi)數(shù)量:%lu",(unsigned long)[product count]);
    NSLog(@"產(chǎn)品描述:%@",[p description]);
    NSLog(@"產(chǎn)品標(biāo)題%@",[p localizedTitle]);
    NSLog(@"產(chǎn)品本地化描述%@",[p localizedDescription]);
    NSLog(@"產(chǎn)品價(jià)格:%@",[p price]);
    NSLog(@"產(chǎn)品productIdentifier:%@",[p productIdentifier]);
#endif
     
    SKPayment *payment = [SKPayment paymentWithProduct:p];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}
 
//請(qǐng)求失敗
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
#if DEBUG
    NSLog(@"------------------從App Store中檢索關(guān)于指定產(chǎn)品列表的本地化信息錯(cuò)誤-----------------:%@", error);
#endif
}
 
- (void)requestDidFinish:(SKRequest *)request{
#if DEBUG
    NSLog(@"------------requestDidFinish-----------------");
#endif
}
 
#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
    for (SKPaymentTransaction *tran in transactions) {
        switch (tran.transactionState) {
            case SKPaymentTransactionStatePurchased:
                [self verifyPurchaseWithPaymentTransaction:tran];
                break;
            case SKPaymentTransactionStatePurchasing:
#if DEBUG
                NSLog(@"商品添加進(jìn)列表");
#endif
                break;
            case SKPaymentTransactionStateRestored:
#if DEBUG
                NSLog(@"已經(jīng)購買過商品");
#endif
                // 消耗型不支持恢復(fù)購買
                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:tran];
                break;
            default:
                break;
        }
    }
}

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


/* 調(diào)用支付方法
 - (void)purchaseWithProductID:(NSString *)productID{
      
     [[IAPManager shareIAPManager] startPurchaseWithID:productID completeHandle:^(IAPPurchType type,NSData *data) {
          
     }];
 }
 */

服務(wù)端的工作:

  • 接收iOS端發(fā)過來的購買憑證鲤妥,判斷憑證是否已經(jīng)存在或驗(yàn)證過佳吞,然后存儲(chǔ)該憑證。將該憑證發(fā)送到蘋果的服務(wù)器驗(yàn)證棉安,并將驗(yàn)證結(jié)果返回給客戶端底扳。

恢復(fù)購買

內(nèi)購有4種:消耗型項(xiàng)目,非消耗型贡耽,自動(dòng)續(xù)期訂閱衷模,非續(xù)期訂閱。 其中”非消耗型“和”自動(dòng)續(xù)期訂閱“需要提供恢復(fù)購買的功能蒲赂,例如創(chuàng)建一個(gè)恢復(fù)按鈕阱冶,不然審核很可能會(huì)被拒絕。

//調(diào)起蘋果內(nèi)購恢復(fù)接口
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

“消耗型項(xiàng)目”和“非續(xù)期訂閱”蘋果不會(huì)提供恢復(fù)的接口滥嘴,不要調(diào)用上述方法去恢復(fù)木蹬,否則有可能被拒!H糁濉镊叁!

“非續(xù)期訂閱”也是跨設(shè)備同步的尘颓,所以原則上來說也需要提供恢復(fù)購買的功能,但需要依靠app自建的賬戶體系恢復(fù)晦譬,不能用上述蘋果提供的接口疤苹。

內(nèi)購掉單

掉單是用戶付款買商品,錢扣了蛔添,商品卻沒到賬痰催。掉單一旦發(fā)生,用戶通常會(huì)很生氣地來找客服迎瞧。然后客服只能找開發(fā)人員把商品給用戶手動(dòng)加上夸溶。顯然,傷害用戶的體驗(yàn)凶硅,特別是傷害付費(fèi)用戶的體驗(yàn)缝裁,是一件相當(dāng)糟糕的事情。

掉單是如何產(chǎn)生的呢足绅?這需要從IAP支付的技術(shù)流程說起捷绑。

IAP的支付流程:

1,發(fā)起支付

2氢妈,扣費(fèi)成功

3粹污,得到receipt(支付憑據(jù))

4,去后臺(tái)驗(yàn)證憑據(jù)獲取商品交易狀態(tài)

5首量,返回?cái)?shù)據(jù)壮吩,驗(yàn)證成功前端刷新數(shù)據(jù)

  • 漏單情況一:

    2到3環(huán)節(jié)出問題屬于蘋果的問題,目前沒做處理加缘。

  • 漏單情況二:

3到4的時(shí)候出問題鸭叙,比如斷網(wǎng)。此時(shí)前端會(huì)把支付憑據(jù)持久化存儲(chǔ)下來拣宏,如果期間用戶卸載APP此單在前端就真漏了沈贝,如果沒有協(xié)助,下次重新打開app進(jìn)入購買頁會(huì)先判斷有無未成功的支付勋乾,有就提示用戶宋下,用戶選擇找回,重走4辑莫,5流程杨凑。這一步看產(chǎn)品需求怎么做,可以讓用戶自主選擇是否恢復(fù)未成功的支付也可以前端默默恢復(fù)就行摆昧。

  • 漏單情況三:

4到5的時(shí)候出問題。此時(shí)后臺(tái)其實(shí)已經(jīng)成功蜒程,只是前端沒獲取到數(shù)據(jù)绅你,當(dāng)漏單處理伺帘,下次進(jìn)入的時(shí)候先刷新數(shù)據(jù)即可。

內(nèi)購注意事項(xiàng)

  • 交易憑據(jù)receipt判重

一般來說驗(yàn)證支付憑據(jù)(receipt)是否有效放后臺(tái)去做忌锯,如果后臺(tái)不做判重伪嫁,同一個(gè)憑據(jù)就可以無數(shù)次驗(yàn)證通過,因?yàn)樘O果也不判重偶垮,這就會(huì)導(dǎo)致前端可以憑此取到的一個(gè)支付憑據(jù)可以去后臺(tái)無數(shù)次做校驗(yàn)U趴取!K贫妗脚猾!,后臺(tái)就會(huì)給前端發(fā)放無數(shù)次商品砚哗,但是用戶只支付了一次錢龙助,所以安全的做法是后臺(tái)把驗(yàn)證通過的支付憑據(jù)做個(gè)記錄,每次來新的憑據(jù)先判斷是否已經(jīng)使用過蛛芥,防止多次發(fā)放商品提鸟。

參考

iOS 內(nèi)購(In-App Purchase)總結(jié)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市仅淑,隨后出現(xiàn)的幾起案子舔腾,更是在濱河造成了極大的恐慌考杉,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桑逝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茬缩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門择卦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人醉鳖,你說我怎么就攤上這事捡硅。” “怎么了盗棵?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵壮韭,是天一觀的道長。 經(jīng)常有香客問我纹因,道長喷屋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任瞭恰,我火速辦了婚禮屯曹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己恶耽,他們只是感情好密任,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著偷俭,像睡著了一般浪讳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涌萤,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天淹遵,我揣著相機(jī)與錄音,去河邊找鬼负溪。 笑死透揣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的笙以。 我是一名探鬼主播淌实,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼猖腕!你這毒婦竟也來了拆祈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤倘感,失蹤者是張志新(化名)和其女友劉穎放坏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體老玛,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淤年,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蜡豹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片麸粮。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖镜廉,靈堂內(nèi)的尸體忽然破棺而出弄诲,到底是詐尸還是另有隱情,我是刑警寧澤娇唯,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布齐遵,位于F島的核電站,受9級(jí)特大地震影響塔插,放射性物質(zhì)發(fā)生泄漏梗摇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一想许、第九天 我趴在偏房一處隱蔽的房頂上張望伶授。 院中可真熱鬧断序,春花似錦、人聲如沸谎砾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽景图。三九已至,卻和暖如春碉哑,著一層夾襖步出監(jiān)牢的瞬間挚币,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工扣典, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妆毕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓贮尖,卻偏偏與公主長得像笛粘,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子湿硝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者薪前,不喜歡去冒險(xiǎn),但是人生放棄了冒險(xiǎn)关斜,也就放棄了無數(shù)的可能示括。 ...
    yichen大刀閱讀 6,033評(píng)論 0 4
  • 公元:2019年11月28日19時(shí)42分農(nóng)歷:二零一九年 十一月 初三日 戌時(shí)干支:己亥乙亥己巳甲戌當(dāng)月節(jié)氣:立冬...
    石放閱讀 6,870評(píng)論 0 2
  • 今天上午陪老媽看病,下午健身房跑步痢畜,晚上想想今天還沒有斷舍離垛膝,馬上做,衣架和旁邊的的布衣架丁稀,一看亂亂吼拥,又想想自己是...
    影子3623253閱讀 2,905評(píng)論 1 8