問題描述
最近公司發現公司發現有人通過蘋果內購充值,實際上蘋果後臺查詢充值記錄並沒有相關記錄,初步判斷可能內購流程出現了問題進行排查。
蘋果內購流程圖
通用流程梳理
- IOS SDK 請求服務器,創建訂單;
- 服務器生成訂單,並返回訂單號;
- 發起支付,如果沒有登陸會要求用戶登陸appleID,如果已經登陸,會彈出購買確認;
- 點擊購買,等待蘋果返回支付結果;
- 如果支付成功,蘋果會返回receipt-data 數據,與訂單號一起發到服務器進行校驗;
- 接收到IOS SDK參數進行校驗,成功後請求APPLE 服務校驗支付結果;
- 驗證返回結果返回進行業務操作,並返回IOS SDK最終支付結果;
問題原因
蘋果服務器再返回給我們服務器訂單結果。receipt_data 在越獄環境下是可以被插件僞造的,後臺向蘋果驗證時,居然還能驗證通過。下面是幾種receipt_data 請求APPLE 服務器返回的結果
正常返回JSON格式爲:
第一種
{
"status": 0,
"environment": "Sandbox",
"receipt": {
"receipt_type": "ProductionSandbox",
"adam_id": 0,
"app_item_id": 0,
"bundle_id": "com.xxx.xxx",
"application_version": "84",
"download_id": 0,
"version_external_identifier": 0,
"receipt_creation_date": "2016-12-05 08:41:57 Etc/GMT",
"receipt_creation_date_ms": "1480927317000",
"receipt_creation_date_pst": "2016-12-05 00:41:57 America/Los_Angeles",
"request_date": "2016-12-05 08:41:59 Etc/GMT",
"request_date_ms": "1480927319441",
"request_date_pst": "2016-12-05 00:41:59 America/Los_Angeles",
"original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
"original_purchase_date_ms": "1375340400000",
"original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
"original_application_version": "1.0",
"in_app": [
{
"quantity": "1",
"product_id": "*******【支付定義產品ID】*******",
"transaction_id": "10000003970",
"original_transaction_id": "10000003970",
"purchase_date": "2016-12-05 08:41:57 Etc/GMT",
"purchase_date_ms": "1480927317000",
"purchase_date_pst": "2016-12-05 00:41:57 America/Los_Angeles",
"original_purchase_date": "2016-12-05 08:41:57 Etc/GMT",
"original_purchase_date_ms": "1480927317000",
"original_purchase_date_pst": "2016-12-05 00:41:57 America/Los_Angeles",
"is_trial_period": "false"
}
]
}
}
這裏邊in_app 可能會出現多條數據情況,每當有一筆交易發起的時候,in_app裏就會添加收據的一些信息。這些信息會一直保存直到你結束這筆交易。在此之後,下次更新收據時會將其從收據中刪除 - 例如,當用戶再次購買時,或者您的應用明確刷新收據時。
非消耗型項目,自動續期訂閱,非續期訂閱或免費訂閱的應用內購買收據將無限期保留在收據中。
第二種
{
"receipt": {
"original_purchase_date_pst": "2016-12-03 01:11:01 America/Los_Angeles",
"purchase_date_ms": "1480756261254",
"unique_identifier": "96f51b28f628493709966f33a1fe7ba",
"original_transaction_id": "1000000255766",
"bvrs": "82",
"transaction_id": "1000000255766",
"quantity": "1",
"unique_vendor_identifier": "FE358-1362-40FD-870F-DF788AC5",
"item_id": "11822945",
"product_id": ""*******【支付定義產品ID】*******"",
"purchase_date": "2016-12-03 09:11:01 Etc/GMT",
"original_purchase_date": "2016-12-03 09:11:01 Etc/GMT",
"purchase_date_pst": "2016-12-03 01:11:01 America/Los_Angeles",
"bid": "com.xxx.xxx",
"original_purchase_date_ms": "1480756261254"
},
"status": 0
}
查看資料說是在IOS7以前版本支付和現在版本支付驗證返回不用的數據結構。
越獄訂單receipt_data向蘋果服務器校驗後如下:
{
"status": 0,
"environment": "Production",
"receipt": {
"receipt_type": "Production",
"adam_id": 1377028992,
"app_item_id": 1377028992,
"bundle_id": "com.xxx.xxx",
"application_version": "3",
"download_id": 80042231041057,
"version_external_identifier": 827853261,
"receipt_creation_date": "2018-07-23 07:30:45 Etc/GMT",
"receipt_creation_date_ms": "1532331045000",
"receipt_creation_date_pst": "2018-07-23 00:30:45 America/Los_Angeles",
"request_date": "2018-07-23 07:33:54 Etc/GMT",
"request_date_ms": "1532331234485",
"request_date_pst": "2018-07-23 00:33:54 America/Los_Angeles",
"original_purchase_date": "2018-07-01 12:16:21 Etc/GMT",
"original_purchase_date_ms": "1530447381000",
"original_purchase_date_pst": "2018-07-01 05:16:21 America/Los_Angeles",
"original_application_version": "3",
"in_app": [ ]
}
}
解決方法
將校驗邏輯進行修改,不能只校驗status=0
。
- 首先客戶端傳參數需要增加:product_id,transaction_id
//該方法爲監聽內購交易結果的回調
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
transactions 爲一個數組 遍歷就可以得到 SKPaymentTransaction 對象的元素transaction。然後從transaction裏可以取到以下這兩個個參數,product_id,transaction_id。另外從沙盒裏取到票據信息receipt_data
我們先看怎麼取到以上的三個參數
//獲取receipt_data
NSData *data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
NSString * receipt_data = [data base64EncodedStringWithOptions:0];
//獲取product_id
NSString *product_id = transaction.payment.productIdentifier;
//獲取transaction_id
NSString * transaction_id = transaction.transactionIdentifier;
這是我們必須要傳給服務器的三個字段。以上三個字段需要做好空值校驗,避免崩潰。
下面我們來解釋一下,爲什麼要給服務器傳這三個參數。
receipt_data:這個不解釋了 大家都懂 不傳的話 服務器根本沒法校驗
product_id:這個也不用解釋 內購產品編號 你不傳的話 服務器不知道你買的哪個訂單
transaction_id:這個是交易編號,是必須要傳的。因爲你要是防止越獄下內購被破解就必須要校驗in_app這個參數。而這個參數的數組元素有可能爲多個,你必須得找到一個唯一標示,纔可以區分訂單到底是那一筆。
- 服務器邏輯修改
- 先判斷是否重複分發內購產品。收到客戶端上報的transaction_id 後,直接MD5後去數據庫查,能查到說明是重複訂單,返回相應錯誤碼給客戶端,查不到去蘋果那邊校驗。
沙箱校驗地址 = “https://sandbox.itunes.apple.com/verifyReceipt”;
正式校驗地址 = “https://buy.itunes.apple.com/verifyReceipt”; - 服務器拿到蘋果校驗結果,首先判斷訂單狀態是否成功。
- 如果訂單成功在判斷格式爲IOS7 之前還是之後。
- 如果爲IOS7 之前版本直接判斷transaction_id、product_id;
- 如果爲IOS7 之後判斷in_app 是否有值,如果沒有直接返回失敗,如果存在遍歷查詢對應transaction_id並比較product_id;
- 以上校驗都正確就可以把訂單充值進去,給用戶分發內購產品。
- 先判斷是否重複分發內購產品。收到客戶端上報的transaction_id 後,直接MD5後去數據庫查,能查到說明是重複訂單,返回相應錯誤碼給客戶端,查不到去蘋果那邊校驗。
注:
後臺傳入參數一定要進行存儲。
解析
說明:in_app參數可能爲空,如果爲空得話,也需要將這筆交易認爲是有效的交易。當然我們肯定不能這麼幹,這個參數是必須必須要校驗的,不然越獄環境下,分分鐘就把你內購破解了。我去校驗了很多正常用戶的內購訂單,沒發現一個in_app參數是爲空的。但爲了保險,還是讓後臺把所有前端傳的receipt_data等參數不管成功失敗都保存下來,萬一哪個用戶因此投訴充值不到賬,我們有據可查。
參考:https://www.jianshu.com/p/5cf686e92924
參考:https://www.jianshu.com/p/7e7c3a918946utm_campaign=hugo&utm_medium=reader_share&utm_content=note&utm_source=qq