iOS內(nèi)購-防越獄刷單

2020年8月12日更新

關(guān)于文中,蘋果用戶退款了也不知道是誰退的那塊表述,現(xiàn)在來看 是有誤的。實(shí)際上從今年WWDC后,蘋果就增加了一個Server To Server的回調(diào)通知,當(dāng)有用戶退款時,會觸發(fā)該通知。非續(xù)期訂閱,消耗型,非消耗型均會收到退款通知。自動續(xù)期類訂閱,蘋果之前就會有通知。

詳情可參考以下官方文檔:
蘋果退款回調(diào)

2019年5月8日更新

最近統(tǒng)計丟單率的時候,反查我們公司的訂單有時候會出現(xiàn)后臺的某個商品銷量居然比iTunes后臺的該商品銷量還高的現(xiàn)象。排除時差因素和丟單自動補(bǔ)的流程因素以外,發(fā)現(xiàn)是后臺校驗(yàn)訂單重復(fù)性的邏輯出現(xiàn)了問題。

問題原因:

我們后臺之前的校驗(yàn)邏輯是對receipt_data 進(jìn)行MD5映射,然后每次服務(wù)器收到客戶端上報receipt_data的時候,先MD5,然后在數(shù)據(jù)庫進(jìn)行排重對比。以上的這個校驗(yàn)邏輯是建立在相同訂單的receipt_data一定相同。但實(shí)際過程中,我發(fā)現(xiàn)蘋果并不是這樣的,存在以下現(xiàn)象。
對于同一筆訂單,蘋果在極個別情況下會回調(diào)不一樣的receipt_data。所以用以上的排重校驗(yàn)邏輯,就存在有給客戶多發(fā)內(nèi)購商品的現(xiàn)象。

解決方案:

對蘋果返回的transaction_id進(jìn)行MD5映射或者直接保存在數(shù)據(jù)庫里,排重用這個字段。這里有一點(diǎn)要注意的是如果直接保存到數(shù)據(jù)庫的話,數(shù)據(jù)庫類型不要用整型,因?yàn)橛型蟹答佌f該字段可能會出現(xiàn)字符串。

博客上開頭講內(nèi)購大家通常的邏輯第⑥點(diǎn)時候是說的receipt_data用MD5映射做排重。但是后面的正文講服務(wù)器的校驗(yàn)邏輯那里說的是要用transaction_id做排重。這個應(yīng)該是當(dāng)時沒注意。現(xiàn)在為了避免引起誤解,我將排重方式都統(tǒng)一改成用transaction_id做排重。另外如果你們的后臺也是用receipt_data做排重,那就有問題了,需要盡快改成用transaction_id做排重。


---------------------------以下為正文---------------------------

iOS內(nèi)購開發(fā)大家一定不陌生,網(wǎng)上類似的文章能搜出千八百篇。大部分都是圍繞著如何實(shí)現(xiàn)?如何防止漏單丟單說明的。很少有提及到越獄的,即使偶爾有一兩篇說越獄,也是簡單的三言兩語說 為了安全,我們直接屏蔽了越獄手機(jī)的內(nèi)購功能。巴拉巴拉... 以前我也是這么想的,直到上個周末發(fā)現(xiàn)我們的內(nèi)購被xx了...才有了這篇文章。本篇文章就是來講述越獄下的內(nèi)購如何防止被xx。

