App內(nèi)購(gòu)?fù)P(guān):(二)代碼篇

一:內(nèi)購(gòu)流程
app內(nèi)購(gòu)流程圖
二:代碼實(shí)現(xiàn):內(nèi)購(gòu)工具類的集成
1.導(dǎo)入庫(kù)
#import <StoreKit/StoreKit.h>
2.遵守協(xié)議
<SKPaymentTransactionObserver, SKProductsRequestDelegate>
3.內(nèi)購(gòu)工具類的啟動(dòng)與注銷

程序啟動(dòng)就開(kāi)啟工具的原因: 簡(jiǎn)單來(lái)說(shuō)是為了防漏單馁痴,詳情在下面配合代碼來(lái)解釋筋栋。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   /**啟動(dòng)IAP工具類*/
    [[IAPManager shared] startManager];
    return YES;
}

//程序推出的時(shí)候關(guān)閉工具
- (void)applicationWillTerminate:(UIApplication *)application {
     /**結(jié)束IAP工具類*/
    [[IAPManager shared] stopManager];
}

4.內(nèi)購(gòu)工具類的啟動(dòng)與注銷

內(nèi)購(gòu)支付兩個(gè)階段:

  • 階段一: app直接向蘋(píng)果服務(wù)器請(qǐng)求商品,支付階段裸扶;
  • 階段二: 蘋(píng)果服務(wù)器返回憑證,app向公司服務(wù)器發(fā)送驗(yàn)證凌蔬,公司再向蘋(píng)果服務(wù)器驗(yàn)證階段闲礼。
- (void)startManager { //開(kāi)啟監(jiān)聽(tīng)
    /*
     階段一正在進(jìn)中,app退出。
     在程序啟動(dòng)時(shí)秤茅,設(shè)置監(jiān)聽(tīng)稚补,監(jiān)聽(tīng)是否有未完成訂單,有的話恢復(fù)訂單框喳。
     */
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    /*
     階段二正在進(jìn)行中,app退出课幕。
     在程序啟動(dòng)時(shí),檢測(cè)本地是否有receipt文件帖努,有的話,去二次驗(yàn)證粪般。
     */
    [self checkIAPFiles];
}

- (void)stopManager{ //移除監(jiān)聽(tīng) 
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
5.通過(guò)產(chǎn)品ID發(fā)起查詢商品請(qǐng)求
- (void)requestProductWithId:(NSString *)productId {
    if ([SKPaymentQueue canMakePayments]) { //用戶允許app內(nèi)購(gòu)
        if (productId.length) {
            NSArray *product = [[NSArray alloc] initWithObjects:productId, nil];
            NSSet *set = [NSSet setWithArray:product];
            SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
            productRequest.delegate = self;
            [productRequest start];
        } else {
            NSLog(@"商品為空");   
        }
    } else { 
          NSLog(@"沒(méi)有權(quán)限");
    }
}
6.查詢成功
#pragma mark SKProductsRequestDelegate 查詢成功后的回調(diào)
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSArray *product = response.products; 
    if (product.count == 0) { 
        NSLog(@"無(wú)法獲取商品信息拼余,請(qǐng)重試"); 
    } else {
        //發(fā)起購(gòu)買(mǎi)請(qǐng)求
        SKPayment * payment = [SKPayment paymentWithProduct:product[0]];
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
}
7.查詢失敗
#pragma mark SKProductsRequestDelegate 查詢失敗后的回調(diào)
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
   NSLog(@"查詢失敗:%@",[error localizedDescription]);
}
8.步驟6中查詢成功后發(fā)起了購(gòu)買(mǎi)請(qǐng)求,用戶操作付款后的回調(diào)
#pragma Mark 購(gòu)買(mǎi)操作后的回調(diào)
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {

    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing://正在交易
                break;
            
            case SKPaymentTransactionStatePurchased://交易完成
                
               //獲取蘋(píng)果訂單號(hào)
               //self.transaction_id = transaction.transactionIdentifier;
                [self getReceipt]; //獲取交易成功后的購(gòu)買(mǎi)憑證
                [self saveReceipt]; //存儲(chǔ)交易憑證
                [self checkIAPFiles];//把self.receipt發(fā)送到服務(wù)器驗(yàn)證是否有效
                [self completeTransaction:transaction];
                break;
                
            case SKPaymentTransactionStateFailed://交易失敗
                [self failedTransaction:transaction];
                break;
                
            case SKPaymentTransactionStateRestored://已經(jīng)購(gòu)買(mǎi)過(guò)該商品
                [self restoreTransaction:transaction];
                break;
            default:
            break;
        }
    }
}
9.獲取交易成功后的購(gòu)買(mǎi)憑證

