內(nèi)購支付踩過的坑以及自己的解決途徑

更新:經(jīng)過這幾天的用戶反饋及自己的查找渗稍,發(fā)現(xiàn)了一些問題挑宠。首先,在添加觀察者之前是獲取不到未完成訂單的郑口,只有在觀察者的updateTransaction方法中才能獲取到鸳碧,所以,我和服務(wù)端同事聯(lián)調(diào)做了如下調(diào)整:

上個版本做的內(nèi)購支付犬性,在內(nèi)購封裝方法中有過初步介紹和整理瞻离,結(jié)果在版本上線后收到用戶的反饋說是支付成功,但是充值賬戶卻不能到賬乒裆,結(jié)果引發(fā)了退款等惡性問題琐脏,下面就我在實際項目中遇到的問題以及解決方案給出詳細(xì)的介紹(上述給出的鏈接是swift版本的,由于筆者項目依舊是OC語言缸兔,所以下面依舊以O(shè)C語言來介紹)

1.封裝的內(nèi)購工具一定要設(shè)置為單例模式日裙,且在程序啟動的時候初始化并在初始化中設(shè)置觀察者模式

筆者上個版本中雖說封裝了內(nèi)購支付工具,但是由于經(jīng)驗缺乏惰蜜,內(nèi)購工具只在支付頁面中有效昂拂,結(jié)果有一個巨大的坑,用戶可能在支付完成之前就退出了支付頁面抛猖,導(dǎo)致了支付成功但是卻沒有充值成功的情形格侯,在檢查代碼之后鼻听,我將內(nèi)購支付工具做成了單例,而且联四,這個單例的初始化放在了程序入口處撑碴,這一點要說明的是,為什么放到入口處呢朝墩?是因為放到這里醉拓,如果之前有未移除的訂單,可以在這里做一些邏輯處理收苏,因為項目及實際情況亿卤,筆者是這樣處理的:

這個方法不能奏效,移除不用鹿霸,此思路就是錯的

- (void)removeOldTransaction {

/*
    NSArray *tansactions = [SKPaymentQueue defaultQueue].transactions;
    //如果沒有移除過訂單信息
    BOOL result = NO;
    
    if ( ![kUserDefaults boolForKey:@"hasFinishOldTransaction"] && tansactions.count > 0) {
        for (SKPaymentTransaction *transaction in tansactions) {
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        }
        result = YES;
    }
    [kUserDefaults setBool:YES forKey:@"hasFinishOldTransaction"];
    if (result) {
        return;
    }
*/
}

+ (instancetype)sharedInstance {

    static YGIAPTool *tool;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        tool = [[YGIAPTool alloc] init];
    });
    return  tool;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
       // [self removeOldTransaction];移除不用
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    return self;
}

為什么要移除掉舊的訂單呢排吴?因為我之前的錯誤邏輯,導(dǎo)致一些訂單就算支付成功而且成功充值懦鼠,也沒有移除訂單钻哩,這個時候如果設(shè)置了觀察者,蘋果提供的系統(tǒng)API中會自動去查詢有沒有未移除的訂單肛冶,這樣就會繼續(xù)執(zhí)行充值邏輯街氢,可能會造成重復(fù)充值的情形,為了避免這種情況帶來的損失淑趾,筆者就只能硬性要求在版本升級后啟動時移除舊的訂單,這樣就不會有這種隱憂了忧陪。

更新:此處描述有誤扣泊,硬性移除訂單是不可取的,會給用戶造成一定的損失嘶摊,這里只需要指定updateTranscation方法延蟹,按照正確邏輯走就可以了

didFinishLaunching中調(diào)用初始化方法 [YGIAPTool sharedInstance];

更新,關(guān)于何時移除訂單的問題叶堆,之前想著本地存取憑證可以管理訂單阱飘,后來偶然間發(fā)現(xiàn),盡管是同一個訂單虱颗,如果有未完成的沥匈,每次啟動app,執(zhí)行到updateTransaction方法后忘渔,走到Purchased狀態(tài)后高帖,取出的憑證都是不一樣的,而交易的transactionIdentifier是一樣的畦粮,所以在訂單移除的問題上做了一些調(diào)整散址,首先乖阵,本地不用管理憑證,因為管理也沒有用预麸。因為業(yè)務(wù)需求瞪浸,我們不再存儲憑證,而是存儲交易id吏祸,每次判斷本地是否有交易id对蒲,如果某一條交易已經(jīng)有交易id了,就記錄到服務(wù)端犁罩,方便以后對賬齐蔽。這個時候結(jié)束交易我們選擇放到了充值成功,也就是success之中床估,同時移除掉本地存儲的交易id含滴。