首先我們先簡單理一下整個內(nèi)購的核心流程:
①客戶端發(fā)起支付訂單
②客戶端監(jiān)聽購買結(jié)果
③蘋果回調(diào)訂單購買成功時,客戶端把蘋果給的receipt_data和一些訂單信息上報給服務(wù)器
④后臺服務(wù)器拿receipt_data向蘋果服務(wù)器校驗(yàn)
⑤蘋果服務(wù)器向返回status結(jié)果,含義如下,其中為0時表示成功。
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)收到這個信息時,解碼后的收據(jù)信息也包含在返回內(nèi)容中
21007 收據(jù)信息是測試用(sandbox),但卻被發(fā)送到產(chǎn)品環(huán)境中驗(yàn)證
21008 收據(jù)信息是產(chǎn)品環(huán)境中使用,但卻被發(fā)送到測試環(huán)境中驗(yàn)證
⑥服務(wù)器發(fā)現(xiàn)訂單校驗(yàn)成功后,會把這筆訂單存起來,transaction_id用MD5值映射下,保存到數(shù)據(jù)庫,防止同一筆訂單,多次發(fā)放內(nèi)購商品。
以上應(yīng)該是主流的校驗(yàn)流程。當(dāng)然客戶端其中會插一些丟單漏單的邏輯校驗(yàn),因?yàn)槟切└酒恼聼o關(guān),所以不在此展開。


從上面的流程可以看出,整個內(nèi)購的核心其實(shí)就是receipt_data。蘋果回調(diào)給客戶端,客戶端上報給服務(wù)器,服務(wù)器拿到后去向蘋果服務(wù)器校驗(yàn),蘋果服務(wù)器再返回給我們服務(wù)器訂單結(jié)果。其實(shí)嚴(yán)格來說,整個流程是沒問題的。整個的漏洞是在最后一步上,【蘋果服務(wù)器再返回給我們服務(wù)器訂單結(jié)果】。receipt_data在越獄環(huán)境下是可以被插件偽造的,后臺向蘋果驗(yàn)證時,居然還能驗(yàn)證通過。是的,你沒看錯,蘋果這里有個賊雞兒坑的地方。這是最坑最坑的地方,偽造的receipt_data蘋果校驗(yàn)也返回支付成功

如何解決?我們先來看下越獄訂單和正常訂單對比

越獄訂單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"
            }
        ]
    }
}

看完兩筆訂單的對比我相信大家可以清楚的知道,越獄訂單雖然狀態(tài)返回是成功的,但是in_app這個參數(shù)是空的。大概查了一下。iOS7以下是沒有這個in_app參數(shù)的,iOS7以上是有的。因?yàn)楝F(xiàn)在App基本支持的起步都是iOS8 iOS9了,iOS7可以不用管了。但這里還有一個問題,就是in_app這個字段并不總是只返回一個,有可能會返回多個,比如下面的這種訂單。

正常訂單receipt_data校驗(yàn)后  in_app多個元素時:
{
    "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"
            },
            ...
        ]
    }
}

綜上,整個服務(wù)器那邊校驗(yàn)邏輯應(yīng)該是這樣的。
首先客戶端必須要給服務(wù)器傳的三個參數(shù):receipt_data, product_id ,transaction_id

//該方法為監(jiān)聽內(nèi)購交易結(jié)果的回調(diào)
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
transactions 為一個數(shù)組 遍歷就可以得到 SKPaymentTransaction 對象的元素transaction。然后從transaction里可以取到以下這兩個個參數(shù),product_id,transaction_id。另外從沙盒里取到票據(jù)信息receipt_data 
我們先看怎么取到以上的三個參數(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ù)器的三個字段。以上三個字段需要做好空值校驗(yàn),避免崩潰。
下面我們來解釋一下,為什么要給服務(wù)器傳這三個參數(shù)。
receipt_data:這個不解釋了 大家都懂 不傳的話 服務(wù)器根本沒法校驗(yàn)
product_id:這個也不用解釋 內(nèi)購產(chǎn)品編號 你不傳的話 服務(wù)器不知道你買的哪個訂單
transaction_id:這個是交易編號,是必須要傳的。因?yàn)槟阋欠乐乖姜z下內(nèi)購被xx就必須要校驗(yàn)in_app這個參數(shù)。而這個參數(shù)的數(shù)組元素有可能為多個,你必須得找到一個唯一標(biāo)示,才可以區(qū)分訂單到底是那一筆。

所以服務(wù)器那邊邏輯就很清晰了。

