赞
踩
20230825更新
因为谷歌新政策要求,目前需要支持V5 V6,更新也很简单,将下面的改成5.0或6.0的aar即可
implementation("com.android.billingclient:billing:4.0.0")
改成
implementation("com.android.billingclient:billing:5.0.0")
或
implementation("com.android.billingclient:billing:6.0.0")
代码都是兼容的,亲测有效
-------------------------------------------------------------------
Unity接GooglePlay In-App Billing坑还是蛮多的,各种坑。
这次介绍的是第二种,使用安卓源生方式接入,因为该方式一劳永逸,新项目可以很快就完成接入。
为什么不用第一种呢?直接导入IAP插件,然后设置参数,就可以很快实现了,该方式可以直接参考google文档。
虽然该方式只需要导入插件,然后进行一些参数的设置,但是此方式特别麻烦的一点是需要在Unity中开启Service,但是开启后又得填一大堆信息,巨麻烦无比,而且Unity的网络简直不能看,要么是打不开,要么是卡半死,而且网站老变,以及没有完整的文档。这些都是很恶心人的事情,甚至还要创建组织啥的,反正谁用谁恶心。
----------------------------------分割线------------------------------------------------------------------------
接入之前需要的储备知识是:Unity如何与Android交互
我们的目标是导出自己封装的jar(里面封装了接口供Unity调用,也就是桥接层),以及找到谷歌官方提供的Billing V4插件(aar)。
因为从2021.8.2起,谷歌要求必须接入V3版以上的插件。所以我们这次干脆接了最新的V4.
接入文档可以参考指南:从 AIDL 迁移到 Google Play 结算库的迁移指南
- implementation("com.android.billingclient:billing:4.0.0")
- 注意:plugins这边要改为'com.android.library',导出才会是aar,否则是apk.
plugins { id 'com.android.library' } android { compileSdkVersion 30 defaultConfig { //applicationId "com.egogame.iapforgoogleplay" minSdkVersion 19 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.3.0' implementation 'org.jetbrains:annotations:15.0' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' implementation("com.android.billingclient:billing:4.0.0") }
- package com.egogame;
-
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import org.json.JSONArray;
- import org.json.JSONException;
- import org.json.JSONObject;
-
- import android.app.Activity;
- import android.content.Context;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Looper;
- import android.widget.Toast;
-
-
- public class BaseMainActivity{
- public static String UNITY_GO_NAME="IAP";
- public static final String LOG_TAG = "EgoGameLog";
-
- protected Handler uiHandler = new Handler(Looper.getMainLooper());
-
- //unity项目启动时的上下文
- private Activity unityActivity;
- private Context context;
-
- public void Init(final String goName,final String googlePlayPublicKey){
- PrintLog("Init:"+goName+"===="+googlePlayPublicKey);
- uiHandler.post(new Runnable() {
-
- @Override
- public void run() {
- UNITY_GO_NAME=goName;
- OnInitHandle(googlePlayPublicKey);
- }
- });
- }
-
- public void PrintLog(final String message,final Boolean toast){
- android.util.Log.d(LOG_TAG, message);
- uiHandler.post(new Runnable() {
- @Override
- public void run() {
- if(toast) Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
- }
- });
- }
-
- public boolean IsIAPSupported(){
- return false;
- }
-
- final public void RequstProduct(final String idsJson){
- uiHandler.post(new Runnable() {
-
- @Override
- public void run() {
- String[] realProducts=null;
- try {
- JSONObject jObject=new JSONObject(idsJson);
- JSONArray jArray=jObject.getJSONArray("productIds");
- realProducts=new String[jArray.length()];
- for (int i = 0; i < jArray.length(); i++) {
- realProducts[i]=jArray.getString(i);
- }
- } catch (Exception e) {
- PrintLog("RequstProduct数据传输错误:"+e.getMessage());
- }
- if(realProducts!=null){
- OnRequstProduct(realProducts);
- }else{
- RequestProductsFail("数据解析错误:"+idsJson);
- }
- }
- });
- }
-
- final public void BuyProduct(final String productJson){
- uiHandler.post(new Runnable() {
-
- @Override
- public void run() {
- try {
- JSONObject jObject=new JSONObject(productJson);
- String productId=jObject.getString("productId");
- boolean isConsumable=jObject.getBoolean("isConsumable");
-
- OnBuyProduct(productId,isConsumable);
- } catch (Exception e) {
- PrintLog("BuyProduct数据传输错误:"+e.getMessage());
- }
- }
- });
- }
-
- protected void OnInitHandle(String googlePlayPublicKey){
- }
-
- protected void OnRequstProduct(String[] productId){
- }
- protected void OnBuyProduct(String productId,boolean isConsumable){
- }
-
- protected void BuyComplete(String productId){
- PrintLog("购买成功:"+productId);
- SendUnityMessage("ProductBuyComplete", productId);
- }
-
- protected void BuyCancle(String productId){
- PrintLog("购买取消:"+productId);
- SendUnityMessage("ProductBuyCancled", productId);
- }
-
- protected void BuyFail(String productId,String error){
- PrintLog("购买失败:"+productId+"原因:"+error);
- try {
- JSONObject jObject=new JSONObject();
- jObject.put("productId", productId);
- jObject.put("error", error);
- SendUnityMessage("ProductBuyFailed", jObject.toString());
- } catch (JSONException e) {
- PrintLog("BuyFail数据错误:"+e.getMessage());
- }
- }
-
- protected void RequestProductsFail(String message){
- SendUnityMessage("ProductRequestFail", message);
- }
-
- protected void RecieveProductInfo(ArrayList<SkuItem> skuItems,ArrayList<String> invalidProductIds){
- JSONObject jsonObject=new JSONObject();
- try {
- JSONArray skuArray=new JSONArray();
- JSONObject tmpObj = null;
- for (int i = 0; i < skuItems.size(); i++) {
- SkuItem skuItem=skuItems.get(i);
- tmpObj = new JSONObject();
- tmpObj.put("productId" , skuItem.productId);
- tmpObj.put("title" , skuItem.title);
- tmpObj.put("desc" , skuItem.desc);
- tmpObj.put("price" , skuItem.price);
- tmpObj.put("formatPrice" , skuItem.formatPrice);
- tmpObj.put("priceCurrencyCode" , skuItem.priceCurrencyCode);
- tmpObj.put("skuType" , skuItem.skuType);
- skuArray.put(tmpObj);
- }
-
- JSONArray invalidArray=new JSONArray();
- for (int i = 0; i < invalidProductIds.size(); i++) {
- invalidArray.put(invalidProductIds.get(i));
- }
- jsonObject.put("skuItems", skuArray);
- jsonObject.put("invalidIds", invalidArray);
- } catch (JSONException e) {
- PrintLog("Json数据错误:"+e.getMessage());
- }
- String info=jsonObject.toString();
- PrintLog("当前产品信息:"+info);
- SendUnityMessage("RecieveProductInfos", info);
- }
-
- public void PrintLog(String message){
- PrintLog(message,false);
- }
-
- public void SendUnityMessage(String func,String value){
- CallUnity(UNITY_GO_NAME, func, value);
- }
-
- public Activity CurrentActivity(){
- return getActivity();
- }
-
- /**
- * Android调用Unity的方法
- * @param gameObjectName 调用的GameObject的名称
- * @param functionName 方法名
- * @param args 参数
- * @return 调用是否成功
- */
- boolean CallUnity(String gameObjectName, String functionName, String args){
- try {
- Class<?> classtype = Class.forName("com.unity3d.player.UnityPlayer");
- Method method =classtype.getMethod("UnitySendMessage", String.class,String.class,String.class);
- method.invoke(classtype,gameObjectName,functionName,args);
- return true;
- } catch (ClassNotFoundException e) {
- System.out.println(e.getMessage());
- } catch (NoSuchMethodException e) {
- System.out.println(e.getMessage());
- } catch (IllegalAccessException e) {
- System.out.println(e.getMessage());
- } catch (InvocationTargetException e) {
-
- }
- return false;
- }
-
- /**
- * 利用反射机制获取unity项目的上下文
- * @return
- */
- Activity getActivity(){
- if(null == unityActivity) {
- try {
- Class<?> classtype = Class.forName("com.unity3d.player.UnityPlayer");
- Activity activity = (Activity) classtype.getDeclaredField("currentActivity").get(classtype);
- unityActivity = activity;
- context = activity;
- } catch (ClassNotFoundException e) {
- System.out.println(e.getMessage());
- } catch (IllegalAccessException e) {
- System.out.println(e.getMessage());
- } catch (NoSuchFieldException e) {
- System.out.println(e.getMessage());
- }
- }
- return unityActivity;
- }
- }
-

- package com.egogame;
-
- public class SkuItem {
- public String productId;
- public String title;
- public String desc;
- public String price;
- public String formatPrice;//格式化价格,包括其货币符号
- public String priceCurrencyCode;//货币代码
- public String skuType;//内购还是订阅
- }
- package com.egogame;
-
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Looper;
- import android.text.TextUtils;
-
- import androidx.annotation.NonNull;
- import androidx.annotation.Nullable;
-
- import com.android.billingclient.api.BillingClient;
- import com.android.billingclient.api.BillingClientStateListener;
- import com.android.billingclient.api.BillingFlowParams;
- import com.android.billingclient.api.BillingResult;
- import com.android.billingclient.api.ConsumeParams;
- import com.android.billingclient.api.Purchase;
- import com.android.billingclient.api.PurchasesUpdatedListener;
- import com.android.billingclient.api.SkuDetails;
- import com.android.billingclient.api.SkuDetailsParams;
- import com.android.billingclient.api.SkuDetailsResponseListener;
-
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- public class MainActivity extends BaseMainActivity implements PurchasesUpdatedListener,BillingClientStateListener {
- private static final long RECONNECT_TIMER_START_MILLISECONDS = 1L * 1000L;
- private static final long RECONNECT_TIMER_MAX_TIME_MILLISECONDS = 1000L * 60L * 15L; // 15 mins
-
- private static final Handler handler = new Handler(Looper.getMainLooper());
- private BillingClient billingClient;
- private String[] cacheRequestList;
- private Map<String, SkuDetails> skuDetailsLiveDataMap=new HashMap<>();
- private boolean isConsumable;
- private String buyProductId;
- private boolean billingSetupComplete = false;
- // how long before the data source tries to reconnect to Google play
- private long reconnectMilliseconds = RECONNECT_TIMER_START_MILLISECONDS;
-
- @Override
- protected void OnInitHandle(String googlePlayPublicKey) {
- super.OnInitHandle(googlePlayPublicKey);
-
- if (googlePlayPublicKey.contains("CONSTRUCT_YOUR")) {
- throw new RuntimeException("Please put your app's public key in MainActivity.java. See README.");
- }
- billingClient = BillingClient.newBuilder(CurrentActivity()).setListener(this).enablePendingPurchases().build();
- billingClient.startConnection(this);
- }
-
- private void retryBillingServiceConnectionWithExponentialBackoff() {
- handler.postDelayed(() ->
- billingClient.startConnection(this),
- reconnectMilliseconds);
- reconnectMilliseconds = Math.min(reconnectMilliseconds * 2,
- RECONNECT_TIMER_MAX_TIME_MILLISECONDS);
- }
-
- @Override
- protected void OnRequstProduct(String[] productId) {
- super.OnRequstProduct(productId);
-
- List<String> skuList = new ArrayList<>();
- skuList.addAll(Arrays.asList(productId));
- cacheRequestList=productId;
- SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
- params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
- billingClient.querySkuDetailsAsync(params.build(),
- new SkuDetailsResponseListener() {
- @Override
- public void onSkuDetailsResponse(BillingResult billingResult,
- List<SkuDetails> skuDetailsList) {
- int responseCode = billingResult.getResponseCode();
- PrintLog("onSkuDetailsResponse:"+billingResult+" code:"+GetResponseText(responseCode));
-
- switch (responseCode){
- case BillingClient.BillingResponseCode.OK:
- RecieveProducts(skuDetailsList);
- break;
- default:
- RequestProductsFail("Failed to query inventory: " + billingResult.getDebugMessage());
- PrintLog("Failed to query inventory: "+billingResult.getDebugMessage());
- break;
- }
- }
- });
- }
-
- private String GetResponseText(int responseCode){
- switch (responseCode){
- case BillingClient.BillingResponseCode.OK:
- return "OK";
- case BillingClient.BillingResponseCode.SERVICE_TIMEOUT:
- return "SERVICE_TIMEOUT";
- case BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED:
- return "FEATURE_NOT_SUPPORTED";
- case BillingClient.BillingResponseCode.USER_CANCELED:
- return "USER_CANCELED";
- case BillingClient.BillingResponseCode.SERVICE_DISCONNECTED:
- return "SERVICE_DISCONNECTED";
- case BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE:
- return "SERVICE_UNAVAILABLE";
- case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE:
- return "BILLING_UNAVAILABLE";
- case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE:
- return "ITEM_UNAVAILABLE";
- case BillingClient.BillingResponseCode.DEVELOPER_ERROR:
- return "DEVELOPER_ERROR";
- case BillingClient.BillingResponseCode.ERROR:
- return "ERROR";
- case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
- return "ITEM_ALREADY_OWNED";
- case BillingClient.BillingResponseCode.ITEM_NOT_OWNED:
- return "ITEM_NOT_OWNED";
- default:
- return "UnKnown";
- }
- }
-
- private void RecieveProducts(List<SkuDetails> skuDetailsList){
- ArrayList<SkuItem> skuItems=new ArrayList<SkuItem>();
- ArrayList<String> invaildIds=new ArrayList<String>();
- PrintLog("cacheRequestList:"+cacheRequestList);
- int length=cacheRequestList.length;
- if(cacheRequestList!=null && length>0){
- for(int i=0;i<length;i++){
- String productId=cacheRequestList[i];
- if(!TextUtils.isEmpty(productId)){
- SkuDetails detail=null;
- for (SkuDetails skuDetails : skuDetailsList) {
- if(skuDetails.getSku().equals(productId)){
- detail=skuDetails;
- break;
- }
- }
-
- if(detail==null){
- PrintLog("未找到该产品信息:"+productId);
- invaildIds.add(productId);
- continue;
- }
- skuDetailsLiveDataMap.put(productId,detail);
-
- String price=detail.getPrice();
- String formatPrice=price;
-
- SkuItem skuItem=new SkuItem();
- skuItem.productId=productId;
- skuItem.title=detail.getTitle();
- skuItem.desc=detail.getDescription();
- skuItem.price=price;
- skuItem.formatPrice=formatPrice;
- skuItem.priceCurrencyCode=detail.getPriceCurrencyCode();
- skuItem.skuType=detail.getType();
-
- skuItems.add(skuItem);
- }
- }
- }
- RecieveProductInfo(skuItems,invaildIds);
- }
-
- @Override
- public boolean IsIAPSupported() {
- return true;
- }
-
- @Override
- protected void OnBuyProduct(String productId, boolean isConsumable) {
- super.OnBuyProduct(productId, isConsumable);
-
- SkuDetails skuDetails=skuDetailsLiveDataMap.get(productId);
- if(null!=skuDetails){
- buyProductId=productId;
- this.isConsumable=isConsumable;
-
- BillingFlowParams purchaseParams =
- BillingFlowParams.newBuilder()
- .setSkuDetails(skuDetails)
- .build();
-
- billingClient.launchBillingFlow(CurrentActivity(), purchaseParams);
- }else{
- BuyFail(productId,"Can not find SkuDetails:"+productId);
- PrintLog("未请求商品数据,请先请求:"+productId);
- }
- }
-
- @Override
- public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> list) {
- int responseCode = billingResult.getResponseCode();
-
- PrintLog("BillingResult [" + GetResponseText(responseCode) + "]: "
- + billingResult.getDebugMessage());
- switch (responseCode) {
- case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
- case BillingClient.BillingResponseCode.OK:
- FlowFinish(true,null,list);
- break;
- case BillingClient.BillingResponseCode.USER_CANCELED:
- String productId=buyProductId;
- buyProductId=null;
- BuyCancle(productId);
- break;
- default:
- FlowFinish(false,billingResult.getDebugMessage(),list);
- break;
- }
- }
-
- private void FlowFinish(Boolean isSuccess,String message,List<Purchase> purchases){
- if(isSuccess){
- if(buyProductId!=null){
- String productId=buyProductId;
- buyProductId=null;
- String purchaseToken=null;
- for (Purchase purchase : purchases) {
- for (String skus : purchase.getSkus()) {
- //需要校验付款状态
- if(skus.contains(productId) &&
- purchase.getPurchaseState()==Purchase.PurchaseState.PURCHASED){
- purchaseToken=purchase.getPurchaseToken();
- break;
- }
- if(purchaseToken!=null) break;
- }
- }
-
- if(isConsumable){
- if(purchaseToken==null){
- CallBackBuyFail("unknown purchaseToken:"+productId);
- }else {
- ConsumeParams consumeParams =
- ConsumeParams.newBuilder()
- .setPurchaseToken(purchaseToken)
- .build();
- billingClient.consumeAsync(consumeParams,(billingResult, token) -> {
- if(billingResult.getResponseCode()==BillingClient.BillingResponseCode.OK){
- BuyComplete(productId);
- }else{
- CallBackBuyFail(billingResult.getDebugMessage());
- }
- });
- }
- }else{
- BuyComplete(productId);
- }
- }
- }else{
- if(buyProductId!=null){
- CallBackBuyFail(message);
- }
- }
- }
-
- private void CallBackBuyFail(String message){
- String productId=buyProductId;
- buyProductId=null;
- BuyFail(productId, message);
- PrintLog("Error purchasing: " + message);
- }
-
- @Override
- public void onBillingServiceDisconnected() {
- PrintLog("onBillingServiceDisconnected");
- billingSetupComplete = false;
- retryBillingServiceConnectionWithExponentialBackoff();
- }
-
- @Override
- public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
- int responseCode = billingResult.getResponseCode();
- String debugMessage = billingResult.getDebugMessage();
- PrintLog("onBillingSetupFinished: " + debugMessage+"("+ GetResponseText(responseCode)+")",true);
- switch (responseCode) {
- case BillingClient.BillingResponseCode.OK:
- // The billing client is ready. You can query purchases here.
- // This doesn't mean that your app is set up correctly in the console -- it just
- // means that you have a connection to the Billing service.
- reconnectMilliseconds = RECONNECT_TIMER_START_MILLISECONDS;
- billingSetupComplete = true;
- break;
- case BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE:
- case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE:
- PrintLog("Billing Service Unavailable:"+debugMessage,true);
- break;
- default:
- retryBillingServiceConnectionWithExponentialBackoff();
- break;
- }
- }
- }

