[貝聊科技]貝聊 IAP 實戰之訂單綁定

你們好,我是貝聊科技 的 iOS 工程師 @NewPangit

注意:文章中討論的 IAP 是指使用蘋果內購購買消耗性的項目。github

此次爲你們帶來我司 IAP 的實現過程詳解,鑑於支付功能的重要性以及複雜性,文章會很長,並且支付驗證的細節也關係重大,因此這個主題會包含三篇。編程

第一篇:[iOS]貝聊 IAP 實戰之滿地是坑,這一篇是支付基礎知識的講解,主要會詳細介紹 IAP,同時也會對比支付寶和微信支付,從而引出 IAP 的坑和注意點。後端

第二篇:[iOS]貝聊 IAP 實戰之見坑填坑,這一篇是高潮性的一篇,主要針對第一篇文章中分析出的 IAP 的問題進行具體解決。數組

第三篇:[iOS]貝聊 IAP 實戰之訂單綁定,這一篇是關鍵性的一篇,主要講述做者探索將本身服務器生成的訂單號綁定到 IAP 上的過程。服務器

不用擔憂,我歷來不會只講原理不留源碼,我已經將我司的源碼整理出來,你使用時只須要拽到工程中就能夠了,下面開始咱們的內容 。微信

源碼在這裏。app

上兩篇文章已經針對 IAP 的九個大的問題中的八個問題進行了詳細的講解,若是你沒有看上一篇文章,建議你先去看一下再回來,由於這三篇文章是按部就班的。上一篇文章解決了第一篇文章提出的九個問題中的八個,還剩下一個,這一個問題至關關鍵,因此單獨用一篇文章來說解。ide

01.爲何如此關鍵?

到如今爲止,是否是感受全部的問題都指揮若定,內心有數了?post

那只是假象,show me the code,編程不是紙上談兵,而是須要親自動手實踐,細節是魔鬼。有位前輩說:「一樣是一個 for 循環,你寫在這裏只值 5 毛錢,可是我寫在那裏就值 5 萬塊」。固然這不是炫耀,而是想誇張的表達編程中細節的重要性。

前兩篇講的內容已經能夠串起來一個相對嚴謹的支付流程了。可是要把整個流程串起來,還差了關鍵的一步,而這一步並不是易事,至少做者走這一步就很是不容易。

這一步是什麼呢?就是要將公司服務器生成的訂單號 orderNo 綁定到蘋果的交易 paymentTransaction 上。第一篇文章中說了,蘋果的規範是用一個 product 生成一個 payment,而後將這個 payment 推入到 paymentQueue 之中,最後咱們成爲交易事務的監聽者,在監聽方法裏拿到交易的 paymentTransaction,咱們放進去一個蘋果的 payment 實例,最後獲得的是一個 paymentTransaction

問題來了,咱們最後拿到的是一個 paymentTransaction,蘋果只告訴咱們 哪個 paymentTransaction 成功了,而咱們根本就無法將咱們本身的訂單號綁定到這個成功的 paymentTransaction 上,從而創建映射,正確的去後臺驗證這個訂單。

而將咱們本身的訂單映射到 paymentTransaction 又是必須的,下面就一塊兒來看看這揪心的最後一步是怎麼走的。

02. 堪當大任的 applicationUsername?

我不相信蘋果會連這個問題都沒想到,因而就去找文檔, paymentTransaction 裏有一個 payment ,這個 payment 就是咱們本身用 product 建立的,可是 payment 的全部屬性都是 readonly 的,無法更改。好在有一個 SKMutablePayment,這個傢伙的有些屬性是 readwrite 的,其中有一個屬性叫作 applicationUsername

var applicationUsername: String
An opaque identifier for the user’s account on your system.
複製代碼

這是一個 iOS 7 之後纔有的屬性,能夠容許咱們本身往 payment 裏保存一個字符串類型的數據。

這不就恰好嘛,我就說蘋果不可能連這麼簡單的需求都想不到。好,就用這個屬性就 OK 了。當用戶點擊購買的時候,首先去後臺生成一筆交易,而後拿到交易訂單號 orderNo,而後將這個訂單號保存到 payment 上面,而後在蘋果支付成功的回調中獲取到 paymentTransacion,而後從這個 paymentTransacionpayment 中將保存的訂單號取出來,那麼就能實現咱們本身的訂單號和蘋果的訂單一一映射,perfect!

做者剛開始就是按照這個原理去實現的,直到功虧一簣。

事情是這樣的,做者公司的測試發現一旦某個訂單未推入 keychain 中持久化,而是等重啓的時候再去檢查未持久化的交易而後將其推入持久化隊列的時候,就會產生崩潰,從 bugly 後臺看到的數據顯示,是由於取 applicationUsername 的時候取不到。而後我就連上電腦測試,發現只要將 APP kill 掉,再次去取以前保存的 applicationUsername 的時候就是 nil。說到底就是蘋果根本就沒有給咱們存進去的信息作持久化,蘋果本身的屬性都有持久化,惟獨 applicationUsername 沒有。

