最近由于工作需要,研究unity如何接入Google Play以实现游戏内购买。目前IAB的实现,prime31做的插件比较好,各平台的IAB均有,但费用相对过高(几乎都是70刀左右,可怜穷小子)。在blogspot上找到仅有的一篇相关文章,特此转载在这以供大家相互交流学习。由于编程基础较差加上没学过java,按照该作者的方法暂时没有实现,希望看到此文章的程序猿们共同探讨解决方案。以下是文章原文:  底下是 Unity 介接 Google Play In-app Billing (IAB) 金流的步驟,使用的環境是 OSX,IAB 是 V3 版 :


1. 編譯 IInAppBillingService.java

a. 按照 GooglePlay 官方網站的指示 (http://developer.android.com/training/in-app-billing/preparing-iab-app.html) 產生此檔案,其實這是一個自動產生的檔案,詳細方法可以參考連結網頁敘述。此外值得注意的是,程式碼也可以由 git 從此處下載 :


git clone https://code.google.com/p/marketbilling/


(附註 : 也許現在已經更新了,但文章撰寫的當下,若是從 SDK Manager 勾選 Google Play Billing Library 下載的版本有 Bug,所以才由 git 下載。 ref :

http://stackoverflow.com/questions/14397343/google-play-in-app-billing-version-3-crash-on-item-already-owned-and-missing )


b. 編譯 IInAppBillingService.java,指令如下 (此處假設 android sdk 之資料夾路徑在 /Users/macaronics/android-sdks/) :

javac ./IInAppBillingService.java -cp /Users/macaronics/android-sdks/platforms/android-10/android.jar -d .

其中 -cp 指示編譯時參考之 library (android-10, Android 2.3.3)。編譯完成後輸入 :


jar cvfM ./iiabs.jar com/


產生的 iiabs.jar 放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下( 此範例將檔案置於 /Users/macaronics/UnityIabProj/Assets/Plugins/Android/) 。

注意要刪除資料夾 com,以免後續執行 jar 時,打包到舊的 com package。 


2. 編譯 class IabHelper


a. 此類別由九個檔案組成,方便用來介接 Google IAB,九個 java 檔案應該位於前步驟下載之程式碼的 marketbilling/v3/src/com/example/android/trivialdrivesample/util 底下。


b. 逐一修改九個檔案裡的 "package com.example.android.trivialdrivesample.util" 改為你的 package name,例如  package com.macaronics.iab.util。


c. 建立欲編譯的檔案的 list,在 Terminal 底下的話直接輸入 cat > sources 然後依序輸入九個檔案檔名後按 control+d 儲存離開。

在 terminal 底下利用 cat 建立 sources file   d. 編譯 class IabHelper,輸入指令如下 :   javac @sources -cp /Users/macaronics/android-sdks/platforms/android- 10/android.jar:/Users/macaronics/UnityIabProj/Assets/Plugins/Android/iiabs.jar

其中 @sources 表示參考檔案 sources 裡的路徑。此外,引入的 library 除了 android-10/android.jar (Android 2.3.3) 外,還包含前一步驟所編譯的檔案 iiabs.jar。完成後接著輸入 :


jar cvfM ./iabhelper.jar com/


產生的 iabhelper.jar 放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下( 此範例將檔案置於 /Users/macaronics/UnityIabProj/Assets/Plugins/Android/) 。



3. 處理 onActivityResult 的結果訊息


a. 為了讓 IabHelper 能在 Activity 結束時處理 Activity 的結果,這裡我們需要自己撰寫繼承 UnityPlayerActivity 的類別來將結果 Relay 給 IabHelper。底下是範例的程式碼 :


​package​​​​com.macaronics.iab;​


​import​​​​com.​​unity3d​​.player.UnityPlayerActivity;​


​import​​​​android.os.Bundle;​​​​import​​​​android.util.Log;​​​​import​​​​android.content.Intent;​


​public​​​​class​​​​overrideActivity ​​​​extends​​​​UnityPlayerActivity {​​​​    ​​​​public​​​​interface​​​​cbEvent{​​​​        ​​​​public​​​​boolean​​​​cbEvent(​​​​int​​​​requestCode, ​​​​int​​​​resultCode, Intent data);​​​​    ​​​​}​


​    ​​​​protected​​​​cbEvent ie;​​​​    ​​​​static​​​​protected​​​​overrideActivity inst;​​​​    ​​​​protected​​​​void​​​​onCreate(Bundle savedInstanceState) {​​​​        ​​​​super​​​​.onCreate(savedInstanceState);​​​​        ​​​​inst =​​​​this​​​​;​​​​        ​​ 

​        ​​​​// print debug message to logcat​​​​        ​​​​Log.d(​​​​"overrideActivity"​​​​, ​​​​"onCreate called!"​​​​);​​​​    ​​​​}​


​    ​​​​@Override​​​​    ​​​​public​​​​void​​​​onDestroy(){​​​​        ​​​​super​​​​.onDestroy();​​​​        ​​​​inst =​​​​null​​​​;​​​​        ​​​​Log.d(​​​​"overrideActivity"​​​​, ​​​​"onDestroy called!"​​​​);​​​​    ​​​​}​


​    ​​​​@Override​​​​    ​​​​public​​​​void​​​​onActivityResult(​​​​int​​​​requestCode, ​​​​int​​​​resultCode, Intent data){​​​​        ​​​​Log.d(​​​​"overrideActivity"​​​​, ​​​​"onActivityResult called!"​​​​);​​​​        ​​ 

​        ​​​​boolean​​​​ret =​​​​false​​​​;​​​​        ​​​​if​​​​(ie !=​​​​null​​​​){​​​​            ​​​​try​​​​{​​​​                ​​​​ret =ie.cbEvent(requestCode, resultCode, data);​​​​            ​​​​}​​​​            ​​​​catch​​​​(Exception e){​​​​                ​​​​ret =​​​​false​​​​;​​​​            ​​​​}​​​​        ​​​​}​


​        ​​​​if​​​​(ret ==​​​​false​​​​){​​​​            ​​​​super​​​​.onActivityResult(requestCode, resultCode, data);​​​​        ​​​​}​​​​    ​​​​}​​​​    ​​ 

​    ​​​​static​​​​public​​​​void​​​​registerOnActivityResultCBFunc(​​​​final​​​​cbEvent pcbfunc){​​​​        ​​​​if​​​​(inst !=​​​​null​​​​)​​​​            ​​​​inst.ie =pcbfunc;​​​​    ​​​​}​​​​} ​

其中函數 registerOnActivityResultCBFunc 只能記憶單一個 call back function,讀者可以把功能擴充成使用佇列來記憶更多的 call back function。


b. 同樣的編譯這個 class :


javac ./overrideActivity.java -cp /Users/macaronics/android-sdks/platforms/android-10/android.jar:/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar -d .


其中引入的 library 有 android-10/android.jar (Android 2.3.3),及 Unity 的 classes.jar。然後輸入 :


jar cvfM ./overrideActivity.jar com/


產生的 overrideActivity.jar 放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下( 此範例將檔案置於 /Users/macaronics/UnityIabProj/Assets/Plugins/Android/) 。



4. 撰寫 iabWrapper.java


a. 主要用於介接 IabHelper 及 Unity ,提供基本的下單功能。程式碼分別敘述如下 :


​package​​​​com.macaronics.iab;​


​import​​​​com.unity3d.player.UnityPlayer;​


​import​​​​android.app.Activity;​​​​import​​​​android.util.Log;​​​​import​​​​android.os.Bundle;​​​​import​​​​android.os.Looper;​​​​import​​​​android.content.Intent;​


​import​​​​java.util.*;​


​import​​​​com.macaronics.iab.util.*;​​​​import​​​​com.macaronics.iab.overrideActivity;​

值得注意的是這裡引入 UnityPlayer,步驟2編譯的 IabHelper (com.macaronics.iab.util),及步驟3編譯的 overrideActivity (com.macaronics.iab.overrideActivity)。


底下是 class iabWrapper 的私有變數部分 :


​public​​​​class​​​​iabWrapper{​​​​    ​​​​private​​​​Activity mActivity;​​​​    ​​​​private​​​​IabHelper mHelper;​​​​    ​​​​private​​​​String mEventHandler;  ​​​​    ​​​​...​

其中 mEventHandler 是回呼 Unity 時,接收訊息的 GameObject 名稱。此 class 有一個 Constructor 函數,三個成員函數及兩個給 IabHelper 回呼的函數,分別敘述如下 :


Constructor 函數在呼叫時給予 GooglePlay API PublicKey (由 GooglePlay 提供),以及回呼 Unity 時,接收訊息的 GameObject 之名稱 : 


​public​​​​iabWrapper(String base64EncodedPublicKey, String strEventHandler){​​​​    ​​​​mActivity =UnityPlayer.currentActivity;​​​​    ​​​​mEventHandler =strEventHandler;​


​    ​​​​if​​​​(mHelper !=​​​​null​​​​){​​​​        ​​​​dispose();​​​​    ​​​​}​


​    ​​​​mHelper =​​​​new​​​​IabHelper(mActivity, base64EncodedPublicKey);​​​​    ​​​​mHelper.enableDebugLogging(​​​​true​​​​);​


​    ​​​​mHelper.startSetup(​​​​new​​​​IabHelper.OnIabSetupFinishedListener() {​​​​        ​​​​public​​​​void​​​​onIabSetupFinished(IabResult result){​​​​            ​​​​if​​​​(!result.isSuccess()){​​​​                ​​​​//回呼 Unity GameObject 之函數 "msgReceiver", 並傳送字串訊息 (JSON 格式)​​​​                ​​​​UnityPlayer.UnitySendMessage(mEventHandler, ​​​​"msgReceiver"​​​​, ​​​​"{\"code\":\"1\",\"ret\":\"false\",\"desc\":\""​​​​+result.toString()+​​​​"\"}"​​​​);​​​​                ​​​​dispose();​​​​                ​​​​return​​​​;​​​​            ​​​​}​​​​            ​​ 

​            ​​​​//回呼 Unity GameObject 之函數 "msgReceiver", 並傳送字串訊息 (JSON 格式)​​​​            ​​​​UnityPlayer.UnitySendMessage(mEventHandler, ​​​​"msgReceiver"​​​​, ​​​​"{\"code\":\"1\",\"ret\":\"true\",\"desc\":\""​​​​+result.toString()+​​​​"\"}"​​​​);​


​            ​​​​//register mHelper​​​​            ​​​​//向 overrideActivity 註冊 onActivityResult 回呼函數,並將資料 Relay 給 mHelper​​​​            ​​​​overrideActivity.registerOnActivityResultCBFunc(​​​​                ​​​​new​​​​overrideActivity.cbEvent(){​​​​                    ​​​​public​​​​boolean​​​​cbEvent(​​​​int​​​​requestCode, ​​​​int​​​​resultCode, Intent data)​​​​                    ​​​​{​​​​                            ​​ 

​                        ​​​​if​​​​(mHelper.handleActivityResult(requestCode, resultCode, data)){​​​​                            ​​​​return​​​​true​​​​;​​​​                        ​​​​}​​​​                        ​​​​else​​​​{​​​​                            ​​​​return​​​​false​​​​;​​​​                        ​​​​}​​​​                    ​​​​}​​​​                ​​​​}​​​​            ​​​​);​​​​        ​​​​}​​​​    ​​​​});​​​​}​

其中的 UnitySendMessage 用來呼叫 Unity GameObject 之函數 "msgReceiver", 並傳送字串訊息 (JSON 格式)。此外值得注意的是此 Constructor 函數還向 overrideActivity 註冊用來接收 onActivityResult 訊息的回呼函數。底下的dispose 函數主要用於釋放 mHelper : 


​public​​​​void​​​​dispose()​​​​{​​​​    ​​​​if​​​​(mHelper !=​​​​null​​​​)​​​​    ​​​​{​​​​        ​​​​mHelper.dispose();​​​​    ​​​​}​​​​    ​​​​mHelper =​​​​null​​​​;​​​​}​

purchase 函數會啟動購買商品的介面,其中第一項參數是 Product SKU (在 GooglePlay 設定的產品 SKU),第二項是回呼 onActivityResult 函數時用來分辨購買動作的 reqCode,第三項是用來確認此筆講買是否合法的 payloadString (購買成功之後, GooglePlay 回覆的資訊裡會包含此字串。)。 


​public​​​​void​​​​purchase(String strSKU, String reqCode, String payloadString)​​​​{​​​​    ​​​​int​​​​intVal =Integer.parseInt(reqCode);​​​​    ​​​​if​​​​(mHelper !=​​​​null​​​​)​​​​        ​​​​mHelper.launchPurchaseFlow(mActivity, strSKU, intVal, mPurchaseFinishedListener, payloadString);​​​​}​

底下是完成購買之後回呼的函數 (onIabPurchaseFinished),這裡把接收的資訊以 JSON 格式轉送給 Unity : 


​IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener =​​​​new​​​​IabHelper.OnIabPurchaseFinishedListener() {​​​​    ​​​​public​​​​void​​​​onIabPurchaseFinished(IabResult result, Purchase purchase) {​​​​        ​​​​if​​​​(result.isFailure()){​​​​            ​​​​UnityPlayer.UnitySendMessage(mEventHandler, ​​​​"msgReceiver"​​​​, ​​​​"{\"code\":\"2\",\"ret\":\"false\",\"desc\":\"\",\"sign\":\"\"}"​​​​);​​​​            ​​​​return​​​​;​​​​        ​​​​}​​​​  ​​ 

​        ​​​​boolean​​​​ret =​​​​false​​​​;​​​​        ​​​​String result_json =​​​​""​​​​;​​​​        ​​​​String result_sign =​​​​""​​​​;​​​​        ​​​​if​​​​(purchase !=​​​​null​​​​){​​​​            ​​​​ret =​​​​true​​​​;​​​​            ​​​​result_json =purchase.getOriginalJson().replace(​​​​'\"'​​​​, ​​​​'\''​​​​);​​​​            ​​​​result_sign =purchase.getSignature();​​​​        ​​​​}​


​        ​​​​UnityPlayer.UnitySendMessage(mEventHandler, ​​​​"msgReceiver"​​​​, ​​​​"{\"code\":\"2\",\"ret\":\""​​​​+ret+​​​​"\",\"desc\":\""​​​​+result_json+​​​​"\",\"sign\":\""​​​​+result_sign+​​​​"\"}"​​​​);​​​​    ​​​​}​​​​};​

產品在購買 (Purchase) 之後若再次購買則會失敗,在 Console 裡可以看到 "Item already owned" 訊息。若產品的類型屬於消耗性的產品,則需要執行 consume 指令才可再次購買 :


​public​​​​void​​​​consume(String itemType, String jsonPurchaseInfo, String signature)​​​​{​​​​    ​​​​String transedJSON =jsonPurchaseInfo.replace(​​​​'\''​​​​, ​​​​'\"'​​​​);​​​​    ​​​​if​​​​(mHelper ==​​​​null​​​​)​​​​        ​​​​return​​​​;​


​    ​​​​Purchase pp =​​​​null​​​​;​​​​    ​​​​try​​​​{​​​​        ​​​​pp =​​​​new​​​​Purchase(itemType, transedJSON, signature);​​​​    ​​​​}​​​​    ​​​​catch​​​​(Exception e){​​​​        ​​​​pp=​​​​null​​​​;​​​​    ​​​​}​


​    ​​​​if​​​​(pp !=​​​​null​​​​){​​​​        ​​​​final​​​​Purchase currpp =pp;​​​​        ​​​​mActivity.runOnUiThread(​​​​new​​​​Runnable(){​​​​            ​​​​public​​​​void​​​​run(){​​​​                ​​​​mHelper.consumeAsync(currpp, mConsumeFinishedListener);​​​​            ​​​​}​​​​        ​​​​});​​​​    ​​​​}​​​​}​

其中參數 itemType 在此範例為 "inapp",jsonPurchaseInfo 及 signature 是在 purchase 完成後, IabHelper 回呼 onIabPurchaseFinished 所給的資料。consume 完成之後回呼的函數如下 :


​IabHelper.OnConsumeFinishedListener mConsumeFinishedListener =​​​​new​​​​IabHelper.OnConsumeFinishedListener() {​​​​    ​​​​public​​​​void​​​​onConsumeFinished(Purchase purchase, IabResult result) {​​​​        ​​​​if​​​​(result.isSuccess()){​​​​            ​​​​Log.d(​​​​"iabWrapper"​​​​, ​​​​"Consumption successful. Provisioning"​​​​);​​​​            ​​​​UnityPlayer.UnitySendMessage(mEventHandler, ​​​​"msgReceiver"​​​​, ​​​​"{\"code\":\"3\",\"ret\":\"true\",\"desc\":\""​​​​+purchase.getOriginalJson().replace(​​​​'\"'​​​​, ​​​​'\''​​​​)+"\​​​​",\"sign\":\""​​​​+purchase.getSignature()+​​​​"\"}"​​​​);​​​​        ​​​​}​​​​        ​​​​else​​​​{​​​​            ​​​​UnityPlayer.UnitySendMessage(mEventHandler, ​​​​"msgReceiver"​​​​, ​​​​"{\"code\":\"3\",\"ret\":\"false\",\"desc\":\"\",\"sign\":\"\"}"​​​​);​​​​        ​​​​}​​​​    ​​​​}​​​​};​


b. 編譯 iabWrapper.java


javac ./iabWrapper.java -cp /Users/macaronics/android-sdks/platforms/android-10/android.jar:/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar:/Users/macaronics/UnityIabProj/Assets/Plugins/Android/iabhelper.jar:/Users/macaronics/UnityIabProj/Assets/Plugins/Android/overrideActivity.jar -d .


其中引入的 library 有 android-10/android.jar (Android 2.3.3), Unity 的 classes.jar,先前編譯的 iabhelper.jar 及 overrideActivity.jar。然後輸入 :


jar cvfM ./iabWrapper.jar com/


產生的 iabWrapper.jar 放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下( 此範例將檔案置於 /Users/macaronics/UnityIabProj/Assets/Plugins/Android/) 。



5. 設定 Permission ( AndroidManifest.xml )


若要執行 GooglePlay Billing 功能則要設定 AndroidManifest.xml,將底下的 AndroidManifest.xml 置於 /Users/macaronics/UnityIabProj/Assets/Plugins/Android 資料夾。


​<?​​​​xml​​​​version​​​​=​​​​"1.0"​​​​encoding​​​​=​​​​"utf-8"​​​​?>​​​​<​​​​manifest​​​​android:versionCode​​​​=​​​​"1"​​​​android:versionName​​​​=​​​​"1.0"​​​​          ​​​​android:installLocation​​​​=​​​​"preferExternal"​​​​          ​​​​package​​​​=​​​​"com.macaronics.iab"​​​​          ​​​​xmlns:android​​​​=​​​​"http://schemas.android.com/apk/res/android"​​​​>​​​​  ​​​​<​​​​supports-screens​​​​android:anyDensity​​​​=​​​​"true"​​​​android:smallScreens​​​​=​​​​"true"​​​​                    ​​​​android:normalScreens​​​​=​​​​"true"​​​​android:largeScreens​​​​=​​​​"true"​​​​                    ​​​​android:xlargeScreens​​​​=​​​​"true"​​​​/>​​​​  ​​​​<​​​​application​​​​android:label​​​​=​​​​"@string/app_name"​​​​               ​​​​android:icon​​​​=​​​​"@drawable/app_icon"​​​​android:debuggable​​​​=​​​​"false"​​​​>​​​​    ​​​​<​​​​activity​​​​android:label​​​​=​​​​"@string/app_name"​​​​              ​​​​android:name​​​​=​​​​"com.macaronics.iab.overrideActivity"​​​​              ​​​​android:screenOrientation​​​​=​​​​"portrait"​​​​              ​​​​android:configChanges​​​​=​​​​"locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"​​             

​              ​​​​>​​​​      ​​​​<​​​​intent-filter​​​​>​​​​        ​​​​<​​​​action​​​​android:name​​​​=​​​​"android.intent.action.MAIN"​​​​/>​​​​        ​​​​<​​​​category​​​​android:name​​​​=​​​​"android.intent.category.LAUNCHER"​​​​/>​​​​      ​​​​</​​​​intent-filter​​​​>​​​​    ​​​​</​​​​activity​​​​>​​​​    ​​​​<​​​​activity​​​​android:label​​​​=​​​​"@string/app_name"​​​​              ​​​​android:name​​​​=​​​​"com.unity3d.player.UnityPlayerActivity"​​​​              ​​​​android:screenOrientation​​​​=​​​​"portrait"​​​​              ​​​​android:configChanges​​​​=​​​​"locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"​​​​              ​​​​/>​​​​    ​​​​<​​​​activity​​​​android:label​​​​=​​​​"@string/app_name"​​​​              ​​​​android:name​​​​=​​​​"com.unity3d.player.UnityPlayerNativeActivity"​​​​              ​​​​android:screenOrientation​​​​=​​​​"portrait"​​​​              ​​​​android:configChanges​​​​=​​​​"locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"​​​​              ​​​​>​​​​      ​​​​<​​​​meta-data​​​​android:name​​​​=​​​​"android.app.lib_name"​​​​android:value​​​​=​​​​"unity"​​​​/>​​​​      ​​​​<​​​​meta-data​​​​android:name​​​​=​​​​"unityplayer.ForwardNativeEventsToDalvik"​​​​                 ​​​​android:value​​​​=​​​​"false"​​​​/>​​​​    ​​​​</​​​​activity​​​​>​​​​    ​​​​<​​​​activity​​​​android:label​​​​=​​​​"@string/app_name"​​​​              ​​​​android:name​​​​=​​​​"com.unity3d.player.VideoPlayer"​​​​              ​​​​android:screenOrientation​​​​=​​​​"behind"​​​​              ​​​​android:configChanges​​​​=​​​​"locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"​​​​              ​​​​/>​​​​  ​​​​</​​​​application​​​​>​​​​  ​​​​<​​​​uses-feature​​​​android:glEsVersion​​​​=​​​​"0x20000"​​​​/>​​​​ ​​ 

​  ​​​​<​​​​uses-permission​​​​android:name​​​​=​​​​"com.android.vending.BILLING"​​​​/>​​​​  ​​​​<​​​​uses-permission​​​​android:name​​​​=​​​​"android.permission.INTERNET"​​​​/>​​​​  ​​​​<​​​​uses-permission​​​​android:name​​​​=​​​​"android.permission.ACCESS_NETWORK_STATE"​​​​/>​


​</​​​​manifest​​​​>​

注意其中的 package="com.macaronics.iab" 要改成你自己的 package 名稱,與 Unity 裡的 Bundle Identifier 相同。此外,這裡還設定了主要啟動的 Activity 為 overrideActivity,以及 uses-permission。 


到目前為止 /Users/macaronics/UnityIabProj/Assets/Plugins/Android 資料夾底下應該會有這些檔案 (iabhelper.jar, iabWrapper.jar, iiabs.jar, overrideActivity.jar, AndroidManifest.xml ) : 


到目前為止應該要有的檔案

6. Unity 部分的 iabWrapper.cs


利用 AndroidJNI 介接 Java 程式 (注意為了接收 java 回傳的字串訊息,此 script (component) 必須加入到名為 iabWrapper 的 GameObject 裡)。 :


​using​​​​UnityEngine;​


​using​​​​System.Collections;​​​​using​​​​System.Collections.Generic;​


​public​​​​class​​​​iabWrapper : MonoBehaviour ​​​​{​​​​    ​​​​public​​​​delegate​​​​void​​​​cbFunc(​​​​object​​​​[] retarr);​​​​    ​​​​cbFunc iabSetupCB =​​​​null​​​​;​​​​    ​​​​cbFunc iabPurchaseCB =​​​​null​​​​;​​​​    ​​​​cbFunc iabConsumeCB =​​​​null​​​​;​


​    ​​​​AndroidJavaObject mIABHelperObj =​​​​null​​​​;​​​​    ​​​​static​​​​iabWrapper g_inst =​​​​null​​​​;​


​    ​​​​void​​​​Start(){​​​​        ​​​​g_inst =​​​​this​​​​;​​​​    ​​​​}​


​    ​​​​static​​​​public​​​​void​​​​init(​​​​string​​​​base64EncodedPublicKey, cbFunc tmpIabSetupCBFunc){​​​​        ​​​​if​​​​(g_inst ==​​​​null​​​​)​​​​            ​​​​return​​​​;​


​        ​​​​g_inst.iabSetupCB =tmpIabSetupCBFunc;​


​        ​​​​dispose();​​​​        ​​​​g_inst.mIABHelperObj =​​​​new​​​​AndroidJavaObject(​​​​"com.macaronics.iab.iabWrapper"​​​​, ​​​​new​​​​object​​​​[2]{base64EncodedPublicKey, ​​​​"iabWrapper"​​​​});​​​​    ​​​​}​


​    ​​​​static​​​​public​​​​void​​​​dispose(){​​​​        ​​​​if​​​​(g_inst ==​​​​null​​​​)​​​​            ​​​​return​​​​;​


​        ​​​​if​​​​(g_inst.mIABHelperObj !=​​​​null​​​​){​​​​            ​​​​g_inst.mIABHelperObj.Call(​​​​"dispose"​​​​);​​​​            ​​​​g_inst.mIABHelperObj.Dispose();​​​​            ​​​​g_inst.mIABHelperObj =​​​​null​​​​;​​​​        ​​​​}​​​​    ​​​​}​


​        ​​​​...​

其中 init 負責呼叫 iabWrapper.java 之建構函數並建立該物件,dispose 則是負責刪除釋放 iabWrapper.java 物件。底下是主要的兩個功能,purchase 和 consume : 


​static​​​​public​​​​void​​​​purchase(​​​​string​​​​strSKU, ​​​​int​​​​reqCode, ​​​​string​​​​payload, cbFunc tmpIabPurchaseCBFunc){​​​​    ​​​​if​​​​(g_inst ==​​​​null​​​​)​​​​        ​​​​return​​​​;​


​    ​​​​g_inst.iabPurchaseCB =tmpIabPurchaseCBFunc;​


​    ​​​​if​​​​(g_inst.mIABHelperObj !=​​​​null​​​​){​​​​        ​​​​g_inst.mIABHelperObj.Call(​​​​"purchase"​​​​, ​​​​new​​​​object​​​​[3]{strSKU, reqCode.ToString(), payload});​​​​    ​​​​}​​​​}​


​static​​​​public​​​​void​​​​consume_inapp(​​​​string​​​​strPurchaseJsonInfo, ​​​​string​​​​strSignature, cbFunc tmpIabConsumeCBFunc){​​​​    ​​​​if​​​​(g_inst ==​​​​null​​​​)​​​​        ​​​​return​​​​;​


​    ​​​​g_inst.iabConsumeCB =tmpIabConsumeCBFunc;​


​    ​​​​if​​​​(g_inst.mIABHelperObj !=​​​​null​​​​){​​​​        ​​​​g_inst.mIABHelperObj.Call(​​​​"consume"​​​​, ​​​​new​​​​object​​​​[3]{​​​​"inapp"​​​​, strPurchaseJsonInfo, strSignature});​​​​    ​​​​}​​​​}​

其中先將 callback 函數記錄起來,然後呼叫其對應的 java function。最後一部分是 msgReceiver,負責接收 java 回呼的結果並將資訊回呼對應的 callback function (先前呼叫 purchase 或是 consume 所給予的 callback function)。 


​void​​​​msgReceiver(​​​​string​​​​msg){​​​​    ​​​​if​​​​(g_inst ==​​​​null​​​​)​​​​        ​​​​return​​​​;​​​​ ​​ 

​    ​​​​//parse json​​​​    ​​​​Dictionary<​​​​string​​​​, ​​​​object​​​​> cache =(Dictionary<​​​​string​​​​, ​​​​object​​​​>)MiniJSON.Json.Deserialize(msg);​


​    ​​​​//dispatch msg​​​​    ​​​​if​​​​(cache.ContainsKey(​​​​"code"​​​​)==​​​​true​​​​){​​​​        ​​​​int​​​​val =0;​​​​        ​​​​int​​​​.TryParse((​​​​string​​​​)cache[​​​​"code"​​​​], ​​​​out​​​​val);​​​​        ​​​​switch​​​​(val){​​​​            ​​​​case​​​​0:{​​​​                ​​​​//unknown​​​​                ​​​​Debug.Log(​​​​"Unity-iabWrappe :cannot parse cache[code]"​​​​);​


​            ​​​​}​​​​            ​​​​break​​​​;​


​            ​​​​case​​​​1:{​​​​                ​​​​//OnIabSetupFinishedListener​​​​                ​​​​if​​​​(cache.ContainsKey(​​​​"ret"​​​​)==​​​​true​​​​){​​​​                    ​​​​string​​​​retval =(​​​​string​​​​)cache[​​​​"ret"​​​​];​​​​                    ​​​​if​​​​(retval ==​​​​"true"​​​​){​​​​                        ​​​​//可使用​​​​                        ​​​​if​​​​(iabSetupCB !=​​​​null​​​​){​​​​                            ​​​​iabSetupCB( ​​​​new​​​​object​​​​[1]{​​​​true​​​​} );​​​​                        ​​​​}​


​                    ​​​​}​​​​                    ​​​​else​​​​if​​​​(retval ==​​​​"false"​​​​){​​​​                        ​​​​//不可使用​​​​                        ​​​​if​​​​(iabSetupCB !=​​​​null​​​​)​​​​                        ​​​​{​​​​                            ​​​​iabSetupCB( ​​​​new​​​​object​​​​[1]{​​​​false​​​​} );​​​​                        ​​​​}​​​​                    ​​​​}​​​​else​​​​{​​​​                        ​​​​Debug.Log(​​​​"Unity-iabWrapper :cannot parse cache[ret], code=1"​​​​);​​​​                    ​​​​}​​​​                ​​​​}​​​​            ​​​​}​​​​            ​​​​break​​​​;​


​            ​​​​case​​​​2:{​​​​                ​​​​//onIabPurchaseFinished​​​​                ​​​​if​​​​(cache.ContainsKey(​​​​"ret"​​​​)==​​​​true​​​​){​​​​                    ​​​​string​​​​retval =(​​​​string​​​​)cache[​​​​"ret"​​​​];​​​​                    ​​​​if​​​​(retval ==​​​​"true"​​​​){​​​​                        ​​​​//可使用​​​​                        ​​​​if​​​​(iabPurchaseCB !=​​​​null​​​​){​​​​                            ​​​​iabPurchaseCB( ​​​​new​​​​object​​​​[3]{​​​​true​​​​, (​​​​string​​​​)cache[​​​​"desc"​​​​], (​​​​string​​​​)cache[​​​​"sign"​​​​]} );​​​​                        ​​​​}​


​                    ​​​​}​​​​                    ​​​​else​​​​if​​​​(retval ==​​​​"false"​​​​){​​​​                        ​​​​//不可使用​​​​                        ​​​​if​​​​(iabPurchaseCB !=​​​​null​​​​)​​​​                        ​​​​{​​​​                            ​​​​iabPurchaseCB( ​​​​new​​​​object​​​​[3]{​​​​false​​​​, ​​​​""​​​​, ​​​​""​​​​} );​​​​                        ​​​​}​


​                    ​​​​}​​​​                    ​​​​else​​​​{​​​​                        ​​​​Debug.Log(​​​​"Unity-iabWrapper  :cannot parse cache[ret], code=2"​​​​);​​​​                    ​​​​}​​​​                ​​​​}​​​​            ​​​​}​​​​            ​​​​break​​​​;​


​            ​​​​case​​​​3:{​​​​                ​​​​//OnConsumeFinishedListener​​​​                ​​​​if​​​​(cache.ContainsKey(​​​​"ret"​​​​)==​​​​true​​​​){​​​​                    ​​​​string​​​​retval =(​​​​string​​​​)cache[​​​​"ret"​​​​];​​​​                    ​​​​if​​​​(retval ==​​​​"true"​​​​){​​​​                        ​​​​//可使用​​​​                        ​​​​if​​​​(iabConsumeCB !=​​​​null​​​​)​​​​                        ​​​​{​​​​                            ​​​​iabConsumeCB( ​​​​new​​​​object​​​​[3]{​​​​true​​​​, (​​​​string​​​​)cache[​​​​"desc"​​​​], (​​​​string​​​​)cache[​​​​"sign"​​​​]} );​​​​                        ​​​​}​


​                    ​​​​}​​​​else​​​​if​​​​(retval ==​​​​"false"​​​​){​​​​                        ​​​​//不可使用​​​​                        ​​​​if​​​​(iabConsumeCB !=​​​​null​​​​)​​​​                        ​​​​{​​​​                            ​​​​iabConsumeCB( ​​​​new​​​​object​​​​[3]{​​​​false​​​​, ​​​​""​​​​, ​​​​""​​​​} );​​​​                        ​​​​}​


​                    ​​​​}​​​​                    ​​​​else​​​​{​​​​                        ​​​​Debug.Log(​​​​"Unity-iabWrapper :cannot parse cache[ret], code=3"​​​​);​


​                    ​​​​}​​​​                ​​​​}​​​​            ​​​​}​​​​            ​​​​break​​​​;​​​​        ​​​​}​​​​    ​​​​}​​​​}​

