iOS內購-防越獄破解刷單 2020年8月12日更新 2019年5月8日更新 問題原因: 解決方案: 下面在舉幾個大家做內購經常遇到的一些問題,和一些容易混淆的點。

2020年8月12日更新

關於文中,蘋果用戶退款了也不知道是誰退的那塊表述,現在來看 是有誤的。實際上從今年WWDC後,蘋果就增加了一個Server To Server的回調通知,當有用戶退款時,會觸發該通知。非續期訂閱,消耗型,非消耗型均會收到退款通知。自動續期類訂閱,蘋果之前就會有通知。

詳情可參考以下官方文檔:
蘋果退款回調

2019年5月8日更新

最近統計丟單率的時候,反查我們公司的訂單有時候會出現後臺的某個商品銷量居然比iTunes後臺的該商品銷量還高的現象。排除時差因素和丟單自動補的流程因素以外,發現是後臺校驗訂單重複性的邏輯出現了問題。

問題原因:

我們後臺之前的校驗邏輯是對receipt_data 進行MD5映射,然後每次服務器收到客戶端上報receipt_data的時候,先MD5,然後在數據庫進行排重對比。以上的這個校驗邏輯是建立在相同訂單的receipt_data一定相同。但實際過程中,我發現蘋果並不是這樣的,存在以下現象。
對於同一筆訂單,蘋果在極個別情況下會回調不一樣的receipt_data。所以用以上的排重校驗邏輯,就存在有給客戶多發內購商品的現象。

解決方案:

對蘋果返回的transaction_id進行MD5映射或者直接保存在數據庫裏,排重用這個字段。這裏有一點要注意的是如果直接保存到數據庫的話,數據庫類型不要用整型,因爲有同行反饋說該字段可能會出現字符串。

博客上開頭講內購大家通常的邏輯第⑥點時候是說的receipt_data用MD5映射做排重。但是後面的正文講服務器的校驗邏輯那裏說的是要用transaction_id做排重。這個應該是當時沒注意。現在爲了避免引起誤解,我將排重方式都統一改成用transaction_id做排重。另外如果你們的後臺也是用receipt_data做排重,那就有問題了,需要儘快改成用transaction_id做排重。


---------------------------以下爲正文---------------------------

iOS內購開發大家一定不陌生,網上類似的文章能搜出千八百篇。大部分都是圍繞着如何實現?如何防止漏單丟單說明的。很少有提及到越獄的,即使偶爾有一兩篇說越獄,也是簡單的三言兩語說 爲了安全,我們直接屏蔽了越獄手機的內購功能。巴拉巴拉... 以前我也是這麼想的,直到上個週末發現我們的內購被破解了...纔有了這篇文章。本篇文章就是來講述越獄下的內購如何防止被破解。

首先我們先簡單理一下整個內購的核心流程:
①客戶端發起支付訂單
②客戶端監聽購買結果
③蘋果回調訂單購買成功時,客戶端把蘋果給的receipt_data和一些訂單信息上報給服務器
④後臺服務器拿receipt_data向蘋果服務器校驗
⑤蘋果服務器向返回status結果,含義如下,其中爲0時表示成功。
21000 App Store無法讀取你提供的JSON數據
21002 收據數據不符合格式
21003 收據無法被驗證
21004 你提供的共享密鑰和賬戶的共享密鑰不一致
21005 收據服務器當前不可用
21006 收據是有效的,但訂閱服務已經過期。當收到這個信息時,解碼後的收據信息也包含在返回內容中
21007 收據信息是測試用(sandbox),但卻被髮送到產品環境中驗證
21008 收據信息是產品環境中使用,但卻被髮送到測試環境中驗證
⑥服務器發現訂單校驗成功後,會把這筆訂單存起來,transaction_id用MD5值映射下,保存到數據庫,防止同一筆訂單,多次發放內購商品。
以上應該是主流的校驗流程。當然客戶端其中會插一些丟單漏單的邏輯校驗,因爲那些跟本篇文章無關,所以不在此展開。


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

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

越獄訂單receipt_data向蘋果服務器校驗後如下:
{
    "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向蘋果服務器校驗後如下:
{
   {
    "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"
            }
        ]
    }
}