注:驗(yàn)證用的receipt亩歹,不管是你處理匙监,還是讓服務(wù)器處理,發(fā)給蘋(píng)果驗(yàn)證的時(shí)候小作,必須是一個(gè)base64編碼的字符串亭姥。

- (void)getReceipt {
    NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL]; 
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl]; 
    self.receipt = [receiptData base64EncodedStringWithOptions:0];
}
10.先將購(gòu)買(mǎi)憑證存到本地

目的:防止用戶付款拿到receipt后,app發(fā)送給公司服務(wù)器的過(guò)程中顾稀,程序閃退等原因致使憑證丟失达罗。

#pragma mark  持久化存儲(chǔ)用戶購(gòu)買(mǎi)憑證(這里最好還要存儲(chǔ)當(dāng)前日期,用戶id等信息,用于區(qū)分不同的憑證)
-(void)saveReceipt {
    self.date = [NSDate chindDateFormate:[NSDate date]];
    NSString *fileName = [NSString uuid];
    self.userId = @"UserID";
    NSString *savedPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper iapReceiptPath], fileName];
    NSDictionary *dic =[NSDictionary dictionaryWithObjectsAndKeys:
                        self.receipt,                           receiptKey,
                        self.date,                              dateKey,
                        self.userId,                            userIdKey,
                        nil];  
    [dic writeToFile:savedPath atomically:YES];
}
11.檢查本地是否存在憑證
  • 步驟10中將憑證存到了本地粮揉,下面的方法就是查詢本地找到憑證巡李,發(fā)送給服務(wù)器;
  • 同時(shí)這個(gè)方法也會(huì)在程序啟動(dòng)即:內(nèi)購(gòu)工具類啟動(dòng)的時(shí)候調(diào)用扶认,如果能找到本地文件侨拦,說(shuō)明上次因?yàn)殚W退等原因?qū)е聭{證沒(méi)發(fā)送給服務(wù)器, 將會(huì)再次發(fā)送辐宾。(后面有驗(yàn)證成功后憑證的處理方式)
- (void)checkIAPFiles{
    NSFileManager *fileManager = [NSFileManager defaultManager];  
    NSError *error = nil;   
    //搜索該目錄下的所有文件和目錄
    NSArray *cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];    
     if (error == nil) {
        for (NSString *name in cacheFileNameArray) {    
            if ([name hasSuffix:@".plist"]){ //如果有plist后綴的文件狱从,說(shuō)明就是存儲(chǔ)的購(gòu)買(mǎi)憑證      
                NSString *filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];      
                [self sendAppStoreRequestBuyPlist:filePath];
            }
        } 
    } else {
        NSLog(@"AppStoreInfoLocalFilePath error:%@", [error domain]);
    }
}
12.將購(gòu)買(mǎi)憑證發(fā)送到公司服務(wù)器,根據(jù)服務(wù)器向蘋(píng)果驗(yàn)證返回的結(jié)果做相應(yīng)處理
  • 如果憑證有效叠纹,及此次交易完成季研,刪除本地的此次憑證。
