[Unity] 透過 Soomla 來實做 In-app purchase,以Android 為例 (Google Billing Service) , 和 Admob 不相容問題處理

Soomla  Store 的 Assets Store Link https://www.assetstore.unity3d.com/en/#!/content/6103

但是上面這個連結的Plugin 已經太舊了,且上面Youtube 的教學影片也是舊的,所以建議還是直接到官方的github 比較正確https://github.com/soomla/unity3d-store

那就開始來啦


  • 首先Android  APK 設定 (須設定可以上傳的 就是sign key 和  package name 要用真的)

  • 去下載 plugin : http://library.soom.la/fetch/unity3d-store/1.9.2?cf=github
  • 匯入剛下載plugin
  • 到 Soomla 的設定
  • 設定如下 Soomla Secret : 自行決定加密的key , Google Play API Key : 等下再輸入

  • 到 google play Developer Console https://play.google.com/apps/publish/,新增一個應用程式,或是選擇一個應用程式
  • 到 服務和API 選項,複製授權金鑰
  • 然後把授權金鑰貼到 Soomla 的Google Play API Key 上
  • 此時再到Google Console 的 應用程式商品建立商品
  • 建立完成回到Unity 建立一個 Billing Assert C# Script <= 用來記錄商品的資訊  (FOURHUNDMUFF_PACK_PRODUCT_ID  <== 這裡是要填入之前Google play 的商品ID  ) 
using UnityEngine;
using System.Collections;
using Soomla.Store;
using System;
using System.Collections.Generic;

public class GoodAssets : IStoreAssets {

    public VirtualCategory[] GetCategories() {
        return new VirtualCategory[] { GENERAL_CATEGORY };
    }

    public VirtualCurrency[] GetCurrencies() {
        return new VirtualCurrency[] { DIAMOND_CURRENCY };
    }

    public VirtualCurrencyPack[] GetCurrencyPacks() {
        return new VirtualCurrencyPack[] { TENMUFF_PACK };
    }

    public VirtualGood[] GetGoods() {
        return new VirtualGood[] { };
    }

    public int GetVersion() {
        return 0;
    }

    /** Virtual Currencies **/

    public const string DIAMOND_CURRENCY_ITEM_ID = "xxxxx_20";  //這個是程式用的ID

    public const string FOURHUNDMUFF_PACK_PRODUCT_ID = "android.test.purchased"; // Google play product ID , 再測試階段可以用 android.test.purchased


    public static VirtualCurrency DIAMOND_CURRENCY = new VirtualCurrency(
            "DIAMOND X 20",                                      // name
            "",                                             // description
            DIAMOND_CURRENCY_ITEM_ID                         // item id
    );

    public static VirtualCurrencyPack TENMUFF_PACK = new VirtualCurrencyPack(
        "10 Coin",                                   // name
        "Test refund of an item",                       // description
        "muffins_10",                                   // item id
        10,                                             // number of currencies in the pack
        DIAMOND_CURRENCY_ITEM_ID,                        // the currency associated with this pack
        new PurchaseWithMarket(FOURHUNDMUFF_PACK_PRODUCT_ID, 0.99)
);

    /** Virtual Categories **/
    // The muffin rush theme doesn't support categories, so we just put everything under a general category.
    public static VirtualCategory GENERAL_CATEGORY = new VirtualCategory(
            "General", new List<string>(new string[] { })
    );


}

  • 那程式啟動時 執行這一段Code 即可使用 Billing Sevice
SoomlaStore.Initialize(new GoodAssets());
  • 那要購買只要撰寫以下Code 即可

        VirtualCurrency coin = (VirtualCurrency)StoreInfo.GetItemByItemId(GoodAssets.DIAMOND_CURRENCY_ITEM_ID);
        StoreInventory.BuyItem(coin.ItemId);

執行效果如下



那看到執行 出現 VISA xxx-FAKE ,不要緊張 ,因為你的 product id 試用測試的,等要上架再換成真的即可,那此時按BUY 會成功購買,但是不會扣款

  • 查詢買了多少物品