看完兩筆訂單的對比我相信大家可以清楚的知道,越獄訂單雖然狀態返回是成功的,但是in_app這個參數是空的。大概查了一下。iOS7以下是沒有這個in_app參數的,iOS7以上是有的。因爲現在App基本支持的起步都是iOS8 iOS9了,iOS7可以不用管了。但這裏還有一個問題,就是in_app這個字段並不總是隻返回一個,有可能會返回多個,比如下面的這種訂單。

正常訂單receipt_data校驗後  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"
            },
            ...
        ]
    }
}

綜上,整個服務器那邊校驗邏輯應該是這樣的。
首先客戶端必須要給服務器傳的三個參數:receipt_data, 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";

②服務器拿到蘋果的校驗結果後,首先判斷訂單狀態是不是成功。

③如果訂單狀態成功在判斷in_app這個字段有沒有,沒有直接就返回失敗了。如果存在的話,遍歷整個數組,通過客戶端給的transaction_id 來比較,取到相同的訂單時,對比一下bundle_id ,product_id 是不是正確的。

如果以上校驗都正確就把這筆訂單充值進去,給用戶分發內購商品。

注意:一定要告訴後臺,不論校驗是否成功,只要客戶端給服務器傳了receipt_data等參數就一定要保存到數據庫裏。【下面會解釋爲什麼】

以上的校驗步驟,可以有效的防止內購破解,下面內容是我看蘋果官方能文檔的關於in_app這個參數說明和解釋下爲啥服務器必須要保存每一個不同的receipt_data。

蘋果IAP官方文檔

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文件中,這個鍵的值是一個數組,該數組包含基於base-64後的所有內購收據。如果你的內購類型是自動更新訂閱,那麼請通過檢查latest_receipt_info鍵的值,來確定最近更新的狀態。

很有意思的是,蘋果還特別標明瞭這麼一句話:

Note: An empty array is a valid receipt.

也就是說這個in_app參數可能爲空,如果爲空的話,也需要把這筆交易認爲是有效的交易。這是蘋果建議的操作。當然我們肯定不能這麼幹,這個參數是必須必須要校驗的,不然越獄環境下,分分鐘就把你內購破解了。我去校驗了很多正常用戶的內購訂單,沒發現一個in_app參數是爲空的。但爲了保險,還是讓後臺把所有前端傳的receipt_data等參數不管成功失敗都保存下來,萬一哪個用戶因此投訴充值不到賬,我們有據可查。

下面兩段話

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.

大概意思是說:
每當有一筆交易發起的時候,in_app裏就會添加收據的一些信息。這些信息會一直保存直到你結束這筆交易。在此之後,下次更新收據時會將其從收據中刪除 - 例如,當用戶再次購買時,或者您的應用明確刷新收據時。

非消耗型項目,自動續期訂閱,非續期訂閱或免費訂閱的應用內購買收據將無限期保留在收據中。

這一點也解釋了說,爲什麼in_app這個數組有時候會有多個元素。


以下內容和本篇文章無關,下面只是簡單分享下我瞭解到的越獄,以及我身邊關於越獄的一些事情和對這件事情的反思。

關於越獄的一些事情:
現在越獄已經支持iOS11.3了,我的安卓同事前陣子用自己的iPhone X 越獄了。裝了下插件【付費的 並且需要美國區App Store賬號】他是我們產品的重度用戶,很多小號。但偏偏產品因爲某些原因不讓做退出登錄,切換賬號的功能。所以一般用戶如果想切換賬號,只能卸載重裝。但他就不一樣了,他騷的飛起,在越獄機上裝的插件,他可以改我的所有代碼。我看了一眼。整個項目的類,方法,返回值,入參,在那個插件下一覽無遺。他找到了我保存用戶信息的方法,因爲是公司自己人,各種業務流程,他都懂。篡改了一些我的方法,返回參數。比如一些判斷bool的方法,應該返回NO,直接篡改成YES。還一些其他的東西。當然後臺本身對安全的邏輯校驗控制的比較弱,只在一些關鍵接口【支付 送禮等】做了控制。