- #if UNITY_ANDROID
- using System;
- using UnityEngine;
- using System.Collections;
- using System.Collections.Generic;
- using Newtonsoft.Json;
-
- public class GooglePlay_IAPBridge{
- class BuyProductData
- {
- public string productId;
- public bool isConsumable;
- }
- private AndroidJavaObject javaObject;
-
- private GooglePlay_IAPBridge() {
- if (Application.platform != RuntimePlatform.Android)
- return;
- javaObject = new AndroidJavaObject("com.egogame.MainActivity");
- }
-
- private volatile static GooglePlay_IAPBridge _instance = null;
- private static readonly object lockHelper = new object();
- public static GooglePlay_IAPBridge getInstance()
- {
- if(_instance == null)
- {
- lock(lockHelper)
- {
- if(_instance == null)
- _instance = new GooglePlay_IAPBridge();
- }
- }
- return _instance;
- }
- public void Init(string goName,string publicKey){
- Debug.Log ("[GooglePlay_IAPBridge]Init:" + goName + "=====" + publicKey);
- if (Application.platform != RuntimePlatform.Android)
- return;
- javaObject.Call ("Init", goName, publicKey);
- }
-
- public bool IsIAPSupported(){
- if (Application.platform != RuntimePlatform.Android)
- return false;
- return javaObject.Call<bool> ("IsIAPSupported");
- }
-
- public void RequestProducts(string jsonData)
- {
- Debug.Log ("[GooglePlay_IAPBridge]RequstProduct:" + jsonData);
- if (Application.platform != RuntimePlatform.Android)
- return;
- javaObject.Call ("RequstProduct", jsonData);
- }
-
- public void BuyProduct(string productId,bool isConsumable){
- BuyProductData buyProductData=new BuyProductData();
- buyProductData.productId = productId;
- buyProductData.isConsumable = isConsumable;
- string jsonData = JsonConvert.SerializeObject(buyProductData);
- Debug.Log ("[GooglePlay_IAPBridge]BuyProduct:" + jsonData);
- if (Application.platform != RuntimePlatform.Android)
- return;
- javaObject.Call ("BuyProduct", jsonData);
- }
- }
- #endif

