1. 环境准备与基础配置在开始集成Google Play Billing V5之前我们需要确保开发环境已经正确配置。首先确认你的Unity版本在2020.3或更高版本这是保证Android构建稳定性的基础。我推荐使用Unity Hub管理不同版本避免因版本兼容性问题导致不必要的麻烦。Android SDK的配置是关键环节。打开Unity的Preferences窗口在External Tools选项卡中确保Android SDK路径指向正确的安装位置。这里有个容易踩坑的地方如果你同时安装了Android StudioUnity可能会默认使用Android Studio内置的SDK这可能导致版本冲突。我建议单独下载命令行工具版SDK进行管理。支付库的导入需要特别注意。从Google的Maven仓库获取最新版的billing-client库当前是5.0.0版本可以通过在Unity项目的Assets/Plugins/Android目录下创建billinglib.gradle文件实现自动依赖dependencies { implementation com.android.billingclient:billing:5.0.0 implementation com.android.billingclient:billing-ktx:5.0.0 // 可选Kotlin扩展 }2. Java层支付客户端实现2.1 支付管理器核心类支付客户端的初始化是整套系统的基石。我建议采用单例模式设计GoogleBillingManager类这样可以确保整个应用生命周期内支付状态的一致性。下面是经过实战验证的核心代码结构public class GoogleBillingManager { private static final String TAG BillingManager; private BillingClient billingClient; private boolean isServiceConnected false; // 单例实现 private static class Holder { static final GoogleBillingManager INSTANCE new GoogleBillingManager(); } public static GoogleBillingManager getInstance() { return Holder.INSTANCE; } public void initialize(Activity activity) { if (billingClient ! null) return; billingClient BillingClient.newBuilder(activity) .enablePendingPurchases() .setListener(this::handlePurchasesUpdated) .build(); startServiceConnection(() - { // 连接成功后的初始化操作 queryPendingPurchases(); }); } }2.2 商品查询与购买流程V5版本最大的改进之一是引入了ProductDetails替代了原来的SkuDetails提供了更丰富的商品信息展示能力。在实际项目中我发现正确处理价格展示非常重要public void queryProductDetails(ListString productIds, String productType) { ListQueryProductDetailsParams.Product products new ArrayList(); for (String productId : productIds) { products.add(QueryProductDetailsParams.Product.newBuilder() .setProductId(productId) .setProductType(productType) .build()); } QueryProductDetailsParams params QueryProductDetailsParams.newBuilder() .setProductList(products) .build(); billingClient.queryProductDetailsAsync(params, (billingResult, productDetailsList) - { if (billingResult.getResponseCode() BillingClient.BillingResponseCode.OK) { // 处理价格展示 for (ProductDetails details : productDetailsList) { ProductDetails.OneTimePurchaseOfferDetails offerDetails details.getOneTimePurchaseOfferDetails(); String price offerDetails.getFormattedPrice(); long priceMicros offerDetails.getPriceAmountMicros(); String currency offerDetails.getPriceCurrencyCode(); } } }); }3. Unity C#桥接层设计3.1 AndroidJavaObject交互实现C#层需要建立与Java代码的稳定通信机制。我推荐使用AndroidJavaClass和AndroidJavaObject进行跨语言调用同时要注意主线程安全问题public class GoogleBillingBridge : MonoBehaviour { private static AndroidJavaClass billingManagerClass; private static AndroidJavaClass billingHelperClass; void Awake() { #if UNITY_ANDROID billingManagerClass new AndroidJavaClass(com.yourpackage.GoogleBillingManager); billingHelperClass new AndroidJavaClass(com.yourpackage.GoogleBillHelper); AndroidJavaClass unityPlayer new AndroidJavaClass(com.unity3d.player.UnityPlayer); AndroidJavaObject activity unityPlayer.GetStaticAndroidJavaObject(currentActivity); activity.Call(runOnUiThread, new AndroidJavaRunnable(() { billingManagerClass.CallStatic(initialize, activity); })); #endif } public void PurchaseProduct(string productId) { #if UNITY_ANDROID AndroidJavaClass unityPlayer new AndroidJavaClass(com.unity3d.player.UnityPlayer); AndroidJavaObject activity unityPlayer.GetStaticAndroidJavaObject(currentActivity); activity.Call(runOnUiThread, new AndroidJavaRunnable(() { billingHelperClass.CallStatic(purchaseProduct, activity, productId); })); #endif } }3.2 支付状态回调处理Unity接收Java回调需要建立完整的消息处理机制。我建议采用C#事件系统封装支付状态变化public class BillingEventDispatcher : MonoBehaviour { public static event ActionPurchaseResult OnPurchaseCompleted; // 由UnitySendMessage调用的方法 public void OnPurchaseSuccess(string purchaseData) { var result JsonUtility.FromJsonPurchaseResult(purchaseData); OnPurchaseCompleted?.Invoke(result); } public void OnPurchaseFailed(string errorMessage) { Debug.LogError($Purchase failed: {errorMessage}); } } [Serializable] public class PurchaseResult { public string productId; public string purchaseToken; public long purchaseTime; public string orderId; }4. Lua业务层集成方案4.1 支付流程封装在Lua层需要封装易用的支付接口。根据我的项目经验良好的错误处理和状态管理能显著提升支付成功率function ShopModule:initialize() self.paymentState { READY 0, PROCESSING 1, SUCCESS 2, FAILED 3 } self.currentState self.paymentState.READY -- 注册Unity回调 CS.BillingEventDispatcher.OnPurchaseCompleted:AddListener(function(result) self:handlePurchaseResult(result) end) end function ShopModule:purchaseItem(itemId, callback) if self.currentState self.paymentState.PROCESSING then callback(false, Another payment is in progress) return end self.currentState self.paymentState.PROCESSING self.currentCallback callback -- 调用C#层支付接口 CS.GoogleBillingBridge.PurchaseProduct(itemId) end4.2 补单与验证机制支付验证是防止欺诈的关键环节。我建议采用客户端服务端双重验证机制function ShopModule:verifyPurchase(purchaseData) local verificationParams { productId purchaseData.productId, purchaseToken purchaseData.purchaseToken, userId PlayerData:getUserId(), deviceId SystemInfo.deviceUniqueIdentifier } HttpService:post(/api/verify_purchase, verificationParams, function(success, response) if success then if response.valid then self:deliverProduct(purchaseData.productId) else self:handleInvalidPurchase(purchaseData) end else -- 网络错误处理 self:retryVerification(purchaseData) end end) end function ShopModule:checkPendingPurchases() -- 查询未完成的交易 CS.GoogleBillingBridge.QueryPurchases(function(purchases) for _, purchase in ipairs(purchases) do if not PurchaseCache:isProcessed(purchase.orderId) then self:verifyPurchase(purchase) end end end) end5. 测试与调试技巧5.1 测试账号配置在Google Play Console中配置测试账号时我发现一个常见问题是测试人员无法正常看到测试商品。这通常是由于以下原因造成的测试账号未添加到许可测试人员列表应用版本未发布到测试轨道商品状态未设置为活跃正确的测试流程应该是在Google Play Console的许可测试部分添加测试账号将应用发布到内部测试轨道审核最快确保商品状态为活跃测试设备必须登录测试账号且未登录其他Google账号5.2 常见问题排查在集成过程中我遇到过几个典型问题及解决方案问题1支付流程立即返回商品已拥有原因未正确处理消费型商品的消耗流程解决方案在确认服务器收到支付通知后调用consumeAsyncpublic void consumePurchase(Purchase purchase) { ConsumeParams params ConsumeParams.newBuilder() .setPurchaseToken(purchase.getPurchaseToken()) .build(); billingClient.consumeAsync(params, (billingResult, purchaseToken) - { if (billingResult.getResponseCode() BillingClient.BillingResponseCode.OK) { // 消耗成功可以再次购买 } }); }问题2支付面板无法弹出检查清单文件是否声明了BILLING权限确认测试设备安装了最新版Google Play商店检查商品ID是否与后台配置完全一致区分大小写问题3价格显示格式异常处理不同地区的价格格式特别是使用逗号作为小数点的地区使用NumberFormat类进行本地化显示double price (double) micros / 1_000_000; NumberFormat format NumberFormat.getCurrencyInstance(); format.setCurrency(Currency.getInstance(currencyCode)); String formattedPrice format.format(price);6. 性能优化与最佳实践6.1 支付连接管理支付服务的连接是耗时的操作我建议采用以下优化策略应用启动时预连接支付服务保持长连接而非频繁断开重连实现自动重连机制private IEnumerator MaintainBillingConnection() { while (true) { if (!IsBillingConnected()) { ConnectToBilling(); yield return new WaitForSeconds(5); // 重连间隔 } yield return new WaitForSeconds(30); // 心跳检测间隔 } }6.2 数据缓存策略合理缓存商品信息可以显著提升用户体验本地缓存商品详情定期如24小时更新实现内存磁盘双缓存处理网络异常时的降级方案function ShopModule:getProductInfo(productId) -- 首先检查内存缓存 if self.productCache[productId] then if os.time() - self.productCache[productId].timestamp 86400 then return self.productCache[productId].data end end -- 其次检查持久化缓存 local cached Storage:get(product_..productId) if cached and cached.timestamp then -- 更新内存缓存 self.productCache[productId] cached if os.time() - cached.timestamp 86400 then return cached.data end end -- 最后从网络获取 self:requestProductInfoFromServer(productId) return nil -- 暂时返回nil通过回调通知 end在项目实际运行中这套缓存机制将商品信息加载时间从平均1.5秒降低到了0.2秒大幅提升了商店页面的打开速度。