①先判重,避免重復(fù)分發(fā)內(nèi)購商品。收到客戶端上報的transaction_id后,直接MD5后去數(shù)據(jù)庫查,能查到說明是重復(fù)訂單,返回相應(yīng)錯誤碼給客戶端,如果查不到,去蘋果那邊校驗(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這個字段有沒有,沒有直接就返回失敗了。如果存在的話,遍歷整個數(shù)組,通過客戶端給的transaction_id 來比較,取到相同的訂單時,對比一下bundle_id ,product_id 是不是正確的。

如果以上校驗(yàn)都正確就把這筆訂單充值進(jìn)去,給用戶分發(fā)內(nèi)購商品。

注意:一定要告訴后臺,不論校驗(yàn)是否成功,只要客戶端給服務(wù)器傳了receipt_data等參數(shù)就一定要保存到數(shù)據(jù)庫里?!鞠旅鏁忉尀槭裁础?/h4>

以上的校驗(yàn)步驟,可以有效的防止內(nèi)購xx,下面內(nèi)容是我看蘋果官方能文檔的關(guān)于in_app這個參數(shù)說明和解釋下為啥服務(wù)器必須要保存每一個不同的receipt_data。

蘋果IAP官方文檔

蘋果文檔上介紹in_app參數(shù)內(nèi)容截圖.png

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.

大概意思是說:
在這個JSON文件中,這個鍵的值是一個數(shù)組,該數(shù)組包含基于base-64后的所有內(nèi)購收據(jù)。如果你的內(nèi)購類型是自動更新訂閱,那么請通過檢查latest_receipt_info鍵的值,來確定最近更新的狀態(tài)。

很有意思的是,蘋果還特別標(biāo)明了這么一句話:

Note: An empty array is a valid receipt.

也就是說這個in_app參數(shù)可能為空,如果為空的話,也需要把這筆交易認(rèn)為是有效的交易。這是蘋果建議的操作。當(dāng)然我們肯定不能這么干,這個參數(shù)是必須必須要校驗(yàn)的,不然越獄環(huán)境下,分分鐘就把你內(nèi)購xx了。我去校驗(yàn)了很多正常用戶的內(nèi)購訂單,沒發(fā)現(xiàn)一個in_app參數(shù)是為空的。但為了保險,還是讓后臺把所有前端傳的receipt_data等參數(shù)不管成功失敗都保存下來,萬一哪個用戶因此投訴充值不到賬,我們有據(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ā)起的時候,in_app里就會添加收據(jù)的一些信息。這些信息會一直保存直到你結(jié)束這筆交易。在此之后,下次更新收據(jù)時會將其從收據(jù)中刪除 - 例如,當(dāng)用戶再次購買時,或者您的應(yīng)用明確刷新收據(jù)時。

非消耗型項(xiàng)目,自動續(xù)期訂閱,非續(xù)期訂閱或免費(fèi)訂閱的應(yīng)用內(nèi)購買收據(jù)將無限期保留在收據(jù)中。

這一點(diǎn)也解釋了說,為什么in_app這個數(shù)組有時候會有多個元素。


下面在舉幾個大家做內(nèi)購經(jīng)常遇到的一些問題,和一些容易混淆的點(diǎn)。

Q1:內(nèi)購和Apple Pay的區(qū)別?

A1:內(nèi)購是內(nèi)購,Apple Pay是Apple Pay。我不知道有多少人第一次接觸時,會把這倆概念混淆掉,這里你可以簡單這么理解,虛擬的物品就是用內(nèi)購,實(shí)際的物品就是用Apple Pay。Apple Pay是一種支付方式,你可以類比為支付寶,微信那種。但人家只支持實(shí)際物品,如果你東西是虛擬的話,你卻集成Apple Pay上架是要被拒絕的哦~當(dāng)然反過來,實(shí)際物品你卻集成內(nèi)購上架,也是一樣被拒。對于大部分的國內(nèi)開發(fā)者而言,你很少會遇到需要集成Apple Pay的App的。能用支付寶/微信的場景還要求支持Apple Pay的產(chǎn)品畢竟是少數(shù)。

Q2:內(nèi)購項(xiàng)目的類型區(qū)別?

A2:首先內(nèi)購項(xiàng)目分為以下4種,消耗型項(xiàng)目,非消耗型項(xiàng)目,自動續(xù)期訂閱,非續(xù)期訂閱。我們來一個個介紹。

消耗型項(xiàng)目:只可使用一次的產(chǎn)品,使用之后即失效,必須再次購買。就是大家最廣為所知的虛擬幣,比如直播平臺斗魚的魚翅,熊貓的竹子,嗶哩嗶哩的B幣等,這個概念大家應(yīng)該很好理解,不過多解釋了。

非消耗型項(xiàng)目:只需購買一次,不會過期或隨著使用而減少的產(chǎn)品。這個一般是游戲那里用的多,一般是付費(fèi)解鎖關(guān)卡的場景,用戶買過一次,卸載重裝或者同一個Apple id但換App賬號時,也要能保證用戶重新獲得該內(nèi)購商品。所以App內(nèi)部需要額外去實(shí)現(xiàn)恢復(fù)購買的邏輯。

自動續(xù)期訂閱:允許用戶在固定時間段內(nèi)購買動態(tài)內(nèi)容的產(chǎn)品。除非用戶選擇取消,否則此類訂閱會自動續(xù)期。iTunces上給的示例是:每月訂閱提供流媒體服務(wù)的 App。對比我們熟悉的,網(wǎng)易云音樂的內(nèi)購商品-連續(xù)包月黑膠VIP,就是此類型。一般來說,沒啥必要不要選這一種,如果是VIP的那種場景推薦下面非續(xù)期訂閱類型去做。自動續(xù)期訂閱的坑非常多,比另外幾種內(nèi)購類型都要復(fù)制。

非續(xù)期訂閱:一般來說VIP可以用這種方法來做訂閱,我們公司項(xiàng)目的VIP購買就是這種方式。他的實(shí)現(xiàn)方式你可以完全照搬消耗性項(xiàng)目,不用做什么額外處理,也不用去管返回的訂閱日期什么的東西,就是以服務(wù)器那邊為準(zhǔn)。服務(wù)器的日期開始,服務(wù)器的日期結(jié)束。既簡單又保險,不需要額外的做什么處理。

Q3:VIP一定要用內(nèi)購做嗎?

A3:其實(shí)判斷你們公司的App到底需不需要用內(nèi)購,很簡單,就是看跟實(shí)際物品有沒有關(guān)系。如果你的VIP功能是類似餓了么這種,點(diǎn)外賣可以打折/多領(lǐng)紅包 那么就不需要用內(nèi)購,上架的時候說清楚就行了。如果你的VIP功能是虛擬的,比如頭像更炫酷,尊貴的VIP身份標(biāo)示,獨(dú)特的入場動畫等等虛擬相關(guān)的,比如QQ會員,就必須要用內(nèi)購去做。需要說明的是,那種是VIP才能和某某用戶聊天的場景,是VIP才能得到App里某某用戶的服務(wù)【語音,視頻】時,這一類的場景蘋果一樣認(rèn)為是虛擬的,一樣要用內(nèi)購去做。

Q4:VIP內(nèi)購一定是非續(xù)期/自動續(xù)期訂閱嗎?我可不可以用虛擬幣購買VIP呢?

A4:這個問題我自己經(jīng)歷過。我的答案是你也可以用虛擬幣購買VIP的這種方式,但如果被拒絕,你只能老老實(shí)實(shí)的按前種方式去做。如果你們的App既有虛擬幣又有VIP,產(chǎn)品希望你VIP是直接用虛擬幣去購買,這樣整個流程都很方便。那么你一定要記住。千萬不要在1.0版本這么做,這是血淚教訓(xùn)。1.0版本會抓的很嚴(yán)很嚴(yán),同時虛擬幣+VIP功能,百分百蘋果會要求你VIP要用續(xù)期訂閱去實(shí)現(xiàn)。最保險的做法呢,1.0版本不要做任何內(nèi)購,迭代幾個小版本后,加入虛擬幣內(nèi)購,在迭代幾個小版本,加入VIP直接用虛擬幣購買的功能,這是最最保險的做法。記?。?.0的審核力度是真的很嚴(yán),能先不做內(nèi)購就不要做內(nèi)購,老板或許不懂,1.0版本什么都想要,但往往因?yàn)閮?nèi)購,會讓你們的產(chǎn)品反復(fù)被拒。這一塊如果大家感興趣,可以看看,我的1.0版本就是加了內(nèi)購,反復(fù)被拒5次。血淚教訓(xùn)