抖音的國際板Tik Tok 如果你在國內切換成美區的App Store賬號,並用***下載後,你會發現接口刷不到數據,看不到國外的小姐姐。我同事篡改了抖音的方法,就可以拉到數據了。具體方法。[PS:日本人發的抖音感覺都傻乎乎的,上面的小姐姐比國內的差遠了。水平【化妝 濾鏡 美顏 拍攝角度】明顯不如國內的666]

類名:CTCarrier 方法:- (id) isoCountryCode 該方法是系統方法 拿來獲取電話服務商的iOS國家編碼。改成日本就是拿日本數據,改成香港就是香港數據

修改支付寶步數

類名:APStepInfo 方法:- (long long) numberofSteps 返回值就是步數,你隨便改成幾萬步都可以。

修改支付寶朋友小紅點

類名:MPBadgeView 方法:- (void) drawBadgeRedPoint 

另外,還有各種被玩壞的逆向微信功能。
是的,就是這麼簡單。在越獄環境下。我們寫的App猶如一個被剝光衣服的小姑娘,只要越獄+V【和】P【諧】N+一些付費插件,誰都可以過來上幾下。我的安卓同事不會寫任何iOS的代碼,不懂彙編,不懂砸殼,不懂啥iOS逆向,但他懂只要我付費幾美元買個插件,一個個App就是一個個脫光衣服的姑娘。是的,現在破解的門檻真的很低,你花點錢站在巨人的肩膀上就可以爲所欲爲。

另外上面的幾個例子,不知道大家發現了沒。方法名,類名基本上都是見名知意,這也正是我們iOS的規範所在。嚴格的命名方式,讓人一目瞭然,也讓破解者一目瞭然。我大概看了,國內大廠App。抖音,支付寶,微信等等這些,沒有一家做混淆的。方法名都挺規範的。這時候,我又想起了之前和同事開玩笑說的話,“以後再也特麼不歧視命名-(void)a -(void)b -(void)c,label1 label2 label3的人了,人家自帶混淆😂😂😂”。這裏,我也很奇怪,雖然我知道其實就算你做了混淆對攻擊者來說也是沒卵用,但好歹之前是裸體小姑娘,現在是穿衣服的小姑娘了,總是會提高一些門檻把。細思了一下,大概是真的因爲這玩意工作量大,容易引起很多潛在bug,收益又很低【攻擊者無法是秒破變成天破而已,慢慢試總能試出來】,性價比極低,所以纔不做的把。


另外,可能也有朋友會說,既然越獄機下這麼多搞事情的,我能不能寫個方法直接禁掉越獄機子啊。反正越獄用戶也不會多,就不要了。答案是否定的,網上的一些代碼,你去搜iOS判斷是否越獄,iOS越獄檢測等等。出來的文章基本都是複製粘貼,沒什麼價值的,裏面的代碼古老且舊,尤其是檢測越獄時方法用bool值返回的那種,在一些防越獄檢測的插件下,更是被秒破的。有價值的資料比我想象中的要更少。如果有朋友能有一些最新防越獄插件檢測的方法,請不吝分享下。


下面在舉幾個大家做內購經常遇到的一些問題,和一些容易混淆的點。

Q1:內購和Apple Pay的區別?

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

Q2:內購項目的類型區別?

A2:首先內購項目分爲以下4種,消耗型項目,非消耗型項目,自動續期訂閱,非續期訂閱。我們來一個個介紹。

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

非消耗型項目:只需購買一次,不會過期或隨着使用而減少的產品。這個一般是遊戲那裏用的多,一般是付費解鎖關卡的場景,用戶買過一次,卸載重裝或者同一個Apple id但換App賬號時,也要能保證用戶重新獲得該內購商品。所以App內部需要額外去實現恢復購買的邏輯。

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

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

Q3:VIP一定要用內購做嗎?

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

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

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

Q5:我們老闆心疼那百分之30的手續費,我能不能不用內購啊,有沒辦法繞過內購?

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

Q6:網上有好多講丟單的博客,看的是一臉懵逼,有的看懂後,在看下一篇又不懂了,感覺都好複雜。

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