这里面我们直接 javaObject = new AndroidJavaObject("com.egogame.MainActivity");实例化我们jar封装好的类,即可直接调用public方法。请注意:因为Android和Unity线程不一样,所以jar处理时需要规避线程的同步性。- using UnityEngine;
- using System.Collections.Generic;
- using Newtonsoft.Json;
-
- public class IAPBridge{
- class RequestSkuData
- {
- public string[] productIds;
- }
-
- public static void InitIAp(string goName,string publicKey=""){
- Debug.Log("[IAPBridge]InitIAp:" + goName + "==" + publicKey);
- #if UNITY_IPHONE
- if(Application.platform==RuntimePlatform.IPhonePlayer){
- iOS_IAPBridge.InitIAPManager(goName);
- }
- #elif UNITY_ANDROID
- if(Application.platform==RuntimePlatform.Android){
- GooglePlay_IAPBridge.getInstance().Init(goName,publicKey);
- }
- #endif
- }
-
- public static bool IAPEnabeld(){
- #if UNITY_IPHONE
- if(Application.platform==RuntimePlatform.IPhonePlayer){
- return iOS_IAPBridge.IsProductAvailable();
- }
- #elif UNITY_ANDROID
- if(Application.platform==RuntimePlatform.Android){
- return GooglePlay_IAPBridge.getInstance().IsIAPSupported();
- }
- #endif
- return false;
- }
-
- public static void RequstProducts(List<string> productIds){
- RequestSkuData data=new RequestSkuData();
- data.productIds = productIds.ToArray();
- string jsonData = JsonConvert.SerializeObject(data);
- Debug.Log("[IAPBridge]RequstProducts:"+jsonData);
- #if UNITY_IPHONE
- iOS_IAPBridge.RequstProductInfo(jsonData);
- #elif UNITY_ANDROID
- if (Application.platform == RuntimePlatform.Android){
- GooglePlay_IAPBridge.getInstance().RequestProducts(jsonData);
- }
- #endif
- }
-
- public static void SendBuyProduct(string productId,bool isConsumable){
- Debug.Log(string.Format("[IAPBridge]SendBuyProduct:{0} isConsumable:{1}",productId,isConsumable));
- #if UNITY_IPHONE
- if(Application.platform==RuntimePlatform.IPhonePlayer){
- iOS_IAPBridge.BuyProduct(productId);
- }
- #elif UNITY_ANDROID
- if (Application.platform == RuntimePlatform.Android){
- GooglePlay_IAPBridge.getInstance().BuyProduct(productId, isConsumable);
- }
- #endif
- }
-
- public static void RestoreProduct(){
- Debug.Log("[IAPBridge]Restore!");
- #if UNITY_IPHONE
- if(Application.platform==RuntimePlatform.IPhonePlayer){
- iOS_IAPBridge.Restore();
- }
- #endif
- }
- }