「雞肋雞肋,食之無肉,棄之有味」,形象的表達了 applicationUsername 這個屬性的尷尬。show must go on,仍是得繼續尋找這關鍵一環的解決方案。

03.充分利用 purchasing?

接下來我就嘗試,既然蘋果不給咱們的 applicationUsername 屬性作持久化,那能不能咱們本身來作呢?

全部的交易都是有惟一的交易標識的,咱們若是能將全部的交易在 purchasing 狀態就存起來,那麼當某筆交易是 purchased 的時候,咱們就能以交易標識爲引子去一堆以前保存的 purchasing 狀態的 paymentTransaction 中找到對應的交易,而後取到咱們以前持久化的 applicationUsername。若是這樣能行得通,那咱們就又能把整個過程串起來了。

「理想很豐滿,現實很骨感」。某筆交易狀態仍是 purchasing 時,支付系統尚未爲這筆交易分配交易標識,因此就算是存了,也沒有辦法在那筆交易的狀態變爲 purchased 時從以前持久化的數據中找到存的數據。

這個方案也只能做罷。

04.粗放式驗證?

從以上兩個嘗試再結合蘋果後臺不對帳的風格,咱們大體能體會到,IAP 的設計思想就是不想讓咱們可以將本身的訂單關聯到 IAP 的訂單,這也符合蘋果一向想控制一切的做風。

在真正的解決方案浮出水面以前,做者規劃了一種**「粗放式的驗證」**來應對這種窘況,下面咱們來說一下什麼叫作「粗放式驗證」。

咱們將進入 purchasing 的全部訂單都持久化起來,而後此時雖然沒有分配交易標識,可是產品標識仍是有的。等某筆交易到了 purchased 的時候,咱們用這個 purchased 的交易的產品標識去持久化的交易中將全部是這個產品標識的交易都取出來組成一個數組,而後任一取一筆進行驗證,只要驗證成功了,就算交易成功。

若是難以理解,那咱們就對着上面這個圖來看看。咱們將本身的訂單號存到交易裏,而後將交易存起來,那麼本身的訂單號也獲得了持久化。之後在 purchased 的時候去取任意一筆交易的時候(指定產品標識的),其實取的是咱們後臺生成的任意一個交易訂單號(指定產品標識的),而後將已經完成的 IAP 交易和咱們的訂單號拼接組合起來進行驗證。

這種方案確實是能達到咱們驗證的目的。可是對於有潔癖的同窗來講,這個方案只能算是過渡方案,稱不上完美,更談不上優雅,因此只能叫作「粗放式的」。並且有一個無法避免的問題是,咱們存的那麼多 purchasing 狀態的交易,只有少數能在使用之後刪除,大多數都是無效的。可是咱們又沒有一個契機能去清理這個持久化數據,由於咱們根本無從知道那個交易是有用的,哪一個是無用的。因此咱們只能所有保存,不敢清理,這樣致使這個持久化數據愈來愈多,卻沒有清理的可能。

05.打破思惟慣性

如今想明白了就會知道,以上的嘗試迂迂迴回,都是掉進了思惟慣性裏了。咱們嚴苛遵循了古老的傳統:先去本身服務器建立訂單,再使用 IAP 交易。其實突破點就在這裏,咱們後端的一個同事提出,先去蘋果那裏交易,交易完成之後再去咱們本身的服務器建立訂單是否可行?

還記得第一篇文章中的這張圖嗎?

咱們調轉支付流程之後,應該變成下面這樣。

我不作解釋了,聰明的你必定知道這個微妙的區別帶來的極大的便利。至此,訂單綁定獲得了優雅的解決。

06.方案缺陷分析

若是是按照這個邏輯來走的話,有一個很顯而易見的邏輯缺陷,從 IAP 支付到咱們去後臺建立訂單這個過程有蘋果支付的和咱們建立訂單的延時。如今情景是用戶 A 發起了支付,而後還未購買就退出了登陸,而後用 B 帳號登陸了,而後 IAP 支付成功,咱們將支付信息存進了以 B 的 userid 爲 key 的帳戶中,這樣就會致使咱們去後臺驗證的時候會把錢充到 B 帳戶中,以下圖所示。

因此咱們在用戶退出登陸的時候須要去檢查他是否有未完成交易,若是有就要給個警告。可是仍是沒辦法完全解決掉這個問題,可是考慮到這個結果是用戶的行爲致使的,並且出現這個問題的概率不大,暫時就這樣處理。

若是你確實有這方面的擔憂,那就應該採用上面說的粗放式的驗證,粗放式的驗證是不存在這個問題的。

相關文章
相關標籤/搜索