最近公司的app锡搜,提交appstore審核時橙困,被拒了,理由是:必須使用IAP接口支付耕餐,除了apple pay的用戶使用門檻要比第三方支付要高很多凡傅,而且iap接口,要跟apple公司三七分成肠缔,也就是用戶支付10元夏跷,蘋果要分掉你3元(服務費)。這跟國內(nèi)的第三方支付相比較明未,APPLE這種費率太TM的黑了槽华。
但是沒辦法,你要上架appstore趟妥,你只能使用IAP猫态,這TM就是壟斷。
首先要登錄APPLE開發(fā)者中心:https://itunesconnect.apple.com 設置協(xié)議披摄,稅務亲雪,和銀行賬戶信息。這部分網(wǎng)上都有教程行疏,按著教程來填匆光,雖然有點麻煩,但也不至于太難酿联。其中有一個步驟终息,是設置購買項目夺巩,需要設置:名稱,消費類型周崭,價格柳譬,ID。
例如:
名稱 類型 價格 ID
5幣 消耗型 1元 rjkf_itemid_1
15幣 消耗型 2元 rjkf_itemid_2
一般來說,我們的項目都包含和服務器端數(shù)據(jù)交互棠笑,所以大概的流程是這樣:
1僚害、客戶端首先需要顯示充值列表,實際上就是上面這個價目表制跟,我們需要提供一個價目表接口給客戶端,這個數(shù)據(jù)不建議在客戶端做死酱虎,因為如果我們經(jīng)常搞一些優(yōu)惠活動雨膨,或者調(diào)整價格,這樣在服務器端調(diào)整價目表就可以了读串,不需要更新客戶端版本聊记。
2、客戶端通過用戶選擇的充值金額恢暖,首先向服務器提交訂單排监,服務器生成相應的訂單。并將我們自己的訂單系統(tǒng)的訂單編號返回給客戶端杰捂。
3舆床、客戶端向apple發(fā)起訂單支付,如果支付完成嫁佳,這個時候apple服務器峭弟,會將訂單結果返回給客戶端,這里與支付寶或者微信支付有區(qū)別脱拼,支付寶或者微信支付瞒瘸,都是通過回調(diào)的方式,將訂單結果通知給我們提供的服務器端回調(diào)地址熄浓,進行訂單處理情臭。但是IAP是直接將支付結果,返回給客戶端赌蔑。
4俯在、客戶端收到APPLE返回的receipt-data
,然后和第二步生成的訂單號
娃惯,將信息提交給服務器端跷乐。服務器端收到receipt-data后,然后到apple server進行驗證趾浅。官方文檔地址:https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW3
apple server提供了兩個環(huán)境:
//sandbox 開發(fā)環(huán)境
https://sandbox.itunes.apple.com/verifyReceipt
//prod 生產(chǎn)環(huán)境
https://buy.itunes.apple.com/verifyReceipt
驗證接口參數(shù)如下:
需要注意的是:我們在提交app store審核時愕提,apple審核人員實際上是在sandbox環(huán)境進行支付測試的馒稍,所以,我們在審核時需要單獨處理浅侨。
驗證返回值如下:
首先纽谒,根據(jù)接口返回的status狀態(tài)碼,如果status=0
如输,表示支付成功鼓黔,如果是其他的表示有錯誤,比如上面的步驟中不见,在appstore審核時的處理澳化,我們可以先提交prod進行驗證,如果返回status=21007
稳吮,然后我們在提交sandbox驗證肆捕。
這里有一個地方需要注意,在我們開發(fā)調(diào)試過程中盖高,發(fā)現(xiàn)驗證結果會返回兩種數(shù)據(jù)格式的返回值:
1、數(shù)據(jù)格式1
{
"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": "rjkf_itemid_1",
"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
}
2眼虱、數(shù)據(jù)格式2
{
"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": "rjkf_itemid_1",
"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"
}
]
}
}
這特么也太坑了喻奥,返回的數(shù)據(jù)格式還會不一樣?查了一下資料捏悬,大概說是:ios7(這個數(shù)字可能不對撞蚕,印象中的,大概就是這么個意思)以前的版本支付和現(xiàn)在的版本支付过牙,去驗證時會返回不同的數(shù)據(jù)結構甥厦。WTF,那么作為服務器端寇钉,只能先麻煩點刀疙,先判斷結構中是否包含:in_app
,這部分扫倡,如果包含就用下面的結構解析谦秧,反之則用第一種結構來處理。如果status=0
撵溃,我們就可以根據(jù)客戶端提交上來的訂單號疚鲤,將訂單狀態(tài)進行變更了,并將驗證結果返回給客戶端缘挑。
所以集歇,我們這邊可以看到,apple的Receipts屬于那種不記名语淘,是由客戶端提交給服務器端诲宇,在由服務器端到apple server進行驗證际歼。所以,我們需要在第二步中焕窝,先由我們自己生成一個訂單蹬挺,驗證時,將訂單號也提交上來進行驗證它掂。但是巴帮,細想一下,好像還有漏洞虐秋,比如用戶創(chuàng)建2個訂單榕茧,訂單1,充值1元客给,訂單2用押,充值2000元,如果用戶將充值1元靶剑,返回的Receipts蜻拨,然后加上訂單2的編號來進行驗證,我們?nèi)pple server驗證桩引,status=0缎讼,說明票據(jù)是對的,然后根據(jù)訂單號查詢坑匠,充值2000元血崭,然后將這個訂單狀態(tài)更改為成功,那不就是用戶通過充值1元厘灼,騙了2000元的訂單么夹纫。
這個漏洞怎么破?我們看到apple返回的Receipts中设凹,有一個"product_id": "rjkf_itemid_1"舰讹,
這個是關鍵,因為這個是我們在蘋果創(chuàng)建的項目價格表的ID闪朱,每個ID都是對應一個金額跺涤,所以我們看到rjkf_itemid_1
是充值1元,特么的你給我的訂單是充值2000元监透,你丫的居然想騙錢桶错?這樣就防止了這個漏洞。
至此胀蛮,充值的步驟就處理完成了院刁,當然高手這么多,我擔心業(yè)務邏輯還有漏洞粪狼,希望你能告訴我退腥。
這個是我的處理邏輯任岸,當然因為有一些隱私性,將部分邏輯寫成了偽代碼狡刘,大家根據(jù)自己的實際情況進行處理享潜。
public ResultDto verify(String tradeNo, String receipt){
Recharge recharge = rechargeService.findByStoreTradeNo(tradeNo);
//訂單不存在
ICheck.notFound.check(!Objects.equal(null, recharge), e -> e.message("[W004]非法的回調(diào)請求"));
//訂單已經(jīng)成功,不處理
if(Objects.equal(recharge.getStatus(), TradeStatusType.TRADE_SUCCESS.name()) ||
Objects.equal(recharge.getStatus(), TradeStatusType.TRADE_FINISHED.name())){
return fail("訂單已經(jīng)成功");
}
//訂單已經(jīng)失敗嗅蔬,不處理
if(Objects.equal(recharge.getStatus(), TradeStatusType.TRADE_CLOSED.name())){
return fail("訂單已經(jīng)關閉");
}
//根據(jù)不同的環(huán)境剑按,選擇是去測試環(huán)境還是開發(fā)環(huán)境驗證
String verifyUri = VBConfig.isProd() ? VBConfig.getIapProdVerifyUri() : VBConfig.getIapSendboxVerifyUri();
JsonObject obj = new JsonObject();
obj.addProperty("receipt-data", receipt);
okhttp3.RequestBody body = okhttp3.RequestBody.create(MediaType.parse("application/json; charset=utf-8"), obj.toString());
Request request = new Request.Builder().url(verifyUri)
.post(body).build();
try {
Response response = new OkHttpClient().newCall(request).execute();
if (response.isSuccessful()) {
String result = response.body().string();
//解析json
IapVerifyDto iapVerifyDto = new Gson().fromJson(new JsonParser().parse(result), new TypeToken<IapVerifyDto>(){}.getType());
if(iapVerifyDto.status == 0){
//下面是偽代碼,說下處理思路
//判斷product_id澜术,看返回的product_id與實際的充值金額是不是一致艺蝴,防止騙單
//將訂單更改為成功
return ok();
}else if(iapVerifyDto.status == 21007){
//下面是偽代碼,說下處理思路
//驗證sandbox環(huán)境鸟废,主要就是為了appstore審核
}else{
//記錄錯誤日志
}
}else{
//記錄錯誤日志
}
} catch (JsonSyntaxException e) {
//記錄錯誤日志
} catch (IOException e) {
//記錄錯誤日志
}
return fail("支付失敗猜敢,請聯(lián)系客服");
}
?聲明:除非注明,本站所有文章皆為原創(chuàng)盒延,轉載請以鏈接形式標明本文地址缩擂。
?轉載請注明來源:https://www.rjkf.cn/apple-pay-iap/