Q5:我們老板心疼那百分之30的手續(xù)費(fèi),我能不能不用內(nèi)購啊,有沒辦法繞過內(nèi)購?

A5:辦法是有的。但是有風(fēng)險。我16年做過繞開內(nèi)購的方法。思路很簡單,就是App里集成支付寶/微信/內(nèi)購這種功能,后臺做控制開關(guān),審核時,開關(guān)打開,給審核人員看內(nèi)購功能,審核通過后,開關(guān)關(guān)閉,給正常用戶是用支付寶/微信功能。這個方法,我17年的時候聽到很多群友說不行了,你在上架審核時,蘋果會掃描你的包,檢測到第三方支付sdk時,會拒絕掉。后來又有群友說可以用H5的方式實(shí)現(xiàn)支付功能。另外可能會有別的繞開蘋果審核的實(shí)現(xiàn)方式,如果有哪位朋友知道,不妨留言告知。但不管是哪種方式,都是有風(fēng)險的,蘋果對內(nèi)購一直抓的很嚴(yán),如果讓它知道你們在錢的方面上欺騙過他,后果還是很嚴(yán)重的。iOS上的用戶付費(fèi)率還是很不錯的,付費(fèi)意愿基本上可以是安卓用戶的十幾倍。所以如果你們的產(chǎn)品真的有前景,并且想長久做下去,還是奉勸不要做欺騙蘋果的事情了。