- using Newtonsoft.Json;
- using UnityEngine;
- #pragma warning disable 0649
-
- /// <summary>
- /// 该类主要用于接收iOS和Android回调,做一个桥接用途
- /// </summary>
- public class IAPMessage : MonoBehaviour {
- class BuyFailData
- {
- public string productId;
- public string error;
- }
-
- #region callback from Objective-c/JAR
- //获取到产品列表回调
- void RecieveProductInfos(string jsonData)
- {
- if(string.IsNullOrEmpty(jsonData)) return;
- var infoData = JsonConvert.DeserializeObject<IAPProductInfoData>(jsonData);
- OnProductInfoReceived (infoData);
- }
-
- //产品列表请求失败
- void ProductRequestFail(string message)
- {
- OnProductInfoFail(message);
- }
-
- //购买成功回调
- void ProductBuyComplete(string productId)
- {
- OnProductBuyComplete(productId);
- }
-
- //购买失败回调
- void ProductBuyFailed(string jsonData)
- {
- var infoData = JsonConvert.DeserializeObject<BuyFailData>(jsonData);
- OnBuyProductFail (infoData.productId, infoData.error);
- }
-
- //获取商品回执回调
- void ProvideContent(string msg){}
-
- //购买取消回调
- void ProductBuyCancled(string productId)
- {
- OnBuyProductCancled(productId);
- }
-
- /// <summary>
- /// 恢复购买成功
- /// </summary>
- /// <param name="productId"></param>
- void RestoreComplete(string productId){
- OnRestoreCompleted (productId);
- }
- #endregion
-
- //接收到产品信息
- void OnProductInfoReceived(IAPProductInfoData info){
- Debug.Log("[IAPMessage]Unity接收到商品信息:" + info.ToString());
- IAPManager.internal_CallBySDK_ProductInfosReceive(info);
- }
-
- //接收到产品信息
- void OnProductInfoFail(string error){
- Debug.Log("[IAPMessage]Unity商品信息请求失败:" + error);
- IAPManager.internal_RequestProductInfoFail(error);
- }
-
- //购买完成
- void OnProductBuyComplete(string productId){
- Debug.Log ("[IAPMessage]购买完成" + productId);
- IAPManager.internal_CallBySDK_BuyComplete(productId);
- }
-
- //购买失败
- void OnBuyProductFail(string productId,string error){
- Debug.Log(string.Format("[IAPMessage]购买失败:{0} 错误信息{1}", productId, error));
- IAPManager.internal_CallBySDK_BuyFail(productId, error);
- }
-
- //购买取消
- void OnBuyProductCancled(string productId){
- Debug.Log ("[IAPMessage]购买取消" + productId);
- IAPManager.internal_CallBySDK_BuyCanceled(productId);
- }
-
- //恢复完成
- void OnRestoreCompleted(string productId){
- Debug.Log ("[IAPMessage]恢复购买完成:"+productId);
- IAPManager.internal_CallBySDK_RestoreCompleted(productId);
- }
- }
-
- #pragma warning restore 0649

