2020年8月12日更新
關(guān)于文中余佛,蘋果用戶退款了也不知道是誰退的那塊表述,現(xiàn)在來看 是有誤的瑞驱。實(shí)際上從今年WWDC后冯键,蘋果就增加了一個(gè)Server To Server的回調(diào)通知,當(dāng)有用戶退款時(shí),會(huì)觸發(fā)該通知惑灵。非續(xù)期訂閱山上,消耗型,非消耗型均會(huì)收到退款通知英支。自動(dòng)續(xù)期類訂閱胶哲,蘋果之前就會(huì)有通知。
詳情可參考以下官方文檔:
蘋果退款回調(diào)
2019年5月8日更新
最近統(tǒng)計(jì)丟單率的時(shí)候潭辈,反查我們公司的訂單有時(shí)候會(huì)出現(xiàn)后臺(tái)的某個(gè)商品銷量居然比iTunes后臺(tái)的該商品銷量還高的現(xiàn)象鸯屿。排除時(shí)差因素和丟單自動(dòng)補(bǔ)的流程因素以外,發(fā)現(xiàn)是后臺(tái)校驗(yàn)訂單重復(fù)性的邏輯出現(xiàn)了問題把敢。
問題原因:
我們后臺(tái)之前的校驗(yàn)邏輯是對(duì)receipt_data 進(jìn)行MD5映射寄摆,然后每次服務(wù)器收到客戶端上報(bào)receipt_data的時(shí)候,先MD5修赞,然后在數(shù)據(jù)庫進(jìn)行排重對(duì)比婶恼。以上的這個(gè)校驗(yàn)邏輯是建立在相同訂單的receipt_data一定相同。但實(shí)際過程中柏副,我發(fā)現(xiàn)蘋果并不是這樣的勾邦,存在以下現(xiàn)象。
對(duì)于同一筆訂單割择,蘋果在極個(gè)別情況下會(huì)回調(diào)不一樣的receipt_data眷篇。所以用以上的排重校驗(yàn)邏輯,就存在有給客戶多發(fā)內(nèi)購商品的現(xiàn)象荔泳。
解決方案:
對(duì)蘋果返回的transaction_id進(jìn)行MD5映射或者直接保存在數(shù)據(jù)庫里蕉饼,排重用這個(gè)字段。這里有一點(diǎn)要注意的是如果直接保存到數(shù)據(jù)庫的話玛歌,數(shù)據(jù)庫類型不要用整型昧港,因?yàn)橛型蟹答佌f該字段可能會(huì)出現(xiàn)字符串。
博客上開頭講內(nèi)購大家通常的邏輯第⑥點(diǎn)時(shí)候是說的receipt_data用MD5映射做排重支子。但是后面的正文講服務(wù)器的校驗(yàn)邏輯那里說的是要用transaction_id做排重创肥。這個(gè)應(yīng)該是當(dāng)時(shí)沒注意。現(xiàn)在為了避免引起誤解值朋,我將排重方式都統(tǒng)一改成用transaction_id做排重叹侄。另外如果你們的后臺(tái)也是用receipt_data做排重,那就有問題了吞歼,需要盡快改成用transaction_id做排重圈膏。
---------------------------以下為正文---------------------------
iOS內(nèi)購開發(fā)大家一定不陌生塔猾,網(wǎng)上類似的文章能搜出千八百篇篙骡。大部分都是圍繞著如何實(shí)現(xiàn)?如何防止漏單丟單說明的。很少有提及到越獄的糯俗,即使偶爾有一兩篇說越獄尿褪,也是簡(jiǎn)單的三言兩語說 為了安全,我們直接屏蔽了越獄手機(jī)的內(nèi)購功能得湘。巴拉巴拉... 以前我也是這么想的杖玲,直到上個(gè)周末發(fā)現(xiàn)我們的內(nèi)購被xx了...才有了這篇文章。本篇文章就是來講述越獄下的內(nèi)購如何防止被xx淘正。
首先我們先簡(jiǎn)單理一下整個(gè)內(nèi)購的核心流程:
①客戶端發(fā)起支付訂單
②客戶端監(jiān)聽購買結(jié)果
③蘋果回調(diào)訂單購買成功時(shí)摆马,客戶端把蘋果給的receipt_data和一些訂單信息上報(bào)給服務(wù)器
④后臺(tái)服務(wù)器拿receipt_data向蘋果服務(wù)器校驗(yàn)
⑤蘋果服務(wù)器向返回status結(jié)果,含義如下鸿吆,其中為0時(shí)表示成功囤采。
21000 App Store無法讀取你提供的JSON數(shù)據(jù)
21002 收據(jù)數(shù)據(jù)不符合格式
21003 收據(jù)無法被驗(yàn)證
21004 你提供的共享密鑰和賬戶的共享密鑰不一致
21005 收據(jù)服務(wù)器當(dāng)前不可用
21006 收據(jù)是有效的,但訂閱服務(wù)已經(jīng)過期惩淳。當(dāng)收到這個(gè)信息時(shí)蕉毯,解碼后的收據(jù)信息也包含在返回內(nèi)容中
21007 收據(jù)信息是測(cè)試用(sandbox),但卻被發(fā)送到產(chǎn)品環(huán)境中驗(yàn)證
21008 收據(jù)信息是產(chǎn)品環(huán)境中使用思犁,但卻被發(fā)送到測(cè)試環(huán)境中驗(yàn)證
⑥服務(wù)器發(fā)現(xiàn)訂單校驗(yàn)成功后代虾,會(huì)把這筆訂單存起來,transaction_id用MD5值映射下激蹲,保存到數(shù)據(jù)庫棉磨,防止同一筆訂單,多次發(fā)放內(nèi)購商品学辱。
以上應(yīng)該是主流的校驗(yàn)流程含蓉。當(dāng)然客戶端其中會(huì)插一些丟單漏單的邏輯校驗(yàn),因?yàn)槟切└酒恼聼o關(guān)项郊,所以不在此展開馅扣。
從上面的流程可以看出,整個(gè)內(nèi)購的核心其實(shí)就是receipt_data着降。蘋果回調(diào)給客戶端差油,客戶端上報(bào)給服務(wù)器,服務(wù)器拿到后去向蘋果服務(wù)器校驗(yàn)任洞,蘋果服務(wù)器再返回給我們服務(wù)器訂單結(jié)果蓄喇。其實(shí)嚴(yán)格來說,整個(gè)流程是沒問題的交掏。整個(gè)的漏洞是在最后一步上妆偏,【蘋果服務(wù)器再返回給我們服務(wù)器訂單結(jié)果】。receipt_data在越獄環(huán)境下是可以被插件偽造的盅弛,后臺(tái)向蘋果驗(yàn)證時(shí)钱骂,居然還能驗(yàn)證通過叔锐。是的,你沒看錯(cuò)见秽,蘋果這里有個(gè)賊雞兒坑的地方愉烙。這是最坑最坑的地方,偽造的receipt_data蘋果校驗(yàn)也返回支付成功
如何解決解取?我們先來看下越獄訂單和正常訂單對(duì)比
越獄訂單receipt_data向蘋果服務(wù)器校驗(yàn)后如下:
{
"status": 0,
"environment": "Production",
"receipt": {
"receipt_type": "Production",
"adam_id": 1377028992,
"app_item_id": 1377028992,
"bundle_id": "*******【敏感信息不給看】*******",
"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": [ ]
}
}
正常訂單receipt_data向蘋果服務(wù)器校驗(yàn)后如下:
{
{
"status": 0,
"environment": "Production",
"receipt": {
"receipt_type": "Production",
"adam_id": 1377028992,
"app_item_id": 1377028992,
"bundle_id": "*******【敏感信息不給看】*******",
"application_version": "3",
"download_id": 36042096097927,
"version_external_identifier": 827703432,
"receipt_creation_date": "2018-07-10 13:54:27 Etc/GMT",
"receipt_creation_date_ms": "1531230867000",
"receipt_creation_date_pst": "2018-07-10 06:54:27 America/Los_Angeles",
"request_date": "2018-07-23 08:03:27 Etc/GMT",
"request_date_ms": "1532333007664",
"request_date_pst": "2018-07-23 01:03:27 America/Los_Angeles",
"original_purchase_date": "2018-06-13 06:52:13 Etc/GMT",
"original_purchase_date_ms": "1528872733000",
"original_purchase_date_pst": "2018-06-12 23:52:13 America/Los_Angeles",
"original_application_version": "5",
"in_app": [
{
"quantity": "1",
"product_id": "*******【敏感信息不給看】*******",
"transaction_id": "160000477610856",
"original_transaction_id": "160000477610856",
"purchase_date": "2018-07-10 13:54:27 Etc/GMT",
"purchase_date_ms": "1531230867000",
"purchase_date_pst": "2018-07-10 06:54:27 America/Los_Angeles",
"original_purchase_date": "2018-07-10 13:54:27 Etc/GMT",
"original_purchase_date_ms": "1531230867000",
"original_purchase_date_pst": "2018-07-10 06:54:27 America/Los_Angeles",
"is_trial_period": "false"
}
]
}
}
看完兩筆訂單的對(duì)比我相信大家可以清楚的知道步责,越獄訂單雖然狀態(tài)返回是成功的,但是in_app這個(gè)參數(shù)是空的禀苦。大概查了一下蔓肯。iOS7以下是沒有這個(gè)in_app參數(shù)的,iOS7以上是有的振乏。因?yàn)楝F(xiàn)在App基本支持的起步都是iOS8 iOS9了省核,iOS7可以不用管了。但這里還有一個(gè)問題昆码,就是in_app這個(gè)字段并不總是只返回一個(gè)气忠,有可能會(huì)返回多個(gè),比如下面的這種訂單赋咽。
正常訂單receipt_data校驗(yàn)后 in_app多個(gè)元素時(shí):
{
"status":0,
"environment":"Sandbox",
"receipt":{
"receipt_type":"ProductionSandbox",
"adam_id":0,
"app_item_id":0,
"bundle_id":"*******【敏感信息不給看】*******",
"application_version":"1",
"download_id":0,
"version_external_identifier":0,
"receipt_creation_date":"2018-07-24 04:28:24 Etc/GMT",
"receipt_creation_date_ms":"1532406504000",
"receipt_creation_date_pst":"2018-07-23 21:28:24 America/Los_Angeles",
"request_date":"2018-07-24 04:30:06 Etc/GMT",
"request_date_ms":"1532406606695",
"request_date_pst":"2018-07-23 21:30:06 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":"*******【敏感信息不給看】*******",
"transaction_id":"1000000398911598",
"original_transaction_id":"1000000398911598",
"purchase_date":"2018-05-16 03:26:12 Etc/GMT",
"purchase_date_ms":"1526441172000",
"purchase_date_pst":"2018-05-15 20:26:12 America/Los_Angeles",
"original_purchase_date":"2018-05-16 03:26:12 Etc/GMT",
"original_purchase_date_ms":"1526441172000",
"original_purchase_date_pst":"2018-05-15 20:26:12 America/Los_Angeles",
"is_trial_period":"false"
},
{
"quantity":"1",
"product_id":"*******【敏感信息不給看】*******",
"transaction_id":"1000000398911640",
"original_transaction_id":"1000000398911640",
"purchase_date":"2018-05-16 03:26:37 Etc/GMT",
"purchase_date_ms":"1526441197000",
"purchase_date_pst":"2018-05-15 20:26:37 America/Los_Angeles",
"original_purchase_date":"2018-05-16 03:26:37 Etc/GMT",
"original_purchase_date_ms":"1526441197000",
"original_purchase_date_pst":"2018-05-15 20:26:37 America/Los_Angeles",
"is_trial_period":"false"
},
{
"quantity":"1",
"product_id":"*******【敏感信息不給看】*******",
"transaction_id":"1000000398911784",
"original_transaction_id":"1000000398911784",
"purchase_date":"2018-05-16 03:26:50 Etc/GMT",
"purchase_date_ms":"1526441210000",
"purchase_date_pst":"2018-05-15 20:26:50 America/Los_Angeles",
"original_purchase_date":"2018-05-16 03:26:50 Etc/GMT",
"original_purchase_date_ms":"1526441210000",
"original_purchase_date_pst":"2018-05-15 20:26:50 America/Los_Angeles",
"is_trial_period":"false"
},
{
"quantity":"1",
"product_id":"*******【敏感信息不給看】*******",
"transaction_id":"1000000398911801",
"original_transaction_id":"1000000398911801",
"purchase_date":"2018-05-16 03:27:22 Etc/GMT",
"purchase_date_ms":"1526441242000",
"purchase_date_pst":"2018-05-15 20:27:22 America/Los_Angeles",
"original_purchase_date":"2018-05-16 03:27:22 Etc/GMT",
"original_purchase_date_ms":"1526441242000",
"original_purchase_date_pst":"2018-05-15 20:27:22 America/Los_Angeles",
"is_trial_period":"false"
},
{
"quantity":"1",
"product_id":"*******【敏感信息不給看】*******",
"transaction_id":"1000000399060767",
"original_transaction_id":"1000000399060767",
"purchase_date":"2018-05-16 11:10:45 Etc/GMT",
"purchase_date_ms":"1526469045000",
"purchase_date_pst":"2018-05-16 04:10:45 America/Los_Angeles",
"original_purchase_date":"2018-05-16 11:10:45 Etc/GMT",
"original_purchase_date_ms":"1526469045000",
"original_purchase_date_pst":"2018-05-16 04:10:45 America/Los_Angeles",
"is_trial_period":"false"
},
{
"quantity":"1",
"product_id":"*******【敏感信息不給看】*******",
"transaction_id":"1000000399061778",
"original_transaction_id":"1000000399061778",
"purchase_date":"2018-05-16 11:14:52 Etc/GMT",
"purchase_date_ms":"1526469292000",
"purchase_date_pst":"2018-05-16 04:14:52 America/Los_Angeles",
"original_purchase_date":"2018-05-16 11:14:52 Etc/GMT",
"original_purchase_date_ms":"1526469292000",
"original_purchase_date_pst":"2018-05-16 04:14:52 America/Los_Angeles",
"is_trial_period":"false"
},
...
]
}
}
綜上旧噪,整個(gè)服務(wù)器那邊校驗(yàn)邏輯應(yīng)該是這樣的。
首先客戶端必須要給服務(wù)器傳的三個(gè)參數(shù):receipt_data脓匿, product_id 淘钟,transaction_id
//該方法為監(jiān)聽內(nèi)購交易結(jié)果的回調(diào)
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
transactions 為一個(gè)數(shù)組 遍歷就可以得到 SKPaymentTransaction 對(duì)象的元素transaction。然后從transaction里可以取到以下這兩個(gè)個(gè)參數(shù)陪毡,product_id米母,transaction_id。另外從沙盒里取到票據(jù)信息receipt_data
我們先看怎么取到以上的三個(gè)參數(shù)
//獲取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;
這是我們必須要傳給服務(wù)器的三個(gè)字段毡琉。以上三個(gè)字段需要做好空值校驗(yàn)铁瞒,避免崩潰。
下面我們來解釋一下桅滋,為什么要給服務(wù)器傳這三個(gè)參數(shù)慧耍。
receipt_data:這個(gè)不解釋了 大家都懂 不傳的話 服務(wù)器根本沒法校驗(yàn)
product_id:這個(gè)也不用解釋 內(nèi)購產(chǎn)品編號(hào) 你不傳的話 服務(wù)器不知道你買的哪個(gè)訂單
transaction_id:這個(gè)是交易編號(hào),是必須要傳的丐谋。因?yàn)槟阋欠乐乖姜z下內(nèi)購被xx就必須要校驗(yàn)in_app這個(gè)參數(shù)芍碧。而這個(gè)參數(shù)的數(shù)組元素有可能為多個(gè),你必須得找到一個(gè)唯一標(biāo)示号俐,才可以區(qū)分訂單到底是那一筆泌豆。
所以服務(wù)器那邊邏輯就很清晰了。
①先判重吏饿,避免重復(fù)分發(fā)內(nèi)購商品踪危。收到客戶端上報(bào)的transaction_id后蔬浙,直接MD5后去數(shù)據(jù)庫查,能查到說明是重復(fù)訂單陨倡,返回相應(yīng)錯(cuò)誤碼給客戶端敛滋,如果查不到许布,去蘋果那邊校驗(yàn)兴革。
沙箱校驗(yàn)地址 = "https://sandbox.itunes.apple.com/verifyReceipt";
正式校驗(yàn)地址 = "https://buy.itunes.apple.com/verifyReceipt";
②服務(wù)器拿到蘋果的校驗(yàn)結(jié)果后,首先判斷訂單狀態(tài)是不是成功蜜唾。
③如果訂單狀態(tài)成功在判斷in_app這個(gè)字段有沒有杂曲,沒有直接就返回失敗了。如果存在的話袁余,遍歷整個(gè)數(shù)組擎勘,通過客戶端給的transaction_id 來比較,取到相同的訂單時(shí)颖榜,對(duì)比一下bundle_id 棚饵,product_id 是不是正確的。
如果以上校驗(yàn)都正確就把這筆訂單充值進(jìn)去掩完,給用戶分發(fā)內(nèi)購商品噪漾。
注意:一定要告訴后臺(tái),不論校驗(yàn)是否成功且蓬,只要客戶端給服務(wù)器傳了receipt_data等參數(shù)就一定要保存到數(shù)據(jù)庫里欣硼。【下面會(huì)解釋為什么】
以上的校驗(yàn)步驟恶阴,可以有效的防止內(nèi)購xx诈胜,下面內(nèi)容是我看蘋果官方能文檔的關(guān)于in_app這個(gè)參數(shù)說明和解釋下為啥服務(wù)器必須要保存每一個(gè)不同的receipt_data。
In the JSON file, the value of this key is an array containing all in-app purchase receipts based on the in-app purchase transactions present in the input base-64 receipt-data. For receipts containing auto-renewable subscriptions, check the value of the latest_receipt_info key to get the status of the most recent renewal.
大概意思是說:
在這個(gè)JSON文件中冯事,這個(gè)鍵的值是一個(gè)數(shù)組焦匈,該數(shù)組包含基于base-64后的所有內(nèi)購收據(jù)。如果你的內(nèi)購類型是自動(dòng)更新訂閱昵仅,那么請(qǐng)通過檢查latest_receipt_info鍵的值括授,來確定最近更新的狀態(tài)。
很有意思的是岩饼,蘋果還特別標(biāo)明了這么一句話:
Note: An empty array is a valid receipt.
也就是說這個(gè)in_app參數(shù)可能為空荚虚,如果為空的話,也需要把這筆交易認(rèn)為是有效的交易籍茧。這是蘋果建議的操作版述。當(dāng)然我們肯定不能這么干,這個(gè)參數(shù)是必須必須要校驗(yàn)的寞冯,不然越獄環(huán)境下渴析,分分鐘就把你內(nèi)購xx了晚伙。我去校驗(yàn)了很多正常用戶的內(nèi)購訂單,沒發(fā)現(xiàn)一個(gè)in_app參數(shù)是為空的俭茧。但為了保險(xiǎn)咆疗,還是讓后臺(tái)把所有前端傳的receipt_data等參數(shù)不管成功失敗都保存下來,萬一哪個(gè)用戶因此投訴充值不到賬母债,我們有據(jù)可查午磁。
下面兩段話
The in-app purchase receipt for a consumable product is added to the receipt when the purchase is made. It is kept in the receipt until your app finishes that transaction. After that point, it is removed from the receipt the next time the receipt is updated - for example, when the user makes another purchase or if your app explicitly refreshes the receipt.
The in-app purchase receipt for a non-consumable product, auto-renewable subscription, non-renewing subscription, or free subscription remains in the receipt indefinitely.
大概意思是說:
每當(dāng)有一筆交易發(fā)起的時(shí)候,in_app里就會(huì)添加收據(jù)的一些信息毡们。這些信息會(huì)一直保存直到你結(jié)束這筆交易迅皇。在此之后,下次更新收據(jù)時(shí)會(huì)將其從收據(jù)中刪除 - 例如衙熔,當(dāng)用戶再次購買時(shí)登颓,或者您的應(yīng)用明確刷新收據(jù)時(shí)。
非消耗型項(xiàng)目红氯,自動(dòng)續(xù)期訂閱框咙,非續(xù)期訂閱或免費(fèi)訂閱的應(yīng)用內(nèi)購買收據(jù)將無限期保留在收據(jù)中。
這一點(diǎn)也解釋了說痢甘,為什么in_app這個(gè)數(shù)組有時(shí)候會(huì)有多個(gè)元素喇嘱。
下面在舉幾個(gè)大家做內(nèi)購經(jīng)常遇到的一些問題,和一些容易混淆的點(diǎn)产阱。
Q1:內(nèi)購和Apple Pay的區(qū)別婉称?
A1:內(nèi)購是內(nèi)購,Apple Pay是Apple Pay构蹬。我不知道有多少人第一次接觸時(shí)王暗,會(huì)把這倆概念混淆掉,這里你可以簡(jiǎn)單這么理解庄敛,虛擬的物品就是用內(nèi)購俗壹,實(shí)際的物品就是用Apple Pay。Apple Pay是一種支付方式藻烤,你可以類比為支付寶绷雏,微信那種。但人家只支持實(shí)際物品怖亭,如果你東西是虛擬的話涎显,你卻集成Apple Pay上架是要被拒絕的哦~當(dāng)然反過來,實(shí)際物品你卻集成內(nèi)購上架兴猩,也是一樣被拒期吓。對(duì)于大部分的國內(nèi)開發(fā)者而言,你很少會(huì)遇到需要集成Apple Pay的App的倾芝。能用支付寶/微信的場(chǎng)景還要求支持Apple Pay的產(chǎn)品畢竟是少數(shù)讨勤。
Q2:內(nèi)購項(xiàng)目的類型區(qū)別箭跳?
A2:首先內(nèi)購項(xiàng)目分為以下4種,消耗型項(xiàng)目潭千,非消耗型項(xiàng)目谱姓,自動(dòng)續(xù)期訂閱,非續(xù)期訂閱刨晴。我們來一個(gè)個(gè)介紹屉来。
消耗型項(xiàng)目:只可使用一次的產(chǎn)品,使用之后即失效割捅,必須再次購買奶躯。就是大家最廣為所知的虛擬幣帚桩,比如直播平臺(tái)斗魚的魚翅亿驾,熊貓的竹子,嗶哩嗶哩的B幣等账嚎,這個(gè)概念大家應(yīng)該很好理解莫瞬,不過多解釋了。
非消耗型項(xiàng)目:只需購買一次郭蕉,不會(huì)過期或隨著使用而減少的產(chǎn)品疼邀。這個(gè)一般是游戲那里用的多,一般是付費(fèi)解鎖關(guān)卡的場(chǎng)景召锈,用戶買過一次旁振,卸載重裝或者同一個(gè)Apple id但換App賬號(hào)時(shí),也要能保證用戶重新獲得該內(nèi)購商品涨岁。所以App內(nèi)部需要額外去實(shí)現(xiàn)恢復(fù)購買的邏輯拐袜。
自動(dòng)續(xù)期訂閱:允許用戶在固定時(shí)間段內(nèi)購買動(dòng)態(tài)內(nèi)容的產(chǎn)品。除非用戶選擇取消梢薪,否則此類訂閱會(huì)自動(dòng)續(xù)期蹬铺。iTunces上給的示例是:每月訂閱提供流媒體服務(wù)的 App。對(duì)比我們熟悉的秉撇,網(wǎng)易云音樂的內(nèi)購商品-連續(xù)包月黑膠VIP甜攀,就是此類型。一般來說琐馆,沒啥必要不要選這一種规阀,如果是VIP的那種場(chǎng)景推薦下面非續(xù)期訂閱類型去做。自動(dòng)續(xù)期訂閱的坑非常多瘦麸,比另外幾種內(nèi)購類型都要復(fù)制谁撼。
非續(xù)期訂閱:一般來說VIP可以用這種方法來做訂閱,我們公司項(xiàng)目的VIP購買就是這種方式瞎暑。他的實(shí)現(xiàn)方式你可以完全照搬消耗性項(xiàng)目彤敛,不用做什么額外處理与帆,也不用去管返回的訂閱日期什么的東西,就是以服務(wù)器那邊為準(zhǔn)墨榄。服務(wù)器的日期開始玄糟,服務(wù)器的日期結(jié)束。既簡(jiǎn)單又保險(xiǎn)袄秩,不需要額外的做什么處理阵翎。
Q3:VIP一定要用內(nèi)購做嗎?
A3:其實(shí)判斷你們公司的App到底需不需要用內(nèi)購之剧,很簡(jiǎn)單郭卫,就是看跟實(shí)際物品有沒有關(guān)系。如果你的VIP功能是類似餓了么這種背稼,點(diǎn)外賣可以打折/多領(lǐng)紅包 那么就不需要用內(nèi)購贰军,上架的時(shí)候說清楚就行了。如果你的VIP功能是虛擬的蟹肘,比如頭像更炫酷词疼,尊貴的VIP身份標(biāo)示,獨(dú)特的入場(chǎng)動(dòng)畫等等虛擬相關(guān)的帘腹,比如QQ會(huì)員贰盗,就必須要用內(nèi)購去做。需要說明的是阳欲,那種是VIP才能和某某用戶聊天的場(chǎng)景舵盈,是VIP才能得到App里某某用戶的服務(wù)【語音,視頻】時(shí)球化,這一類的場(chǎng)景蘋果一樣認(rèn)為是虛擬的秽晚,一樣要用內(nèi)購去做。
Q4:VIP內(nèi)購一定是非續(xù)期/自動(dòng)續(xù)期訂閱嗎赊窥?我可不可以用虛擬幣購買VIP呢爆惧?
A4:這個(gè)問題我自己經(jīng)歷過。我的答案是你也可以用虛擬幣購買VIP的這種方式锨能,但如果被拒絕扯再,你只能老老實(shí)實(shí)的按前種方式去做。如果你們的App既有虛擬幣又有VIP址遇,產(chǎn)品希望你VIP是直接用虛擬幣去購買熄阻,這樣整個(gè)流程都很方便。那么你一定要記住倔约。千萬不要在1.0版本這么做秃殉,這是血淚教訓(xùn)。1.0版本會(huì)抓的很嚴(yán)很嚴(yán),同時(shí)虛擬幣+VIP功能钾军,百分百蘋果會(huì)要求你VIP要用續(xù)期訂閱去實(shí)現(xiàn)鳄袍。最保險(xiǎn)的做法呢,1.0版本不要做任何內(nèi)購吏恭,迭代幾個(gè)小版本后拗小,加入虛擬幣內(nèi)購,在迭代幾個(gè)小版本樱哼,加入VIP直接用虛擬幣購買的功能哀九,這是最最保險(xiǎn)的做法。記捉练:1.0的審核力度是真的很嚴(yán)扎谎,能先不做內(nèi)購就不要做內(nèi)購蜕该,老板或許不懂谋作,1.0版本什么都想要舀锨,但往往因?yàn)閮?nèi)購,會(huì)讓你們的產(chǎn)品反復(fù)被拒琢融。這一塊如果大家感興趣界牡,可以看看簿寂,我的1.0版本就是加了內(nèi)購漾抬,反復(fù)被拒5次。血淚教訓(xùn)
Q5:我們老板心疼那百分之30的手續(xù)費(fèi)常遂,我能不能不用內(nèi)購啊纳令,有沒辦法繞過內(nèi)購?
A5:辦法是有的克胳。但是有風(fēng)險(xiǎn)平绩。我16年做過繞開內(nèi)購的方法。思路很簡(jiǎn)單漠另,就是App里集成支付寶/微信/內(nèi)購這種功能捏雌,后臺(tái)做控制開關(guān),審核時(shí)笆搓,開關(guān)打開性湿,給審核人員看內(nèi)購功能,審核通過后满败,開關(guān)關(guān)閉肤频,給正常用戶是用支付寶/微信功能。這個(gè)方法算墨,我17年的時(shí)候聽到很多群友說不行了宵荒,你在上架審核時(shí),蘋果會(huì)掃描你的包,檢測(cè)到第三方支付sdk時(shí)报咳,會(huì)拒絕掉侠讯。后來又有群友說可以用H5的方式實(shí)現(xiàn)支付功能。另外可能會(huì)有別的繞開蘋果審核的實(shí)現(xiàn)方式暑刃,如果有哪位朋友知道继低,不妨留言告知。但不管是哪種方式稍走,都是有風(fēng)險(xiǎn)的袁翁,蘋果對(duì)內(nèi)購一直抓的很嚴(yán),如果讓它知道你們?cè)阱X的方面上欺騙過他婿脸,后果還是很嚴(yán)重的粱胜。iOS上的用戶付費(fèi)率還是很不錯(cuò)的,付費(fèi)意愿基本上可以是安卓用戶的十幾倍狐树。所以如果你們的產(chǎn)品真的有前景焙压,并且想長(zhǎng)久做下去,還是奉勸不要做欺騙蘋果的事情了抑钟。
Q6:網(wǎng)上有好多講丟單的博客涯曲,看的是一臉懵逼,有的看懂后在塔,在看下一篇又不懂了幻件,感覺都好復(fù)雜。
A6:引起內(nèi)購丟單的主要操作其實(shí)是當(dāng)用戶點(diǎn)擊內(nèi)購商品時(shí)蛔溃,蘋果服務(wù)器太慢了绰沥,支付頁面一直不出來。結(jié)果用戶退出或者殺死App贺待,這時(shí)候在Home頁面徽曲,支付框又彈出來了,然后用戶點(diǎn)擊支付麸塞,成功后在打開App發(fā)現(xiàn)丟單秃臣。
一般這種只要你在Appdelegate的didFinishLaunchingWithOptions方法就開始對(duì)蘋果內(nèi)購回調(diào)做監(jiān)聽,然后把所有相關(guān)內(nèi)購的東西抽出來做一個(gè)單例即可解決丟單哪工。另外還有一些丟單可能是用applicationUsername做透?jìng)饕鸬陌麓耍@種解決辦法一般就是NSUserDefaults或者keychain或者在極端點(diǎn)NSUserDefaults+ keychain來做本地信息的記錄。關(guān)于丟單這一塊感興趣的可以自己搜貝聊解決丟單的那個(gè)博客【雖然他講的比較亂正勒,但思路可以借鑒下】得院。另外評(píng)論區(qū)下面有個(gè) 廣東深圳 的
網(wǎng)上博客還愛用那種切換賬號(hào)的場(chǎng)景舉例钾怔,A內(nèi)購成功了碱呼,但用戶各種騷操作后,自己換到B賬號(hào)宗侦,然后服務(wù)器那邊把商品發(fā)到B賬號(hào)上了愚臀,等等。
這些情況都是存在的凝垛,因?yàn)樘O果的內(nèi)購機(jī)制問題懊悯,你是不能百分百保證不丟單的,不要把丟單情況看的那么嚴(yán)重梦皮,邏輯寫的那么復(fù)雜。你看看所有大廠的App上都會(huì)寫充值遇到問題桃焕,點(diǎn)我聯(lián)系客服 巴拉巴拉剑肯。關(guān)于丟單,我的做法是這樣的观堂,在蘋果內(nèi)購成功的回調(diào)里让网,NSUserDefaults存每一筆支付成功的訂單,如果服務(wù)器校驗(yàn)成功师痕,就把本地存的這筆訂單刪除溃睹。如果沒收到服務(wù)器的響應(yīng),就一直保留胰坟。然后每次App啟動(dòng)就會(huì)去把本地存的丟單信息扔向服務(wù)器校驗(yàn)因篇,校驗(yàn)成功刪除,校驗(yàn)失敗不管。這里還是看開發(fā)時(shí)間竞滓,當(dāng)時(shí)我寫內(nèi)購功能的時(shí)候咐吼,預(yù)算時(shí)間就兩天不到,所以寫的飛快商佑,就簡(jiǎn)單的用這個(gè)辦法去防止丟單锯茄,目前來看,沒有發(fā)現(xiàn)過一筆真正用戶充錢但商品沒到賬的例子茶没。如果大家開發(fā)時(shí)間充足肌幽,可以慢慢去彌補(bǔ)極端操作漏洞。
Q7:內(nèi)購為什么會(huì)有這么多坑白グ搿牍颈?看網(wǎng)上好多博客都在說,我自己做微信/支付寶的時(shí)候琅关,沒感覺有這么多坑啊
A7:蘋果的內(nèi)購坑主要有以下幾點(diǎn)
applicationUsername該字段可能為nil 導(dǎo)致客戶端沒辦法用這個(gè)參數(shù)給服務(wù)器透?jìng)饔唵尉幪?hào)煮岁,來形成一個(gè)交易訂單號(hào)的綁定。
校驗(yàn)訂單流程是必須服務(wù)器主動(dòng)去詢問蘋果服務(wù)器涣易,而支付寶/微信 卻是他們的服務(wù)器會(huì)在用戶支付成功時(shí)主動(dòng)給我們服務(wù)器回調(diào)画机。正是這個(gè)原因,讓iOS開發(fā)者飽受折磨新症,大部分的丟單漏單都是蘋果的這個(gè)設(shè)計(jì)造成的步氏。蘋果不會(huì)主動(dòng)回調(diào)給我們服務(wù)器,也就意味著我們服務(wù)器需要主動(dòng)去蘋果那里詢問這筆訂單徒爹,到底成沒成功荚醒。但服務(wù)器詢問的時(shí)機(jī),又是客戶端告訴服務(wù)器的隆嗅。這就雞兒坑了界阁,一些情況下,用戶在付費(fèi)成功后胖喳,突然斷網(wǎng)了/崩潰了/出現(xiàn)意外了等等泡躯,客戶端沒辦法告訴服務(wù)器,這就出現(xiàn)了丽焊,用戶錢成功了较剃,內(nèi)購商品卻沒到賬。所以網(wǎng)上才會(huì)有這么多篇講防止丟單的博客技健。
越獄下写穴,插件也能xx掉蘋果內(nèi)購,然后校驗(yàn)狀態(tài)status還返回成功雌贱。也就是本篇博客開頭講的那種情況啊送。這一點(diǎn)真的是無力吐槽偿短,虧你特么回調(diào)給我的receipt_data那么一大長(zhǎng)串,有卵用删掀?
-
蘋果的訂單機(jī)制翔冀。蘋果為了保護(hù)用戶隱私,你是看不到一條條流水明細(xì)的披泪。你看到的只有??這種纤子。27FED3A0-C9F3-47C1-BA67-4EFAA7B2FCBA.png
每一種內(nèi)購類型的總收入,或者總銷量款票。導(dǎo)致對(duì)賬查詢的時(shí)候加了不少麻煩控硼。
-
蘋果的退款機(jī)制。這個(gè)比上面一點(diǎn)更坑艾少,iOS用戶卡乾,內(nèi)購了某商品,你可以在完全用完了后缚够,聯(lián)系蘋果客服幔妨,說我誤操作了巴拉巴拉或者說感覺這個(gè)商品不值那么多被開發(fā)者欺騙了巴拉巴拉,快給我退款谍椅,客服就會(huì)溫柔的告訴你误堡,不要急,她會(huì)幫你處理雏吭,1-2個(gè)工作日把锁施,你就會(huì)發(fā)現(xiàn)你的錢就退回來了。沒記錯(cuò)的話杖们,一段時(shí)間內(nèi)悉抵,一個(gè)Apple Id可以申請(qǐng)1-2次。但不能多摘完,多了的話就會(huì)被蘋果拒絕姥饰。而這一切,開發(fā)者這邊是完全不知情的描焰。你不知道哪個(gè)用戶退款了媳否,你知道的只是一個(gè)圖,類似下面的這種荆秦。0A762A89-3A96-4503-995D-028A96518958.png
用戶消費(fèi)了你的內(nèi)購商品,公司卻收不到錢力图,很多公司的內(nèi)購服務(wù)都是要成本的步绸。如果這種用戶一旦多起來,壞賬率會(huì)飆升吃媒,公司就會(huì)被活活的拖垮瓤介。一個(gè)好的項(xiàng)目也就涼涼掉吕喘。淘寶上關(guān)于iOS內(nèi)購?fù)丝顚iT有一個(gè)超級(jí)龐大的黑色產(chǎn)業(yè)鏈。從弄賬號(hào)到專門聯(lián)系蘋果客服再到道具銷贓變現(xiàn)刑桑,各司其職氯质,一環(huán)套一環(huán),每個(gè)環(huán)節(jié)人都賺的盆滿缽滿祠斧∥挪欤苦的都是公司,因?yàn)樘O果沒有任何損失琢锋,他也不會(huì)補(bǔ)償你公司1毛錢辕漂,一切損失都是公司自己承擔(dān)。沒記錯(cuò)的話吴超,15-16年钉嘹,很多很多游戲公司都是因?yàn)檫@個(gè)被活活拖垮的。幸運(yùn)的是鲸阻,這種惡意退款一般都是針對(duì)游戲公司跋涣,因?yàn)橛螒虻谰呖梢钥焖僮儸F(xiàn)。像正常的App甚少碰到鸟悴,因?yàn)樗丝盍艘矝]毛用陈辱,沒法及時(shí)變現(xiàn)。畢竟他們可不稀罕跟你們的女用戶1v1視頻聊天遣臼。
Q8:對(duì)于開發(fā)者來講性置,用通過內(nèi)購充值,那開發(fā)者到時(shí)候怎么得到這筆錢揍堰?
A8:做內(nèi)購的時(shí)候鹏浅,會(huì)填寫銀行稅務(wù)等等這些信息。蘋果會(huì)按期把錢打入到你們當(dāng)時(shí)填寫的銀行卡賬戶中屏歹。這里要注意隐砸,如果你當(dāng)月內(nèi)購收入很低,比如只有幾十美金蝙眶,那蘋果是不會(huì)給你打款的季希。具體的額度好像是以150美金分界限,當(dāng)你內(nèi)購收入超過150美金的時(shí)候幽纷,蘋果會(huì)下月給你打款式塌。如果你不夠150美金,那蘋果會(huì)累積到下個(gè)月打款友浸,如果下個(gè)月還不夠峰尝,那就會(huì)繼續(xù)累積。
這里注意兩點(diǎn):
- 150美金是個(gè)大概的數(shù)值收恢,我自己沒有確實(shí)求證過武学,我實(shí)際經(jīng)歷來說遇到的當(dāng)月收入都遠(yuǎn)超過這個(gè)數(shù)字祭往,所以很準(zhǔn)確的最低打款金額,我也不好評(píng)估火窒。
- 蘋果的打款日期硼补,并沒有嚴(yán)格的規(guī)律。上旬熏矿,中旬已骇,下旬打款我都遇到過,另外有時(shí)候曲掰,即便你當(dāng)月金額有很多疾捍,蘋果也可能下個(gè)月不給你打款,而是給你累積到下下個(gè)月栏妖。但至多不會(huì)超過兩個(gè)月以上乱豆,如果你遇到這種情況,需要及時(shí)和蘋果客戶溝通吊趾。
綜上:
只要你銀行卡宛裕,稅務(wù)等相關(guān)財(cái)務(wù)信息填寫正確,賬戶里收入超過150美金论泛,大多數(shù)情況下揩尸,下個(gè)月上旬就能收到蘋果的打款。