VirtualCurrency coin = (VirtualCurrency)StoreInfo.GetItemByItemId(GoodAssets.DIAMOND_CURRENCY_ITEM_ID);
int greenHatsBalance = StoreInventory.GetItemBalance(coin.ItemId);

  • 剩下API 可以參考官方github  https://github.com/soomla/unity3d-store

Storage & Meta-Data

When you initialize SoomlaStore, it automatically initializes two other classes: StoreInventory andStoreInfo:
  • StoreInventory is a convenience class to let you perform operations on VirtualCurrencies and VirtualGoods. Use it to fetch/change the balances of VirtualItems in your game (using their ItemIds!)
  • StoreInfo is where all meta data information about your specific game can be retrieved. It is initialized with your implementation of IStoreAssets and you can use it to retrieve information about your specific game.
The on-device storage is encrypted and kept in a SQLite database. SOOMLA is preparing a cloud-based storage service that will allow this SQLite to be synced to a cloud-based repository that you'll define.
Example Usages
  • Get VirtualCurrency with itemId "currency_coin":
    VirtualCurrency coin = (VirtualCurrency) StoreInfo.GetItemByItemId("currency_coin");
  • Give the user 10 pieces of a virtual currency with itemId "currency_coin":
    StoreInventory.GiveItem("currency_coin", 10);
  • Take 10 virtual goods with itemId "green_hat":
    StoreInventory.TakeItem("green_hat", 10);
  • Get the current balance of green hats (virtual goods with itemId "green_hat"):
    int greenHatsBalance = StoreInventory.GetItemBalance("green_hat");


PS 如果您的專案已經使用 Admob plugin 會發生以下的錯誤