-(void)sendAppStoreRequestBuyPlist:(NSString *)plistPath {

    NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:plistPath];
    //這里的參數(shù)請(qǐng)根據(jù)自己公司后臺(tái)服務(wù)器接口定制吊洼,但是必須發(fā)送的是持久化保存購(gòu)買(mǎi)憑證
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                   [dic objectForKey:receiptKey],          receiptKey,
                                   [dic objectForKey:dateKey],             dateKey,
                                   [dic objectForKey:userIdKey],           userIdKey,
                                   nil];

#warning 在這里將憑證發(fā)送給服務(wù)器
    
    if(@"憑證有效"){    
        [self removeReceipt];    
    } else {//憑證無(wú)效     
        //做你想做的
    }
}
13.刪除憑證
-(void)removeReceipt{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:[SandBoxHelper iapReceiptPath]]) {
        [fileManager removeItemAtPath:[SandBoxHelper iapReceiptPath] error:nil];
    }
}

14.結(jié)束交易
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}


15.如果交易失敗训貌,做相應(yīng)的提示,并在將交易結(jié)束
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
    if(transaction.error.code != SKErrorPaymentCancelled) {
       NSLog(@"購(gòu)買(mǎi)失敗");
    } else {
       NSLog(@"用戶取消了交易");
    }
    //將交易結(jié)束
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 
}

16.恢復(fù)已經(jīng)購(gòu)買(mǎi)過(guò)的產(chǎn)品
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

17.封裝成工具后的使用方法

一句代碼搞定

- (void)payClick {
    [[IAPManager shared] requestProductWithId:productId];
}
以上便為內(nèi)購(gòu)的全部流程冒窍,這里為代碼地址:

GitHub:https://github.com/YZQ-Nine/IAPDemo

App內(nèi)購(gòu)?fù)P(guān):(一)非代碼準(zhǔn)備篇

注:大坑

1.如果你是做類似優(yōu)酷土豆影視會(huì)員開(kāi)通的內(nèi)購(gòu)递沪,一定要添加設(shè)備購(gòu)買(mǎi)功能(既:賬號(hào)沒(méi)登錄也能購(gòu)買(mǎi))!W垡骸款慨!
2.app提交審核時(shí),附上內(nèi)購(gòu)測(cè)試流程Cāi莸臁!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末附帽,一起剝皮案震驚了整個(gè)濱河市埠戳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蕉扮,老刑警劉巖整胃,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異喳钟,居然都是意外死亡屁使,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)奔则,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蛮寂,“玉大人,你說(shuō)我怎么就攤上這事易茬〕晏#” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)除嘹。 經(jīng)常有香客問(wèn)我写半,道長(zhǎng),這世上最難降的妖魔是什么尉咕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任叠蝇,我火速辦了婚禮,結(jié)果婚禮上年缎,老公的妹妹穿的比我還像新娘悔捶。我一直安慰自己,他們只是感情好单芜,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布蜕该。 她就那樣靜靜地躺著,像睡著了一般洲鸠。 火紅的嫁衣襯著肌膚如雪堂淡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天扒腕,我揣著相機(jī)與錄音绢淀,去河邊找鬼。 笑死瘾腰,一個(gè)胖子當(dāng)著我的面吹牛皆的,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蹋盆,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼费薄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了栖雾?” 一聲冷哼從身側(cè)響起楞抡,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎析藕,沒(méi)想到半個(gè)月后召廷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體积暖,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兼呵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片找爱。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖泡孩,靈堂內(nèi)的尸體忽然破棺而出车摄,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布吮播,位于F島的核電站变屁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏意狠。R本人自食惡果不足惜粟关,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望环戈。 院中可真熱鬧闷板,春花似錦、人聲如沸院塞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拦止。三九已至县遣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汹族,已是汗流浹背萧求。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鞠抑,地道東北人饭聚。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像搁拙,于是被迫代替她去往敵國(guó)和親秒梳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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