Q6:網(wǎng)上有好多講丟單的博客,看的是一臉懵逼,有的看懂后,在看下一篇又不懂了,感覺都好復(fù)雜。

A6:引起內(nèi)購丟單的主要操作其實(shí)是當(dāng)用戶點(diǎn)擊內(nèi)購商品時,蘋果服務(wù)器太慢了,支付頁面一直不出來。結(jié)果用戶退出或者殺死App,這時候在Home頁面,支付框又彈出來了,然后用戶點(diǎn)擊支付,成功后在打開App發(fā)現(xiàn)丟單。
一般這種只要你在Appdelegate的didFinishLaunchingWithOptions方法就開始對蘋果內(nèi)購回調(diào)做監(jiān)聽,然后把所有相關(guān)內(nèi)購的東西抽出來做一個單例即可解決丟單。另外還有一些丟單可能是用applicationUsername做透傳引起的,這種解決辦法一般就是NSUserDefaults或者keychain或者在極端點(diǎn)NSUserDefaults+ keychain來做本地信息的記錄。關(guān)于丟單這一塊感興趣的可以自己搜貝聊解決丟單的那個博客【雖然他講的比較亂,但思路可以借鑒下】。另外評論區(qū)下面有個 廣東深圳 的

我在剛接觸內(nèi)購的時候也是這樣,我覺得有些博客講的真有點(diǎn)過了,它為了考慮一些用戶的極端操作,多出來很多邏輯處理,導(dǎo)致博客異常的復(fù)雜,我記得有博客講必須要把receipt_data等信息存到keychain里,因?yàn)橛脩粲锌赡苄遁dApp,如果你只存到NSUserDefaults里,那樣就丟單了。 ......那么有沒有這種情況呢?我覺得是肯定有的。但我們來算算幾率,首先他內(nèi)購成功,在向服務(wù)器調(diào)接口的時候,他手機(jī)突然沒電了/斷網(wǎng)了/程序崩潰了/網(wǎng)絡(luò)差等的久他自己殺死進(jìn)程了 巴拉巴拉。 然后他在下一次手機(jī)恢復(fù)正常的時候,果斷卸載掉App,重新去App Store上下載安裝,進(jìn)入App后,發(fā)現(xiàn)內(nèi)購沒到賬。
867088104DC1554FE5CDF4E962061E43.jpg