CommandInvokationFailure: Unable to convert classes into dex format. See the Console for details. C:/Program Files/Java/jdk1.8.0_60\bin\java.exe -Xmx2048M -Dcom.android.sdkmanager.toolsdir="C:/Users/Saksit/AppData/Local/Android/sdk1\tools" -Dfile.encoding=UTF8 -jar "C:/Program Files/Unity/Editor/Data/BuildTargetTools/AndroidPlayer\sdktools.jar" -
stderr[
UNEXPECTED TOP-LEVEL EXCEPTION: java.util.zip.ZipException: error in opening zip file at java.util.zip.ZipFile.open(Native Method) at java.util.zip.ZipFile.(ZipFile.java:219) at java.util.zip.ZipFile.(ZipFile.java:149) at java.util.zip.ZipFile.(ZipFile.java:163) at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:244) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166) at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158) at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144) at com.android.dx.command.dexer.Main.processOne(Main.java:672) at com.android.dx.command.dexer.Main.processAllFiles(Main.java:574) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:311) at com.android.dx.command.dexer.Main.run(Main.java:277) at com.android.dx.command.dexer.Main.main(Main.java:245) at com.android.dx.command.Main.main(Main.java:106) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at SDKMain.main(SDKMain.java:129)
UNEXPECTED TOP-LEVEL EXCEPTION: java.util.zip.ZipException: error in opening zip file at java.util.zip.ZipFile.open(Native Method) at java.util.zip.ZipFile.(ZipFile.java:219) at java.util.zip.ZipFile.(ZipFile.java:149) at java.util.zip.ZipFile.(ZipFile.java:163) at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:244) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166) at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158) at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144) at com.android.dx.command.dexer.Main.processOne(Main.java:672) at com.android.dx.command.dexer.Main.processAllFiles(Main.java:574) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:311) at com.android.dx.command.dexer.Main.run(Main.java:277) at com.android.dx.command.dexer.Main.main(Main.java:245) at com.android.dx.command.Main.main(Main.java:106) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at SDKMain.main(SDKMain.java:129)
UNEXPECTED TOP-LEVEL EXCEPTION: java.util.zip.ZipException: error in opening zip file at java.util.zip.ZipFile.open(Native Method) at java.util.zip.ZipFile.(ZipFile.java:219) at java.util.zip.ZipFile.(ZipFile.java:149) at java.util.zip.ZipFile.(ZipFile.java:163) at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:244) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166) at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158) at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144) at com.android.dx.command.dexer.Main.processOne(Main.java:672) at com.android.dx.command.dexer.Main.processAllFiles(Main.java:574) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:311) at com.android.dx.command.dexer.Main.run(Main.java:277) at com.android.dx.command.dexer.Main.main(Main.java:245) at com.android.dx.command.Main.main(Main.java:106) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at SDKMain.main(SDKMain.java:129) Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/android/vending/billing/IInAppBillingService; Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/android/vending/billing/IInAppBillingService$Stub; Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/android/vending/billing/IInAppBillingService$Stub$Proxy; 6 errors; aborting ] stdout[ processing archive bin\classes.jar... processing com/unity3d/player/a$1.class... processing com/unity3d/player/a$2.class... processing com/unity3d/player/a$a.class... processing com/unity3d/player/a.class... processing com/unity3d/player/b$1.class... processing com/unity3d/player/b$2.class... processing com/unity3d/player/b.class... processing com/unity3d/player/c.class... processing com/unity3d/player/d$1.class... processing com/unity3d/player/d$2.class... processing com/unity3d/player/d.class... processing com/unity3d/player/e.class... processing com/unity3d/player/f.class... processing com/unity3d/player/g.class... processing com/unity3d/player/h.class... processing com/unity3d/player/i.class... processing com/unity3d/player/j$1.class... processing com/unity3d/player/j$2$1$1.class... processing com/unity3d/player/j$2$1.class... processing com/unity3d/player/j$2.class... processing com/unity3d/player/j.class... processing com/unity3d/player/k$1.class... processing com/unity3d/player/k.class... processing com/unity3d/player/l.class... processing com/unity3d/player/m.class... processing com/unity3d/player/n$1.class... processing com/unity3d/player/n.class... processing com/unity3d/player/NativeLoader.class... processing com/unity3d/player/o.class... processing com/unity3d/player/ReflectionHelper$1.class... processing com/unity3d/player/ReflectionHelper$a.class... processing com/unity3d/player/ReflectionHelper.class... processing com/unity3d/player/p.class... processing com/unity3d/player/q$1.class... processing com/unity3d/player/q$2.class... processing com/unity3d/player/q$3.class... processing com/unity3d/player/q.class... processing com/unity3d/player/r.class... processing com/unity3d/player/s.class... processing com/unity3d/player/UnityPlayer$1.class... processing com/unity3d/player/UnityPlayer$2.class... processing com/unity3d/player/UnityPlayer$3.class... processing com/unity3d/player/UnityPlayer$4.class... processing com/unity3d/player/UnityPlayer$5.class... processing com/unity3d/player/UnityPlayer$6.class... processing com/unity3d/player/UnityPlayer$7.class... processing com/unity3d/player/UnityPlayer$8.class... processing com/unity3d/player/UnityPlayer$9.class... processing com/unity3d/player/UnityPlayer$10.class... processing com/unity3d/player/UnityPlayer$11.class... processing com/unity3d/player/UnityPlayer$12$1.class... processing com/unity3d/player/UnityPlayer$12.class... processing com/unity3d/player/UnityPlayer$13.class... processing com/unity3d/player/UnityPlayer$14.class... processing com/unity3d/player/UnityPlayer$15.class... processing com/unity3d/player/UnityPlayer$16.class... processing com/unity3d/player/UnityPlayer$17.class... processing com/unity3d/player/UnityPlayer$18.class... processing com/unity3d/player/UnityPlayer$a.class... processing com/unity3d/player/UnityPlayer$b.class... processing com/unity3d/player/UnityPlayer$c.class... processing com/unity3d/player/UnityPlayer.class... processing com/unity3d/player/UnityPlayerActivity.class... processing com/unity3d/player/UnityPlayerNativeActivity.class... processing com/unity3d/player/UnityPlayerProxyActivity.class... processing com/unity3d/player/t.class... processing com/unity3d/player/u$1.class... processing com/unity3d/player/u.class... processing com/unity3d/player/WWW.class... processing org/fmod/FMODAudioDevice$a.class... processing org/fmod/FMODAudioDevice.class... processing org/fmod/a.class... processing bitter/jnibridge/JNIBridge$a.class... processing bitter/jnibridge/JNIBridge.class... processing bin\classes.\com\google\android\gms\R$attr.class... processing bin\classes.\com\google\android\gms\R$color.class... processing bin\classes.\com\google\android\gms\R$drawable.class... processing bin\classes.\com\google\android\gms\R$id.class... processing bin\classes.\com\google\android\gms\R$integer.class... processing bin\classes.\com\google\android\gms\R$layout.class... processing bin\classes.\com\google\android\gms\R$raw.class... processing bin\classes.\com\google\android\gms\R$string.class... processing bin\classes.\com\google\android\gms\R$style.class... processing bin\classes.\com\google\android\gms\R$styleable.class... processing bin\classes.\com\google\android\gms\R.class... processing bin\classes.\com\google\unity\R$attr.class... processing bin\classes.\com\google\unity\R$color.class... processing bin\classes.\com\google\unity\R$drawable.class... processing bin\classes.\com\google\unity\R$id.class... processing bin\classes.\com\google\unity\R$integer.class... processing bin\classes.\com\google\unity\R$layout.class... processing bin\classes.\com\google\unity\R$raw.class... processing bin\classes.\com\google\unity\R$string.class... processing bin\classes.\com\google\unity\R$style.class... processing bin\classes.\com\google\unity\R$styleable.class... processing bin\classes.\com\google\unity\R.class... processing bin\classes.\com\Thx4Play\LearnDay\R$attr.class... processing bin\classes.\com\Thx4Play\LearnDay\R$color.class... processing bin\classes.\com\Thx4Play\LearnDay\R$drawable.class... processing bin\classes.\com\Thx4Play\LearnDay\R$id.class... processing bin\classes.\com\Thx4Play\LearnDay\R$integer.class... processing bin\classes.\com\Thx4Play\LearnDay\R$layout.class... processing bin\classes.\com\Thx4Play\LearnDay\R$raw.class... processing bin\classes.\com\Thx4Play\LearnDay\R$string.class... processing bin\classes.\com\Thx4Play\LearnDay\R$style.class... processing bin\classes.\com\Thx4Play\LearnDay\R$styleable.class... processing bin\classes.\com\Thx4Play\LearnDay\R.class... processing bin\classes.\com\unity3d\ads\android\R$attr.class... processing bin\classes.\com\unity3d\ads\android\R$color.class... processing bin\classes.\com\unity3d\ads\android\R$drawable.class... processing bin\classes.\com\unity3d\ads\android\R$id.class... processing bin\classes.\com\unity3d\ads\android\R$integer.class... processing bin\classes.\com\unity3d\ads\android\R$layout.class... processing bin\classes.\com\unity3d\ads\android\R$raw.class... processing bin\classes.\com\unity3d\ads\android\R$string.class... processing bin\classes.\com\unity3d\ads\android\R$style.class... processing bin\classes.\com\unity3d\ads\android\R$styleable.class... processing bin\classes.\com\unity3d\ads\android\R.class... processing archive plugins.\AndroidStore.jar... ignored resource META-INF/ ignored resource META-INF/MANIFEST.MF ignored resource com/ ignored resource com/soomla/ ignored resource com/soomla/rewards/ ignored resource com/soomla/store/ ignored resource com/soomla/store/billing/ ignored resource com/soomla/store/data/ ignored resource com/soomla/store/domain/ ignored resource com/soomla/store/domain/virtualCurrencies/ ignored resource com/soomla/store/domain/virtualGoods/ ignored resource com/soomla/store/events/ ignored resource com/soomla/store/exceptions/ ignored resource com/soomla/store/purchaseTypes/ processing com/soomla/rewards/VirtualItemReward.class... processing com/soomla/store/IStoreAssets.class... processing com/soomla/store/SoomlaStore$1.class... processing com/soomla/store/SoomlaStore$2.class... processing com/soomla/store/SoomlaStore$3$1$1.class... processing com/soomla/store/SoomlaStore$3$1.class... processing com/soomla/store/SoomlaStore$3.class... processing com/soomla/store/SoomlaStore$4$1.class... processing com/soomla/store/SoomlaStore$4.class... processing com/soomla/store/SoomlaStore$5$1.class... processing com/soomla/store/SoomlaStore$5.class... processing com/soomla/store/SoomlaStore$6.class... processing com/soomla/store/SoomlaStore$HandleSuccessfulPurchasesFinishedHandler.class... processing com/soomla/store/SoomlaStore.class... processing com/soomla/store/StoreConfig.class... processing com/soomla/store/StoreInventory.class... processing com/soomla/store/billing/IIabService.class... processing com/soomla/store/billing/IabCallbacks$IabInitListener.class... processing com/soomla/store/billing/IabCallbacks$OnConsumeListener.class... processing com/soomla/store/billing/IabCallbacks$OnFetchSkusDetailsListener.class... processing com/soomla/store/billing/IabCallbacks$OnPurchaseListener.class... processing com/soomla/store/billing/IabCallbacks$OnRestorePurchasesListener.class... processing com/soomla/store/billing/IabCallbacks.class... processing com/soomla/store/billing/IabException.class... processing com/soomla/store/billing/IabHelper$1.class... processing com/soomla/store/billing/IabHelper$2.class... processing com/soomla/store/billing/IabHelper$3.class... processing com/soomla/store/billing/IabHelper$4.class... processing com/soomla/store/billing/IabHelper$5.class... processing com/soomla/store/billing/IabHelper$6.class... processing com/soomla/store/billing/IabHelper$7.class... processing com/soomla/store/billing/IabHelper$8.class... processing com/soomla/store/billing/IabHelper$FetchSkusDetailsFinishedListener.class... processing com/soomla/store/billing/IabHelper$OnIabPurchaseFinishedListener.class... processing com/soomla/store/billing/IabHelper$OnIabSetupFinishedListener.class... processing com/soomla/store/billing/IabHelper$RestorePurchasessFinishedListener.class... processing com/soomla/store/billing/IabHelper.class... processing com/soomla/store/billing/IabInventory.class... processing com/soomla/store/billing/IabPurchase.class... processing com/soomla/store/billing/IabResult.class... processing com/soomla/store/billing/IabSkuDetails.class... processing com/soomla/store/data/StorageManager.class... processing com/soomla/store/data/StoreInfo.class... processing com/soomla/store/data/StoreJSONConsts.class... processing com/soomla/store/data/VirtualCurrencyStorage.class... processing com/soomla/store/data/VirtualGoodsStorage.class... processing com/soomla/store/data/VirtualItemStorage.class... processing com/soomla/store/domain/MarketItem.class... processing com/soomla/store/domain/PurchasableVirtualItem.class... processing com/soomla/store/domain/VirtualCategory.class... processing com/soomla/store/domain/VirtualItem.class... processing com/soomla/store/domain/virtualCurrencies/VirtualCurrency.class... processing com/soomla/store/domain/virtualCurrencies/VirtualCurrencyPack.class... processing com/soomla/store/domain/virtualGoods/EquippableVG$EquippingModel.class... processing com/soomla/store/domain/virtualGoods/EquippableVG.class... processing com/soomla/store/domain/virtualGoods/LifetimeVG.class... processing com/soomla/store/domain/virtualGoods/SingleUsePackVG.class... processing com/soomla/store/domain/virtualGoods/SingleUseVG.class... processing com/soomla/store/domain/virtualGoods/UpgradeVG.class... processing com/soomla/store/domain/virtualGoods/VirtualGood.class... processing com/soomla/store/events/BillingNotSupportedEvent.class... processing com/soomla/store/events/BillingSupportedEvent.class... processing com/soomla/store/events/CurrencyBalanceChangedEvent.class... processing com/soomla/store/events/GoodBalanceChangedEvent.class... processing com/soomla/store/events/GoodEquippedEvent.class... processing com/soomla/store/events/GoodUnEquippedEvent.class... processing com/soomla/store/events/GoodUpgradeEvent.class... processing com/soomla/store/events/IabServiceStartedEvent.class... processing com/soomla/store/events/IabServiceStoppedEvent.class... processing com/sooml

這時候到 Assets/Plugins/Android/GoogleMobileAdsPlugin/libs 目錄下把 in-app-billing-service-aidl.jar 刪除 即可  (參考  :  http://answers.soom.la/t/solved-soomla-plugin-error-commandinvokationfailure-unable-to-convert-classes-into-dex-format/3350/2)

留言

這個網誌中的熱門文章

angular 如何Http 如何設定 CORS (Cross-Origin Resource Sharing)

Google Map 單車路徑計算坡度和角度小工具

Google URL Shortener API 快速教學