程式碼先將 JSON 字串轉為 object 物件並依照物件種類做對應的處理,其中 purchase 的 callback (case 2),回傳的參數依序為 : 1. 是否成功,2. purchase 結果字串以及 3. purchase 結果之 signature (可作為驗証之用,參考這篇)。 



7. 實作 IAB APP


建立一個 script 名為 main,並將此 script 加入至 Camera。這裡實作一個 purchase 功能,並在 purchase 完成之後立刻執行 consume。


​using​​​​UnityEngine;​​​​using​​​​UnityEngine;​​​​using​​​​System.Collections;​​​​ ​​ 

​public​​​​class​​​​main : MonoBehaviour {​​​​ ​​ 

​    ​​​​// Use this for initialization​​​​    ​​​​void​​​​Start () {​​​​        ​​​​iabWrapper.init(​​​​            ​​​​"PUBLIC_KEY"​​​​,​​​​            ​​​​delegate​​​​(​​​​object​​​​[] ret){​​​​                ​​​​if​​​​(​​​​true​​​​==(​​​​bool​​​​)ret[0]){​​​​                    ​​​​Debug.Log(​​​​"iab successfully initialized"​​​​);​​​​                ​​​​}​​​​                ​​​​else​​​​{​​​​                    ​​​​Debug.Log(​​​​"failed to initialize iab"​​​​);​​​​                ​​​​}​​​​            ​​​​});​​​​    ​​​​}​​​​ ​​ 

​    ​​​​void​​​​OnGUI(){​​​​        ​​​​if​​​​(GUI.Button(​​​​new​​​​Rect(0, 0, 100, 100), ​​​​"purchase"​​​​)){​​​​            ​​​​iabWrapper.purchase(​​​​"PRODUCT_SKU"​​​​, 10001, ​​​​"PRODUCT_SKU_AND_USER_ID_AND_DATE"​​​​,​​​​                ​​​​delegate​​​​(​​​​object​​​​[] ret){​​​​                    ​​​​if​​​​(​​​​false​​​​==(​​​​bool​​​​)ret[0]){​​​​                        ​​​​Debug.Log(​​​​"purchase cancelled"​​​​);​​​​                    ​​​​}​​​​                    ​​​​else​​​​{​​​​                        ​​​​string​​​​purchaseinfo =(​​​​string​​​​)ret[1];​​​​                        ​​​​string​​​​signature =(​​​​string​​​​)ret[2];​​​​                        ​​​​iabWrapper.consume_inapp(purchaseinfo, signature, ​​​​                            ​​​​delegate​​​​(​​​​object​​​​[] ret2){​​​​                                ​​​​if​​​​(​​​​false​​​​==(​​​​bool​​​​)ret2[0])​​​​                                ​​​​{​​​​                                    ​​​​Debug.Log(​​​​"failed to consume product"​​​​);​​​​                                ​​​​}​​​​                            ​​​​});​​​​                    ​​​​}​​​​                ​​​​});​​​​        ​​​​}​​​​    ​​​​}​​​​ ​​ 

​    ​​​​void​​​​OnApplicationQuit(){​​​​        ​​​​iabWrapper.dispose();​​​​    ​​​​}​​​​}​

其中的 PUBLIC_KEY,更換成你的 Public Key,代入 purchase 函數的參數更改成你的參數。