網(wǎng)上博客還愛用那種切換賬號的場景舉例,A內(nèi)購成功了,但用戶各種騷操作后,自己換到B賬號,然后服務(wù)器那邊把商品發(fā)到B賬號上了,等等。
這些情況都是存在的,因?yàn)樘O果的內(nèi)購機(jī)制問題,你是不能百分百保證不丟單的,不要把丟單情況看的那么嚴(yán)重,邏輯寫的那么復(fù)雜。你看看所有大廠的App上都會寫充值遇到問題,點(diǎn)我聯(lián)系客服 巴拉巴拉。關(guān)于丟單,我的做法是這樣的,在蘋果內(nèi)購成功的回調(diào)里,NSUserDefaults存每一筆支付成功的訂單,如果服務(wù)器校驗(yàn)成功,就把本地存的這筆訂單刪除。如果沒收到服務(wù)器的響應(yīng),就一直保留。然后每次App啟動就會去把本地存的丟單信息扔向服務(wù)器校驗(yàn),校驗(yàn)成功刪除,校驗(yàn)失敗不管。這里還是看開發(fā)時間,當(dāng)時我寫內(nèi)購功能的時候,預(yù)算時間就兩天不到,所以寫的飛快,就簡單的用這個辦法去防止丟單,目前來看,沒有發(fā)現(xiàn)過一筆真正用戶充錢但商品沒到賬的例子。如果大家開發(fā)時間充足,可以慢慢去彌補(bǔ)極端操作漏洞。

Q7:內(nèi)購為什么會有這么多坑啊?看網(wǎng)上好多博客都在說,我自己做微信/支付寶的時候,沒感覺有這么多坑啊