我在剛接觸內購的時候也是這樣,我覺得有些博客講的真有點過了,它爲了考慮一些用戶的極端操作,多出來很多邏輯處理,導致博客異常的複雜,我記得有博客講必須要把receipt_data等信息存到keychain裏,因爲用戶有可能卸載App,如果你只存到NSUserDefaults裏,那樣就丟單了。 ......那麼有沒有這種情況呢?我覺得是肯定有的。但我們來算算機率,首先他內購成功,在向服務器調接口的時候,他手機突然沒電了/斷網了/程序崩潰了/網絡差等的久他自己殺死進程了 巴拉巴拉。 然後他在下一次手機恢復正常的時候,果斷卸載掉App,重新去App Store上下載安裝,進入App後,發現內購沒到賬。

網上博客還愛用那種切換賬號的場景舉例,A內購成功了,但用戶各種騷操作後,自己換到B賬號,然後服務器那邊把商品發到B賬號上了,等等。
這些情況都是存在的,因爲蘋果的內購機制問題,你是不能百分百保證不丟單的,不要把丟單情況看的那麼嚴重,邏輯寫的那麼複雜。你看看所有大廠的App上都會寫充值遇到問題,點我聯繫客服 巴拉巴拉。關於丟單,我的做法是這樣的,在蘋果內購成功的回調裏,NSUserDefaults存每一筆支付成功的訂單,如果服務器校驗成功,就把本地存的這筆訂單刪除。如果沒收到服務器的響應,就一直保留。然後每次App啓動就會去把本地存的丟單信息扔向服務器校驗,校驗成功刪除,校驗失敗不管。這裏還是看開發時間,當時我寫內購功能的時候,預算時間就兩天不到,所以寫的飛快,就簡單的用這個辦法去防止丟單,目前來看,沒有發現過一筆真正用戶充錢但商品沒到賬的例子。如果大家開發時間充足,可以慢慢去彌補極端操作漏洞。

Q7:內購爲什麼會有這麼多坑啊?看網上好多博客都在說,我自己做微信/支付寶的時候,沒感覺有這麼多坑啊

A7:蘋果的內購坑主要有以下幾點

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

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

  • 越獄下,插件也能破解掉蘋果內購,然後校驗狀態status還返回成功。也就是本篇博客開頭講的那種情況。這一點真的是無力吐槽,虧你特麼回調給我的receipt_data那麼一大長串,有卵用?

  • 蘋果的訂單機制。蘋果爲了保護用戶隱私,你是看不到一條條流水明細的。你看到的只有👇這種。

    每一種內購類型的總收入,或者總銷量。導致對賬查詢的時候加了不少麻煩。

  • 蘋果的退款機制。這個比上面一點更坑,iOS用戶,內購了某商品,你可以在完全用完了後,聯繫蘋果客服,說我誤操作了巴拉巴拉或者說感覺這個商品不值那麼多被開發者欺騙了巴拉巴拉,快給我退款,客服就會溫柔的告訴你,不要急,她會幫你處理,1-2個工作日把,你就會發現你的錢就退回來了。沒記錯的話,一段時間內,一個Apple Id可以申請1-2次。但不能多,多了的話就會被蘋果拒絕。而這一切,開發者這邊是完全不知情的。你不知道哪個用戶退款了,你知道的只是一個圖,類似下面的這種。

    用戶消費了你的內購商品,公司卻收不到錢,很多公司的內購服務都是要成本的。如果這種用戶一旦多起來,壞賬率會飆升,公司就會被活活的拖垮。一個好的項目也就涼涼掉。淘寶上關於iOS內購退款專門有一個超級龐大的黑色產業鏈。從弄賬號到專門聯繫蘋果客服再到道具銷贓變現,各司其職,一環套一環,每個環節人都賺的盆滿鉢滿。苦的都是公司,因爲蘋果沒有任何損失,他也不會補償你公司1毛錢,一切損失都是公司自己承擔。沒記錯的話,15-16年,很多很多遊戲公司都是因爲這個被活活拖垮的。幸運的是,這種惡意退款一般都是針對遊戲公司,因爲遊戲道具可以快速變現。像正常的App甚少碰到,因爲他退款了也沒毛用,沒法及時變現。畢竟他們可不稀罕跟你們的女用戶1v1視頻聊天。

Q8:對於開發者來講,用通過內購充值,那開發者到時候怎麼得到這筆錢?

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章