本文主要是介绍Unity接入GooglePlay内购V4 V5 V6(源生Android方式),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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坑还是蛮多的,各种坑。
接的方式目前来看有三种:
- 采用Unity IAP插件,开启Unity的IAP Service
- 采用Android源生接入,在Android Studio接入,然后打包出jar或aar放到Unity项目,使用Unity调用
- 打包出Android工程,在Android工程中接入
这次介绍的是第二种,使用安卓源生方式接入,因为该方式一劳永逸,新项目可以很快就完成接入。
为什么不用第一种呢?直接导入IAP插件,然后设置参数,就可以很快实现了,该方式可以直接参考google文档。
第一种IAP插件的缺点:
虽然该方式只需要导入插件,然后进行一些参数的设置,但是此方式特别麻烦的一点是需要在Unity中开启Service,但是开启后又得填一大堆信息,巨麻烦无比,而且Unity的网络简直不能看,要么是打不开,要么是卡半死,而且网站老变,以及没有完整的文档。这些都是很恶心人的事情,甚至还要创建组织啥的,反正谁用谁恶心。
----------------------------------分割线------------------------------------------------------------------------
正式接入
接入之前需要的储备知识是:Unity如何与Android交互
准备jar和aar
我们的目标是导出自己封装的jar(里面封装了接口供Unity调用,也就是桥接层),以及找到谷歌官方提供的Billing V4插件(aar)。
因为从2021.8.2起,谷歌要求必须接入V3版以上的插件。所以我们这次干脆接了最新的V4.
接入文档可以参考指南:从 AIDL 迁移到 Google Play 结算库的迁移指南
获取aar文件
- 打开Android Studio,创建一个工程
- 创建一个Module,创建完如下(Module要选择Library)
- 打开build.gradle,修改导出为Library,在dependencies中添加依赖,并同步。
implementation("com.android.billingclient:billing:4.0.0") 注意:plugins这边要改为'com.android.library',导出才会是aar,否则是apk.
plugins {id 'com.android.library' }android {compileSdkVersion 30defaultConfig {//applicationId "com.egogame.iapforgoogleplay"minSdkVersion 19targetSdkVersion 30versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility 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") }
- 同步完成后,在gradle缓存路径下找到谷歌官方提供的.aar文件,复制到Unity工程Assets/Plugins/Android目录下,待用
文件路径:C:\Users\xxx\.gradle\caches\modules-2\files-2.1\com.android.billingclient\billing\4.0.0\31aa58e2d4286f6b96480764e7a84d5de9935f02
打包jar
- 创建BaseMainActivity.java类,该类创建了一些供Unity调用的接口格式。
当前类还未开始接入GP插件接口。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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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;//内购还是订阅 }
- 创建MainActivity类,开始接入billing插件(该命名为Activity其实没有继承Activity,因为继承Activity后,需要Unity那边AndroidManfiest文件指定为Activity才能生效,所以这里不采用继承Activity)
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 minsprivate 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 playprivate long reconnectMilliseconds = RECONNECT_TIMER_START_MILLISECONDS;@Overrideprotected 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);}@Overrideprotected 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() {@Overridepublic 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);}@Overridepublic boolean IsIAPSupported() {return true;}@Overrideprotected 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);}}@Overridepublic 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);}@Overridepublic void onBillingServiceDisconnected() {PrintLog("onBillingServiceDisconnected");billingSetupComplete = false;retryBillingServiceConnectionWithExponentialBackoff();}@Overridepublic 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;}} }
- 选择Module,并且点击Build Module,将会导出aar文件
- 我们将aar改为rar格式,然后用压缩软件打开,取出里面的classes.jar,我们只需要该jar即可,该jar是我们封装好的接口,后续有用,为了方便认,我将名字改为IAPForGooglePlay.jar
- 将jar放置到Unity工程下,Assets/Plugins/Android目录下,该目录下文件如下:
注意:jar要放在Android目录下,而不是Android/libs目录下,经试验放libs下会识别不到 - Android这边的工作就结束了,后面我们来写Unity这边的代码
编写Unity接口
- 创建类GooglePlay_IAPBridge,编写调用Android我们封装好的jar里的接口的桥接类
这里面我们直接 javaObject = new AndroidJavaObject("com.egogame.MainActivity");实例化我们jar封装好的类,即可直接调用public方法。请注意:因为Android和Unity线程不一样,所以jar处理时需要规避线程的同步性。#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
- 再封装一个IAPBridge类,用来分流不同渠道,转接不同接口文件
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_IPHONEif(Application.platform==RuntimePlatform.IPhonePlayer){iOS_IAPBridge.InitIAPManager(goName);} #elif UNITY_ANDROIDif(Application.platform==RuntimePlatform.Android){GooglePlay_IAPBridge.getInstance().Init(goName,publicKey);} #endif}public static bool IAPEnabeld(){ #if UNITY_IPHONEif(Application.platform==RuntimePlatform.IPhonePlayer){return iOS_IAPBridge.IsProductAvailable();} #elif UNITY_ANDROIDif(Application.platform==RuntimePlatform.Android){return GooglePlay_IAPBridge.getInstance().IsIAPSupported();} #endifreturn 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_IPHONEiOS_IAPBridge.RequstProductInfo(jsonData); #elif UNITY_ANDROIDif (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_IPHONEif(Application.platform==RuntimePlatform.IPhonePlayer){iOS_IAPBridge.BuyProduct(productId);} #elif UNITY_ANDROIDif (Application.platform == RuntimePlatform.Android){GooglePlay_IAPBridge.getInstance().BuyProduct(productId, isConsumable);} #endif}public static void RestoreProduct(){Debug.Log("[IAPBridge]Restore!"); #if UNITY_IPHONEif(Application.platform==RuntimePlatform.IPhonePlayer){iOS_IAPBridge.Restore();} #endif} }
- 然后Unity这边要接收下Android那边传过来的接口,将这个类挂载到某个GameObject下,如GameObject名为IAPObject,则上面初始化时调用IAPBridge.InitIAP将该gameObject名作为goName参数传过去即可。
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;//后台产品idpublic string title;//后台标题public string desc;//后台描述public string price;//格式化价格,显示请用formatPrircepublic string formatPrice;//格式化价格,包括其货币符号public string priceCurrencyCode;//货币代码public string skuType;//内购还是订阅 subscription/inapppublic 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; }
- 到这里就接入完成了,调用对应接口即可实现IAP的接入。
请注意:
*打包的apk不要对安卓代码进行混淆,否则代码会被裁剪导致调用不到java代码,如下
Minify和Custom Proguard File不要勾选
如果接入有问题,可以加Q群进行提问
Android部分源码可以在这里下载到:https://download.csdn.net/download/egostudio/20463417
这篇关于Unity接入GooglePlay内购V4 V5 V6(源生Android方式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!