2.關(guān)于何時移除訂單的問題

我之前搜索過相關(guān)的問題,網(wǎng)上給出的答案大都是在充值業(yè)務(wù)成功之后再移除訂單丐巫,這個也有一定的問題谈况,主要的就是網(wǎng)絡(luò)問題或者是用戶在充值完成之前就退出或者意外中斷的時候引發(fā)的問題,這些情況下都會造成訂單不能及時移除递胧,給支付體驗和充值風(fēng)險上帶來一定的問題碑韵。那么,怎么解決這種情況呢缎脾?當(dāng)然祝闻,我所提供的方案也只是相對自己遇到的問題上有所改善,至于全面而深入的方案遗菠,有知道的大神麻煩指點一下联喘,不勝感激。

我們都知道辙纬,如果在客戶端去處理驗證憑證的邏輯豁遭,很容易被有心人入侵做手腳,這個時候常用的保險做法就是客戶端將本次交易產(chǎn)生的憑證發(fā)給服務(wù)端贺拣,讓服務(wù)端去和蘋果服務(wù)器驗證蓖谢,在一定程度上能夠保證了安全性,那么這樣也有一個隱憂譬涡,萬一我傳給服務(wù)端了闪幽,但是服務(wù)端驗證失敗了呢?或者萬一由于網(wǎng)絡(luò)問題傳送失敗呢涡匀?這個時候再加一層保險沟使,就是客戶端在傳遞給服務(wù)端之前先將本憑證存儲下來(關(guān)于存儲方法,筆者在后面會介紹渊跋,這里也有),然后服務(wù)器驗證成功腊嗡,返回到我們的success回調(diào)中去移除本地憑證着倾,而相對應(yīng)的服務(wù)端也已經(jīng)存儲了我們的憑證,當(dāng)然考慮到服務(wù)器驗證失敗的問題燕少,這個邏輯就要在服務(wù)端處理卡者,筆者這里簡單說下:就是服務(wù)器接到客戶端傳的憑證后,也是先存下來客们,直到驗證成功并充值完成后才移除崇决,否則就定時去發(fā)送驗證,知道成功為止底挫。
服務(wù)端不多做介紹恒傻,主要還是客戶端邏輯,在移除本地憑證后建邓,如果服務(wù)端正常處理盈厘,那么充值就應(yīng)該到位了。

3.關(guān)于存儲憑證的坑

筆者一開始存儲用的是NSUserDefault方法官边,在每次支付成功后都會存儲憑證到本地沸手,然后在服務(wù)器驗證成功后,將本地存儲的憑證清空注簿。這樣看似乎沒有毛病契吉,但是如果用戶頻繁操作,會導(dǎo)致創(chuàng)建兩次或者更多次訂單诡渴,那么問題來了捐晶,NSUserDefault只能覆蓋(因為存儲的憑證對應(yīng)的key是同一個),這樣會造成只能保留最后一個存儲的憑證妄辩,會產(chǎn)生一些意想不到的支付問題惑灵,所以在得知這個之后,筆者改成了用數(shù)據(jù)庫存儲到本地恩袱,這樣我就可以在驗證成功后根據(jù)當(dāng)前憑證去刪除數(shù)據(jù)庫中的數(shù)據(jù)泣棋,而且還有一個好處是胶哲,如果憑證發(fā)送失敗畔塔,在合適的地點我可以遍歷數(shù)據(jù)庫中的憑證,然后進(jìn)行憑證驗證鸯屿,這樣用戶支付過的訂單就很難出現(xiàn)充值不對等的問題(到賬延遲問題是必然的澈吨,這個不知道有什么好方法沒)

4.關(guān)于觀察者方法updatedTransactions對應(yīng)狀態(tài)的處理問題。

SKPaymentTransactionStatePurchased:充值成功

SKPaymentTransactionStateFailed:充值失敗

SKPaymentTransactionStateRestored:恢復(fù)內(nèi)購

SKPaymentTransactionStatePurchasing:正在采購