- using System.Collections.Generic;
-
- public class IAPProductInfoData
- {
- public List<IAPSkuItem> skuItems;//请求到的产品列表
- public string[] invalidIds;//无效产品id
- }
-
- public struct IAPSkuItem{
- public string productId;//后台产品id
- public string title;//后台标题
- public string desc;//后台描述
- public string price;//格式化价格,显示请用formatPrirce
- public string formatPrice;//格式化价格,包括其货币符号
- public string priceCurrencyCode;//货币代码
- public string skuType;//内购还是订阅 subscription/inapp
-
- public override string ToString ()
- {
- return string.Format(
- "[productId]:{0} [title]:{1} [desc]:{2} [price]:{3} [formatPrice]:{4} [priceCurrencyCode]:{5} [skuType:]{6}",
- productId, title, desc, price, formatPrice, priceCurrencyCode, skuType);
- }
- }
-
- public struct IAPProvideData
- {
- public string cfgId;
- public string title;
- public string desc;
- public string formatPrice;
- }

请注意:
*打包的apk不要对安卓代码进行混淆,否则代码会被裁剪导致调用不到java代码,如下
Minify和Custom Proguard File不要勾选
如果接入有问题,可以加Q群进行提问
Android部分源码可以在这里下载到:https://download.csdn.net/download/egostudio/20463417
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。