A7:蘋果的內(nèi)購坑主要有以下幾點(diǎn)

  • applicationUsername該字段可能為nil 導(dǎo)致客戶端沒辦法用這個參數(shù)給服務(wù)器透傳訂單編號,來形成一個交易訂單號的綁定。

  • 校驗(yàn)訂單流程是必須服務(wù)器主動去詢問蘋果服務(wù)器,而支付寶/微信 卻是他們的服務(wù)器會在用戶支付成功時主動給我們服務(wù)器回調(diào)。正是這個原因,讓iOS開發(fā)者飽受折磨,大部分的丟單漏單都是蘋果的這個設(shè)計造成的。蘋果不會主動回調(diào)給我們服務(wù)器,也就意味著我們服務(wù)器需要主動去蘋果那里詢問這筆訂單,到底成沒成功。但服務(wù)器詢問的時機(jī),又是客戶端告訴服務(wù)器的。這就雞兒坑了,一些情況下,用戶在付費(fèi)成功后,突然斷網(wǎng)了/崩潰了/出現(xiàn)意外了等等,客戶端沒辦法告訴服務(wù)器,這就出現(xiàn)了,用戶錢成功了,內(nèi)購商品卻沒到賬。所以網(wǎng)上才會有這么多篇講防止丟單的博客。

  • 越獄下,插件也能xx掉蘋果內(nèi)購,然后校驗(yàn)狀態(tài)status還返回成功。也就是本篇博客開頭講的那種情況。這一點(diǎn)真的是無力吐槽,虧你特么回調(diào)給我的receipt_data那么一大長串,有卵用?

  • 蘋果的訂單機(jī)制。蘋果為了保護(hù)用戶隱私,你是看不到一條條流水明細(xì)的。你看到的只有??這種。
    27FED3A0-C9F3-47C1-BA67-4EFAA7B2FCBA.png

    每一種內(nèi)購類型的總收入,或者總銷量。導(dǎo)致對賬查詢的時候加了不少麻煩。

  • 蘋果的退款機(jī)制。這個比上面一點(diǎn)更坑,iOS用戶,內(nèi)購了某商品,你可以在完全用完了后,聯(lián)系蘋果客服,說我誤操作了巴拉巴拉或者說感覺這個商品不值那么多被開發(fā)者欺騙了巴拉巴拉,快給我退款,客服就會溫柔的告訴你,不要急,她會幫你處理,1-2個工作日把,你就會發(fā)現(xiàn)你的錢就退回來了。沒記錯的話,一段時間內(nèi),一個Apple Id可以申請1-2次。但不能多,多了的話就會被蘋果拒絕。而這一切,開發(fā)者這邊是完全不知情的。你不知道哪個用戶退款了,你知道的只是一個圖,類似下面的這種。
    0A762A89-3A96-4503-995D-028A96518958.png

    用戶消費(fèi)了你的內(nèi)購商品,公司卻收不到錢,很多公司的內(nèi)購服務(wù)都是要成本的。如果這種用戶一旦多起來,壞賬率會飆升,公司就會被活活的拖垮。一個好的項(xiàng)目也就涼涼掉。淘寶上關(guān)于iOS內(nèi)購?fù)丝顚iT有一個超級龐大的黑色產(chǎn)業(yè)鏈。從弄賬號到專門聯(lián)系蘋果客服再到道具銷贓變現(xiàn),各司其職,一環(huán)套一環(huán),每個環(huán)節(jié)人都賺的盆滿缽滿。苦的都是公司,因?yàn)樘O果沒有任何損失,他也不會補(bǔ)償你公司1毛錢,一切損失都是公司自己承擔(dān)。沒記錯的話,15-16年,很多很多游戲公司都是因?yàn)檫@個被活活拖垮的。幸運(yùn)的是,這種惡意退款一般都是針對游戲公司,因?yàn)橛螒虻谰呖梢钥焖僮儸F(xiàn)。像正常的App甚少碰到,因?yàn)樗丝盍艘矝]毛用,沒法及時變現(xiàn)。畢竟他們可不稀罕跟你們的女用戶1v1視頻聊天。

Q8:對于開發(fā)者來講,用通過內(nèi)購充值,那開發(fā)者到時候怎么得到這筆錢?

A8:做內(nèi)購的時候,會填寫銀行稅務(wù)等等這些信息。蘋果會按期把錢打入到你們當(dāng)時填寫的銀行卡賬戶中。這里要注意,如果你當(dāng)月內(nèi)購收入很低,比如只有幾十美金,那蘋果是不會給你打款的。具體的額度好像是以150美金分界限,當(dāng)你內(nèi)購收入超過150美金的時候,蘋果會下月給你打款。如果你不夠150美金,那蘋果會累積到下個月打款,如果下個月還不夠,那就會繼續(xù)累積。
這里注意兩點(diǎn):

  1. 150美金是個大概的數(shù)值,我自己沒有確實(shí)求證過,我實(shí)際經(jīng)歷來說遇到的當(dāng)月收入都遠(yuǎn)超過這個數(shù)字,所以很準(zhǔn)確的最低打款金額,我也不好評估。
  2. 蘋果的打款日期,并沒有嚴(yán)格的規(guī)律。上旬,中旬,下旬打款我都遇到過,另外有時候,即便你當(dāng)月金額有很多,蘋果也可能下個月不給你打款,而是給你累積到下下個月。但至多不會超過兩個月以上,如果你遇到這種情況,需要及時和蘋果客戶溝通。
    綜上:
    只要你銀行卡,稅務(wù)等相關(guān)財務(wù)信息填寫正確,賬戶里收入超過150美金,大多數(shù)情況下,下個月上旬就能收到蘋果的打款。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容