對于這四種狀態(tài)對應(yīng)的處理情況寄摆,我這里簡單介紹一下:
正在采購:只要添加訂單谅辣,第一步就會走到這里,這里可以不作處理,要注意的是千萬不能在這里移除訂單婶恼,否則會崩潰桑阶,提示不能再采購狀態(tài)移除訂單柏副。

至于恢復(fù)內(nèi)購,筆者倒沒有遇到蚣录,不過這里主要進(jìn)行以下操作

- (void)removeTransaction {

    [[SKPaymentQueue defaultQueue] finishTransaction:self.currentTransaction];
}

只需要移除訂單就好了

充值失敻钤瘛:毋庸置疑,這時候訂單交易失敗萎河,就是廢訂單了荔泳,所以同樣要移除

充值成功:能進(jìn)入到這里,說明用戶支付成功虐杯,錢已經(jīng)扣掉了玛歌,那么它之后的相關(guān)處理就比較重要了,為了說明清晰擎椰,筆者用代碼來展示:

更新

- (void)requestValidReceipt:(SKPaymentTransaction *)transaction {
    
    self.currentTransaction = transaction;

    //交易驗證
    NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData = [NSData dataWithContentsOfURL:recepitURL];
    
    if(!receiptData){
        [kWindow showLoadingView:@"獲取支付憑證為空"];
        return;
    }
    //轉(zhuǎn)化為base64字符串
    NSString *receiptString= [receiptData base64EncodedStringWithOptions:0];;
    NSString *source = @"";
    if ([YGDataBase isReceiptExists:self.currentTransaction.transactionIdentifier]) {
        self.buyId = [YGDataBase getBuyIdWithReceipt:self.currentTransaction.transactionIdentifier];
        source = @"self.buyId = [YGDataBase getBuyIdWithReceipt:receiptString];";
    }else {
        source = @"購買界面";
        [self buySuccess];
        //1.先將交易id存起來
        [YGDataBase saveReceiptAndGoodsID:self.currentTransaction.transactionIdentifier goodId:self.buyId];
    }
    [self startValidReceipt:receiptString source:source];

    //2.傳給服務(wù)端憑證數(shù)據(jù)
    [kWindow showLoadingView];
    [[YGNetWorkTool sharedInstance] ApplePayReceiptVerifyBuyId:self.buyId buyType:1 receipt:receiptString success:^(id responseObj) {
        [kWindow hideLoadingView];
        if ([responseObj[@"code"] intValue] != 200 ) {
            [kWindow showLoadingView:responseObj[@"msg"]];
        }else {//充值成功之后將憑證移除
             [self removeTransaction];
            [YGDataBase removeReceipt:self.currentTransaction.transactionIdentifier];
        }
        if (self.transactionSuccess) {
            self.transactionSuccess(self.currentTransaction);
        }
        [self showAlert];
        self.buyId = nil;
       
        
    } failure:^(NSError *error) {
        [kWindow hideLoadingView];
        if (self.transactionSuccess) {
            self.transactionSuccess(self.currentTransaction);
        }
        self.buyId = nil;
    }];

}

- (void)requestValidReceipt:(SKPaymentTransaction *)transaction {
    
    self.currentTransaction = transaction;

    //獲取交易的憑證
    NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData = [NSData dataWithContentsOfURL:recepitURL];
    
    if(!receiptData){
        [kWindow showLoadingView:@"獲取支付憑證為空"];
        return;
    }
    //轉(zhuǎn)化為base64字符串
    NSString *receiptString= [receiptData base64EncodedStringWithOptions:0];
    //判斷本地是否已經(jīng)有過這個憑證支子,如果有,為了避免重復(fù)交易确憨,什么也不做(這個可能沒什么用译荞,不過為了財政安全和保險,加上也不錯)
    if ([YGDataBase isReceiptExists:receiptString]) {
        return;
    }

    [self buySuccess];//這個不用管休弃,是項目中的統(tǒng)計作用

    //1.先將憑證存起來
    [YGDataBase saveReceiptAndGoodsID:receiptString goodId:self.ID];
//移除當(dāng)前支付的交易
    [self removeTransaction];
//統(tǒng)計日志
    [self startValidReceipt:receiptString];
    
    //2.傳給服務(wù)端憑證數(shù)據(jù)
    [kWindow showLoadingView];
    [[YGNetWorkTool sharedInstance] ApplePayReceiptVerifyBuyId:self.ID buyType:1 receipt:receiptString success:^(id responseObj) {
        [kWindow hideLoadingView];
        if ([responseObj[@"code"] intValue] != 200 ) {
            [kWindow showLoadingView:responseObj[@"msg"]];
        }else {//充值成功之后將憑證移除 這一點要注意吞歼,一定是服務(wù)端返回200的時候才能將本地憑證移除,否則會造成支付后沒到賬的丟單問題
            
            [YGDataBase removeReceipt:receiptString];
        }
        if (self.transactionSuccess) {
            self.transactionSuccess(self.currentTransaction);
        }
        [self showAlert];
        self.ID = nil;
        
    } failure:^(NSError *error) {
        [kWindow hideLoadingView];
        if (self.transactionSuccess) {
            self.transactionSuccess(self.currentTransaction);
        }
        self.ID = nil;
    }];

}

