1. 適用情況
想使用In-App Purchase(以下簡稱IAP)完成App內付費前,先確定需求是不是能用這個方案來滿足。
除了IAP以外,還有支付寶SDK、信用卡等其他方式完成軟件內付費。
在蘋果制定的遊戲規則中,所有在App內提供的服務需要付費時,都應當使用IAP,比如軟件功能、遊戲道具;所有在App外提供的服務需要付費時,都應使用其他支付方式,比如Uber的信用卡,淘寶、快的打車的支付寶SDK等。
在IAP裏,可以出售:
- 數字內容:比如雜誌、圖片、遊戲關卡解鎖、相機付費濾鏡等;
- 軟件功能:如各種擴展features;
- 一次性服務:比如一次語音通話等。注意是「一次性」服務,不是一次「性服務」。
在IAP裏,不能出售:
- 現實世界的商品或服務:比如剛纔提到的一次「性服務」。嚴格遵守此方案有個好處:IAP 如果被破解,用戶無法得到大量實物,開發商也不會有很大經濟損失。非要做的話想繞過也是可以的:用 IAP 購買代幣,審覈通過後用代幣購買實物。
- 其他the App Review GuideLines中規定的不允許的內容:比如剛纔提到的一次「性服務」。
順便說下,有次大網易的同事分享時提到:使用兌換碼兌換App內服務是一條高壓線。像Uber和Amazon裏允許有碼,是因爲他們的碼是用在現實世界的產品或服務上的。
如果你確定內購需求符合IAP的使用要求,可以繼續往下讀了。
2. 購買及發放虛擬產品流程
官方給出的流程圖是這樣的:
- 獲取內購列表(從App內讀取或從自己服務器讀取)
- App Store請求可用的內購列表
- 向用戶展示內購列表
- 用戶選擇了內購列表,再發個購買請求
- 收到購買完成的回掉
- 發放虛擬產品
3.虛擬產品
虛擬產品的分類
虛擬產品分爲以下幾種類型:
- 消耗品(Consumable products):比如遊戲內金幣等。
- 不可消耗品(Non-consumable products):簡單來說就是一次購買,終身可用(用戶可隨時從App Store restore)。
- 自動更新訂閱品(Auto-renewable subscriptions):和不可消耗品的不同點是有失效時間。比如一整年的付費週刊。在這種模式下,開發者定期投遞內容,用戶在訂閱期內隨時可以訪問這些內容。訂閱快要過期時,系統將自動更新訂閱(如果用戶同意)。
- 非自動更新訂閱品(Non-renewable subscriptions):一般使用場景是從用戶從IAP購買後,購買信息存放在自己的開發者服務器上。失效日期/可用是由開發者服務器自行控制的,而非由App Store控制,這一點與自動更新訂閱品有差異。
- 免費訂閱品(Free subscriptions):在Newsstand中放置免費訂閱的一種方式。免費訂閱永不過期。只能用於Newsstand-enabled apps。
類型2、3、5都是以Apple ID爲粒度的。比如小張有三個iPad,有一個Apple ID購買了不可消耗品,則三個iPad上都可以使用。
類型1、4一般來說則是現買現用。如果開發者自己想做更多控制,一般選4。
幾種產品的區別如下(表格懶得翻譯了):
Table 1-1 Comparison of product types
Product type | Non-consumable | Consumable |
---|---|---|
Users can buy | Once | Multiple times |
Appears in the receipt | Always | Once |
Synced across devices | By the system | Not synced |
Restored | By the system | Not restored |
Table 1-2 Comparison of subscription types
Subscription type | Auto-renewable | Non-renewing | Free |
---|---|---|---|
Users can buy | Multiple times | Multiple times | Once |
Appears in the receipt | Always | Once | Always |
Synced across devices | By the system | By your app | By the system |
Restored | By the system | By your app | By the system |
關於自動更新訂閱品更新週期組(Auto-Renewable Subscription Duration Families):
每種訂閱品的每種更新週期可以在iTunes Connect中設置一個單獨的價格。圖中給出了一種訂閱品的不同長度的更新週期的價格截圖:
你可以把每種訂閱品的每個長度的更新週期看成一個單獨的產品,每個產品有自己獨有的時長、價格、市場促銷屬性。
爲了讓用戶可以從中挑選一個偏好的更新週期,上圖中我們爲此種訂閱的每個長度的更新週期分別設值了一個單獨的價格,有一週的、一個月的、兩個月的、季度的、半年的和一年的。
上圖中這種訂閱品的全部六種更新週期合起來稱爲一個自動更新訂閱品更新週期組(Auto-Renewable Subscription Duration Families)。
4. 人肉和iTunes Connect交互
填寫銀行卡與納稅信息
- 登錄iTunes Connect
- 點擊Agreements, Tax, and Banking,填寫Contact Info, Bank Info, Tax Info。如果填過就不用再填了。剛剛填寫完成後,各種信息正在審覈,如下圖:
即使信息正在審覈,沙箱環境下也是可以訪問IAP服務的,並不需要等審覈完成才能測試。
新建虛擬產品
- 登錄iTunes Connect
- 點擊My Apps
- 進入想使用IAP的App詳情
- 選擇In-App Purchases
- 點擊Create New
- 選擇IAP虛擬產品類型。注意虛擬產品一旦新建,類型無法修改。
- 填入Internal Name。只能在iTunes Connect中看到這個名字。不會出現在App Store中。最長255字節。
- 填入Product ID。每件產品有一個單獨的Product ID,Product ID用於從App Store獲取價格信息,以及付費時標識是哪種產品被購買了。例如com.163.neteasemusic.skin.dog。這個新建之後也是不能修改的。
- 然後是設置價格。Cleared For Sale選爲YES是虛擬產品被審覈通過自動上架。NO是手動上架。Price Tier則是價格。
- 多語言描述,這個是給用戶看的。
- 是否要在iTunes託管可購買內容,這個後面再說
- 填入review notes和review用的截圖。
- 保存。
- 保存後除了類型和Product ID都可以修改
新建完,不用等待蘋果審覈就可以在沙箱環境使用了。
新建測試帳號
- 登錄iTunes Connect
- 點擊Users and Roles
- 點擊Sandbox Testers
- 添加Tester。添加後在測試機上用tester帳號登錄app store
附:在蘋果託管不可消耗品(Non-consumable products)的內容需知
託管內容僅限於針對不可消耗品。
首次創建不可消耗品時可以選擇把內容託管到蘋果服務器,當然也可以隨時將自己服務器上的內容遷移到蘋果服務器由蘋果託管。
需要使用託管功能的話,首先在iTunes Connect中提交不可消耗品讓蘋果審覈。然後在Xcode中選取In-App Purchase Content template創建虛擬產品, 放入需要託管的內容, 然後使用Archive功能上傳。或者使用Xcode爲每一種虛擬產品創建一個.pkg文件,然後使用Application Loader一次性上傳。
具體細節請參考Using Application Loader中和In-App Purchase有關的章節。
關於和iTunes Connect的交互,更多細節請參考In-App Purchase Configuration Guide for iTunes Connect。
5. 代碼裏該做的事情
獲取產品列表
- 首先讀取出App中內嵌的或是服務端中的Product IDs。
- 使用SKProductRequest向蘋果服務器驗證哪些Product IDs是可用的。注意不要在[Class load]裏發送請求,一定要等到App didFinishLauching之後再發,不然無法接到請求返回。核心代碼如下:
1 2 3 4 5 6 7 |
|
接收結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
向自己的服務器生成訂單
如果需要經過自己的服務器做二次驗證,建議在調用蘋果支付接口前做這一步。
訂單中必須要保存的是訂單ID和用戶想要購買的商品ID。這個記錄是爲了在二次驗證時服務端做檢查,防止 A 商品的 receipt 被用戶拿來做 B 商品的購買結果校驗。
發送購買請求
1 2 3 4 5 6 |
|
或者
1 2 3 4 5 |
|
觀察購買狀態
首先在程序啓動時註冊觀察者
1 2 3 |
|
並且實現回調,處理相應的購買返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
需要監聽SKPaymentQueue的更多狀態變更,請實現SKPaymentTransactionObserver協議中提供的更多方法。
完成購買
在收到Purchased或Restored回調後,持久化購買記錄以及receipt data。iOS6.X 或之前的版本中,持久化 receipt data 必須萬無一失,因爲一旦丟失,將沒有任何途徑再次拿到此 receipt,造成用戶購買記錄丟失。獲取 receipt data 需要注意的點將在後面的二次驗證中詳細說。
然後通知PaymentQueue,購買已經完成了。對finishTransaction則會觸發系統IAP的UI刷新:
1 2 |
|
另外在發放功能或道具之前,最好在自己服務端做一次二次校驗,防止越獄插件或者Wifi的HTTP代理僞造購買記錄。
二次驗證防止破解
越獄插件或者HTTP代理均可讓用戶做到僞造購買記錄。當我們收到購買完成的回調後,最好經過自己服務器驗證購買是否合法。
經過 App Store 驗證
以下代碼用Cocoa實現了二次驗證的過程。但是這個過程最好通過自己的後臺服務器來做,不然非常容易在客戶端被僞造返回結果。
這裏使用Cocoa實現只是爲了闡述請求與返回值的格式。發送二次驗證請求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
需要注意的是,如果 App 不需要支持 iOS6.x 及之前的版本,建議僅使用 appStoreReceiptURL 獲取 receipt 數據。appStoreReceiptURL 從 iOS7 開始啓用,會返回用戶在此 app 上購買過的全部 receipt 的 data(粒度猜測應該是本機,本 App Store 帳號,本 App 內的購買,具體沒測試)。
接收二次驗證結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
蘋果的返回值如下:
使用transaction.transactionReceipt取得的小票經二次驗證後返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
使用[[NSBundle mainBundle] appStoreReceiptURL]取得的小票經二次驗證後返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
純本地驗證
除了網絡驗證以外,蘋果提供了純粹的本地驗證方式:Validating Receipts Locally.
Receipt data 經過 App Store 證書籤名,所以第三方無法憑空生成能夠通過此法驗證的 receipt data。只要做好證書校驗,無需擔心用戶會僞造 receipt data。
在客戶端使用這種方式可以做到防止被通用破解方式破解,但並不能防止針對特定 App 的破解。
實際上,這種驗證方式是蘋果爲服務端設計的。Receipt data 的格式遵守ASN.1格式,服務端安裝asn1c就可以解析 receipt data,並不需要純手寫一份解析代碼。只要服務端代碼和 asn1c 不出 bug,在服務端使用這種方式驗證就是安全的。
第三方網站驗證
有些第三方網站提供了經服務端的驗證服務。比如urbanairship. 但是我並沒有用過,所以不知道具體效果如何。畢竟第三方服務無法做到在用戶發起購買之前生成訂單記錄,與購買後驗證結果比對,所以我還是比較擔心第三方驗證服務的安全性的。而且雞國網絡連國外驗證服務器,你懂的。。
總之想要萬無一失,建議開發自己的驗證接口。
更多驗證相關問題,請參考Receipt Validation Programming Guide
大多數產品在驗證成功後,纔是真正的發放內容、道具等。特別是充值後立即消費的虛擬貨幣基本都是這麼處理的。
但是從接口來看, IAP 的設計者是想讓開發者在購買完成時發放內容、道具,在二次驗證失敗時以刪除內容、道具等方式來進行處罰。
6.服務端二次驗證後再發放數據中的安全問題
由於是和錢關係最緊密的功能,IAP安全性顯得無比重要。
客戶端數據安全
客戶端根據使用不同的獲取 receipt 的接口,需要做的事情也不同:
1. 客戶端使用transaction.transactionReceipt(或使用transaction.transactionReceipt + appStoreReceiptURL兩者):
如果要支持 iOS6,那麼不得不使用transaction.transactionReceipt在 iOS6上讀取receipt,客戶端需要保證持久化邏輯:
在transaction完成後,和服務端的二次驗證完成前,要對receipt data做持久化;若存在未上傳成功的 receipt ,需要開定時器重試上傳;如果要做刪除持久化數據,刪除的時機應當是收到從服務端發回的二次驗證請求的響應時,確認服務端已和蘋果完成通信之後(服務端返回和蘋果連接失敗則不應刪除已保存的receiptData),若 IAP 交易不頻繁,可考慮不刪除持久化的 receipt data。
2. 客戶端僅使用appStoreReceiptURL獲取receipt
客戶端每次交易完成從 appStoreReceiptURL 讀取出 data 上傳給服務器。同時有上傳請求正在進行的話,持久化一個標記,表示有未完成的上傳,在全部上傳完成後將此標記置爲上傳結束。如果標記存在,需要開定時器重試上傳。
服務端數據安全
服務端安全包含兩部分:防止已扣款卻購買無效;防止作弊。目前想到最圓的實踐是:
1. 在從客戶端上傳的 receipt data 中解析出的數據(包括本地解析或經蘋果服務器解析)中,從全部交易記錄中找到所有未被標記爲已使用的交易記錄,作爲集合A。
2. 枚舉當前用戶的全部待支付訂單,匹配集合A中的交易記錄中product id相同的項,並將此交易記錄的 transaction id 記錄下,標記爲已使用;並將此訂單標記爲已支付,併發放道具。
3. 線上環境僅在審覈期間允許使用sandbox環境做二次驗證,防止上線後內部同事使用sandbox test user免費充值道具到線上環境。需注意審覈期間必須開啓 sandbox 環境驗證,不然會被 rejected。
其他安全問題
- 儘量使用純 HTTPS 接口上傳 receipt,並嚴格校驗 SSL 證書,防止中間人攻擊。
- 越獄後有木馬會盜取用戶的支付,如果必要,需要提醒用戶越獄風險。
7.切換線上/測試環境
需要在代碼裏顯示聲明的環境,就只有二次驗證地址:
1 2 |
|
而在Cocoa中調用蘋果接口時,開發證書、Beta測試證書、提交證書編譯出的App連接的都是Sandbox環境;只有上線後的App連接的是線上環境。
另外再次強調,除非少量必要的自己線上環境的測試需要連接蘋果的 Sandbox 驗證服務之外,自己服務端的二次驗證 API 應該嚴格做到自己的環境是線上環境,則連接蘋果的線上環境二次驗證接口。防止監守自盜的情況出現。
8.提交審覈
如果是初次提交審覈,IAP 商品要和第一個支持 IAP 的版本一起提交。審覈期間要允許 sandbox 環境二次驗證。
後續新增的 IAP 商品則沒有此限制,可以隨時提交審覈。
Over