按照這個邏輯走下來塔猾,一般的內(nèi)購支付問題應(yīng)該能夠解決了篙骡,筆者也是花了兩天的時間,反復(fù)驗證測試丈甸,將各種可能出現(xiàn)的奇葩操作都測試了一遍糯俗,結(jié)果充值都能夠正常進(jìn)行,希望能夠給有需要的童鞋一些幫助睦擂,有需要源碼的同學(xué)得湘,可以到我的github上查看相關(guān)的邏輯(里面附帶的一些牽扯到公司業(yè)務(wù),筆者有做了詳細(xì)的注釋),喜歡的可以給個贊或者?星哦

寫在最后:由于蘋果官方給出的驗證方法非常簡單顿仇,網(wǎng)上相關(guān)的內(nèi)購資料也大都基于官方文檔淘正,許多實際問題根本找不到方法,希望大家能多多分享些這方面的實際問題臼闻,為以后內(nèi)購的開發(fā)提供便利鸿吆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市述呐,隨后出現(xiàn)的幾起案子惩淳,更是在濱河造成了極大的恐慌,老刑警劉巖乓搬,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件思犁,死亡現(xiàn)場離奇詭異代虾,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)激蹲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門褐着,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人托呕,你說我怎么就攤上這事含蓉。” “怎么了项郊?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵馅扣,是天一觀的道長。 經(jīng)常有香客問我着降,道長差油,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任任洞,我火速辦了婚禮蓄喇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘交掏。我一直安慰自己妆偏,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布盅弛。 她就那樣靜靜地躺著钱骂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挪鹏。 梳的紋絲不亂的頭發(fā)上见秽,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機(jī)與錄音讨盒,去河邊找鬼解取。 笑死,一個胖子當(dāng)著我的面吹牛返顺,可吹牛的內(nèi)容都是我干的禀苦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼创南,長吁一口氣:“原來是場噩夢啊……” “哼伦忠!你這毒婦竟也來了省核?” 一聲冷哼從身側(cè)響起稿辙,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎气忠,沒想到半個月后邻储,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赋咽,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年吨娜,在試婚紗的時候發(fā)現(xiàn)自己被綠了脓匿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡宦赠,死狀恐怖陪毡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情勾扭,我是刑警寧澤毡琉,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站妙色,受9級特大地震影響桅滋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜身辨,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一丐谋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煌珊,春花似錦号俐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至洗贰,卻和暖如春找岖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背敛滋。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工许布, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绎晃。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓蜜唾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親庶艾。 傳聞我的和親對象是個殘疾皇子袁余,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

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

  • 《非銀行支付機(jī)構(gòu)網(wǎng)絡(luò)支付業(yè)務(wù)管理辦法》條款釋義 - 中國支付網(wǎng) - 中國支付行業(yè)第一門戶網(wǎng)站2016年7月1日...
    菜菜苔閱讀 7,564評論 1 44
  • 最近開發(fā)一個項目涉及到內(nèi)購, 也遇到過一些問題. 這里拿出來分享一下, 避免一些人走彎路.開頭先聊一聊最近蘋果關(guān)于...
    東方_未明閱讀 6,195評論 16 56
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)咱揍,斷路器颖榜,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • 九月份的時候到公司,開始制作一個游戲的sdk,包括了登錄注冊,初始化,信息收集等功能... 這些相對來說簡單些,最...
    DovYoung閱讀 2,724評論 5 7
  • 【讀經(jīng)】 箴言5 【金句】 恐怕將你的尊榮給別人,將你的歲月給殘忍的人;(箴言 5:9 和合本) 【感動】 經(jīng)文講...
    chanor閱讀 639評論 0 0