l***@126.com
l***@126.com
  • 发布:2025-07-30 09:55
  • 更新:2025-07-30 16:01
  • 阅读:24

【报Bug】google billing-ktx 7.1.1云打包失败

分类:uni-app

产品分类: uniapp/App

PC开发环境操作系统: Mac

PC开发环境操作系统版本号: 版本15.5 (24F74)

HBuilderX类型: 正式

HBuilderX版本号: 4.66

手机系统: Android

手机系统版本号: Android 15

手机厂商: 三星

手机机型: Galaxy s23

页面类型: vue

vue版本: vue3

打包方式: 云端

项目创建方式: HBuilderX

示例代码:

config.json:

{  
    "dependencies": [  
        "androidx.core:core-ktx:1.9.0",  
        "com.android.billingclient:billing-ktx:7.1.1"  
    ],  
    "minSdkVersion": "24"  
}

Billing.kt:

package uts.sdk.modules.nativeBilling  

import android.app.Activity  
import android.content.Context  
import android.content.SharedPreferences  
import android.util.Log  
import com.android.billingclient.api.*  
import com.android.billingclient.api.ProductDetailsResult  
import io.dcloud.uts.*  
import io.dcloud.uts.UTSJSONObject  
import kotlinx.coroutines.*  
import kotlin.coroutines.resume  
import kotlin.coroutines.suspendCoroutine  
import org.json.JSONArray  
import org.json.JSONObject  

// 订单状态枚举  
enum class OrderStatus {  
    PROCESSING,    // 进行中  
    SUCCESS,       // 成功(会删除记录)  
    FAILED         // 失败(保留用于上传)  
}  

// 订单步骤枚举  
enum class OrderStep {  
    CREATED,              // 订单创建  
    BILLING_CONNECT,      // 连接Billing服务  
    PRODUCT_QUERY,        // 查询商品信息  
    LAUNCH_BILLING,       // 启动支付流程  
    PAYMENT_RESULT,       // 支付结果处理  
    CONSUME_START,        // 开始消费  
    CONSUME_COMPLETE,     // 消费完成  
    ACKNOWLEDGE_START,    // 开始确认  
    ACKNOWLEDGE_COMPLETE  // 确认完成  
}  

// 步骤记录数据类  
data class StepRecord(  
    val step: OrderStep,  
    val startTime: Long,  
    var endTime: Long? = null,  
    var success: Boolean? = null,  
    var errorCode: Int? = null,  
    var errorMessage: String? = null  
)  

// 订单记录数据类  
data class OrderRecord(  
    val transNo: String,  
    val productId: String,  
    val productType: String,  
    val createTime: Long,  
    var updateTime: Long,  
    var currentStep: OrderStep,  
    var status: OrderStatus,  
    var failedStep: OrderStep? = null,  
    var stepHistory: MutableList<StepRecord> = mutableListOf(),  
    var purchaseToken: String? = null,  
    var originalJson: String? = null  
)  

class Billing : PurchasesUpdatedListener {  
    private val TAG = "GoogleBilling"  

    private var sp: SharedPreferences? = null  
    private var purchasesUpdatedCallback: ((Boolean, UTSJSONObject) -> Unit)? = null  
    private var billingClient: BillingClient? = null  
    private var currentTransNo: String? = null  // 当前处理的订单号  

    // 错误码定义,参考 BillingCore  
    companion object {  

        const val FAIL_CODE_CANCEL = 0  
        const val FAIL_CODE_DISCONNECT = -11  
        const val FAIL_CODE_CONNECT = -12  
        const val FAIL_CODE_SKU_TYPE = -13  
        const val FAIL_CODE_SKU = -14  
        const val FAIL_CODE_SKU_CHECK = -15  
        const val FAIL_CODE_CALL_PAY = -16  
        const val FAIL_CODE_CONSUME_QUERY = -17  
        const val FAIL_CODE_CONSUME_NO = -18  
        const val FAIL_CODE_CONSUME_STATUS = -19  
        const val FAIL_CODE_CONSUME = -20  
        const val FAIL_CODE_ACKNOWLEDGE_QUERY = -21  
        const val FAIL_CODE_ACKNOWLEDGE_NO = -22  
        const val FAIL_CODE_ACKNOWLEDGE_STATUS = -23  
        const val FAIL_CODE_ACKNOWLEDGE = -24  
        const val FAIL_CODE_PURCHASES = -25  
        const val FAIL_CODE_GOOGLE_DEF = -999  

        private var instance: Billing? = null  

        fun getInstance(): Billing {  
            if (instance == null) {  
                instance = Billing()  
            }  
            return instance!!  
        }  

    }  

    private constructor() {  
        val context: Context? = UTSAndroid.getAppContext()  
        sp = context?.getSharedPreferences("GoogleBilling", Context.MODE_PRIVATE)  
    }  

    private fun getClient(): BillingClient {  
        if (billingClient == null) {  
            val context = UTSAndroid.getAppContext()!!  
            val params = PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()  
            billingClient =  
                BillingClient.newBuilder(context).enablePendingPurchases(params).setListener(this)  
                    .build()  
        }  
        return billingClient!!  
    }  

    // 检查和建立连接的 suspend 函数,参考 BillingCore.isBillingConnect()  
    private suspend fun isBillingConnect(): Boolean = suspendCoroutine { continuation ->  
        val client = getClient()  
        if (client.isReady) {  
            Log.d(TAG, "Billing client is already connected")  
            continuation.resume(true)  
        } else {  
            Log.d(TAG, "Starting billing client connection...")  
            client.startConnection(  
                object : BillingClientStateListener {  
                    override fun onBillingServiceDisconnected() {  
                        Log.w(TAG, "Billing service disconnected")  
                        continuation.resume(false)  
                    }  

                    override fun onBillingSetupFinished(billingResult: BillingResult) {  
                        val isSuccess =  
                            billingResult.responseCode == BillingClient.BillingResponseCode.OK  
                        continuation.resume(isSuccess)  
                    }  
                },  
            )  
        }  
    }  

    // ===== 订单记录管理方法 =====  

    // JSON序列化方法  
    private fun orderRecordToJson(orderRecord: OrderRecord): String {  
        val json = JSONObject()  
        json.put("transNo", orderRecord.transNo)  
        json.put("productId", orderRecord.productId)  
        json.put("productType", orderRecord.productType)  
        json.put("createTime", orderRecord.createTime)  
        json.put("updateTime", orderRecord.updateTime)  
        json.put("currentStep", orderRecord.currentStep.name)  
        json.put("status", orderRecord.status.name)  
        json.put("failedStep", orderRecord.failedStep?.name)  
        json.put("purchaseToken", orderRecord.purchaseToken)  
        json.put("originalJson", orderRecord.originalJson)  

        val stepsArray = JSONArray()  
        orderRecord.stepHistory.forEach { step ->  
            val stepJson = JSONObject()  
            stepJson.put("step", step.step.name)  
            stepJson.put("startTime", step.startTime)  
            stepJson.put("endTime", step.endTime)  
            stepJson.put("success", step.success)  
            stepJson.put("errorCode", step.errorCode)  
            stepJson.put("errorMessage", step.errorMessage)  
            stepsArray.put(stepJson)  
        }  
        json.put("stepHistory", stepsArray)  

        return json.toString()  
    }  

    // JSON反序列化方法  
    private fun jsonToOrderRecord(json: String): OrderRecord? {  
        return try {  
            val jsonObj = JSONObject(json)  
            val stepHistory = mutableListOf<StepRecord>()  

            val stepsArray = jsonObj.optJSONArray("stepHistory")  
            if (stepsArray != null) {  
                for (i in 0 until stepsArray.length()) {  
                    val stepJson = stepsArray.getJSONObject(i)  
                    val stepRecord = StepRecord(  
                        step = OrderStep.valueOf(stepJson.getString("step")),  
                        startTime = stepJson.getLong("startTime"),  
                        endTime = if (stepJson.isNull("endTime")) null else stepJson.getLong("endTime"),  
                        success = if (stepJson.isNull("success")) null else stepJson.getBoolean("success"),  
                        errorCode = if (stepJson.isNull("errorCode")) null else stepJson.getInt("errorCode"),  
                        errorMessage = if (stepJson.isNull("errorMessage")) null else stepJson.getString("errorMessage")  
                    )  
                    stepHistory.add(stepRecord)  
                }  
            }  

            OrderRecord(  
                transNo = jsonObj.getString("transNo"),  
                productId = jsonObj.getString("productId"),  
                productType = jsonObj.getString("productType"),  
                createTime = jsonObj.getLong("createTime"),  
                updateTime = jsonObj.getLong("updateTime"),  
                currentStep = OrderStep.valueOf(jsonObj.getString("currentStep")),  
                status = OrderStatus.valueOf(jsonObj.getString("status")),  
                failedStep = if (jsonObj.isNull("failedStep")) null else OrderStep.valueOf(jsonObj.getString("failedStep")),  
                stepHistory = stepHistory,  
                purchaseToken = if (jsonObj.isNull("purchaseToken")) null else jsonObj.getString("purchaseToken"),  
                originalJson = if (jsonObj.isNull("originalJson")) null else jsonObj.getString("originalJson")  
            )  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to parse order record: ${e.message}")  
            null  
        }  
    }  

    // 保存订单记录  
    private fun saveOrderRecord(orderRecord: OrderRecord) {  
        try {  
            val key = "order_${orderRecord.transNo}"  
            val json = orderRecordToJson(orderRecord)  
            sp?.edit()?.putString(key, json)?.apply()  
            Log.d(TAG, "Order record saved: ${orderRecord.transNo}, step: ${orderRecord.currentStep}")  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to save order record: ${e.message}")  
        }  
    }  

    // 获取订单记录  
    private fun getOrderRecord(transNo: String): OrderRecord? {  
        return try {  
            val key = "order_$transNo"  
            val json = sp?.getString(key, null)  
            if (json != null) {  
                jsonToOrderRecord(json)  
            } else {  
                null  
            }  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to get order record: ${e.message}")  
            null  
        }  
    }  

    // 删除订单记录  
    private fun deleteOrderRecord(transNo: String) {  
        try {  
            val key = "order_$transNo"  
            sp?.edit()?.remove(key)?.apply()  
            Log.d(TAG, "Order record deleted: $transNo")  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to delete order record: ${e.message}")  
        }  
    }  

    // 开始一个步骤  
    private fun startStep(transNo: String, step: OrderStep) {  
        try {  
            val orderRecord = getOrderRecord(transNo)  
            if (orderRecord != null) {  
                orderRecord.currentStep = step  
                orderRecord.updateTime = System.currentTimeMillis()  

                val stepRecord = StepRecord(  
                    step = step,  
                    startTime = System.currentTimeMillis()  
                )  
                orderRecord.stepHistory.add(stepRecord)  

                saveOrderRecord(orderRecord)  
                Log.d(TAG, "Step started: $transNo -> $step")  
            }  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to start step: ${e.message}")  
        }  
    }  

    // 完成一个步骤  
    private fun finishStep(transNo: String, step: OrderStep, success: Boolean, errorCode: Int? = null, errorMessage: String? = null) {  
        try {  
            val orderRecord = getOrderRecord(transNo)  
            if (orderRecord != null) {  
                orderRecord.updateTime = System.currentTimeMillis()  

                // 更新最后一个相同步骤的记录  
                val lastStepRecord = orderRecord.stepHistory.lastOrNull { it.step == step }  
                if (lastStepRecord != null) {  
                    lastStepRecord.endTime = System.currentTimeMillis()  
                    lastStepRecord.success = success  
                    lastStepRecord.errorCode = errorCode  
                    lastStepRecord.errorMessage = errorMessage  
                }  

                if (!success) {  
                    orderRecord.status = OrderStatus.FAILED  
                    orderRecord.failedStep = step  
                }  

                saveOrderRecord(orderRecord)  
                Log.d(TAG, "Step finished: $transNo -> $step, success: $success")  
            }  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to finish step: ${e.message}")  
        }  
    }  

    // 标记订单失败  
    private fun failOrder(transNo: String, failedStep: OrderStep, errorCode: Int, errorMessage: String) {  
        try {  
            val orderRecord = getOrderRecord(transNo)  
            if (orderRecord != null) {  
                orderRecord.status = OrderStatus.FAILED  
                orderRecord.failedStep = failedStep  
                orderRecord.updateTime = System.currentTimeMillis()  

                saveOrderRecord(orderRecord)  
                Log.d(TAG, "Order failed: $transNo at step $failedStep")  
            }  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to mark order as failed: ${e.message}")  
        }  
    }  

    // 创建新订单记录  
    private fun createOrderRecord(transNo: String, productId: String, productType: String) {  
        try {  
            val currentTime = System.currentTimeMillis()  
            val orderRecord = OrderRecord(  
                transNo = transNo,  
                productId = productId,  
                productType = productType,  
                createTime = currentTime,  
                updateTime = currentTime,  
                currentStep = OrderStep.CREATED,  
                status = OrderStatus.PROCESSING  
            )  

            val stepRecord = StepRecord(  
                step = OrderStep.CREATED,  
                startTime = currentTime,  
                endTime = currentTime,  
                success = true  
            )  
            orderRecord.stepHistory.add(stepRecord)  

            saveOrderRecord(orderRecord)  
            Log.d(TAG, "Order record created: $transNo")  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to create order record: ${e.message}")  
        }  
    }  

    // 根据purchaseToken查找订单记录  
    private fun findOrderByPurchaseToken(purchaseToken: String): OrderRecord? {  
        return try {  
            val allKeys = sp?.all?.keys ?: emptySet()  
            for (key in allKeys) {  
                if (key.startsWith("order_")) {  
                    val json = sp?.getString(key, null)  
                    if (json != null) {  
                        val orderRecord = jsonToOrderRecord(json)  
                        if (orderRecord?.purchaseToken == purchaseToken) {  
                            return orderRecord  
                        }  
                    }  
                }  
            }  
            null  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to find order by purchase token: ${e.message}")  
            null  
        }  
    }  

    fun getProductInfo(  
        type: String,  
        productIds: UTSArray<String>,  
        callback: ((Boolean, UTSJSONObject) -> Unit)?,  
    ) {  

        GlobalScope.launch(Dispatchers.IO) {  
            val response = UTSJSONObject()  
            try {  
                if (isBillingConnect()) {  
                    val productList = arrayListOf<QueryProductDetailsParams.Product>()  
                    productIds.map { it ->  
                        productList.add(  
                            QueryProductDetailsParams.Product.newBuilder().setProductId(it)  
                                .setProductType(type).build()  
                        )  
                    }  
                    val productDetail = getProductDetail(type, productIds)  
                    val billingResult = productDetail.billingResult  
                    val productDetailsList = productDetail.productDetailsList  

                    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && productDetailsList != null) {  
                        val products = arrayListOf<UTSJSONObject>()  
                        productDetailsList!!.forEach { productDetails ->  
                            val product = UTSJSONObject()  
                            product["productId"] = productDetails.productId  
                            product["title"] = productDetails.title  
                            product["description"] = productDetails.description  
                            product["price"] =  
                                productDetails.oneTimePurchaseOfferDetails?.formattedPrice  
                            product["priceAmountMicros"] =  
                                productDetails.oneTimePurchaseOfferDetails?.priceAmountMicros  
                            product["priceCurrencyCode"] =  
                                productDetails.oneTimePurchaseOfferDetails?.priceCurrencyCode  
                            products.add(product)  
                        }  
                        response["products"] = products  
                        callback?.invoke(true, response)  

                    } else {  
                        response["message"] = "Billing client is not connected"  
                        callback?.invoke(false, response)  
                    }  

                } else {  
                    Log.e(TAG, "Billing client is not connected")  
                    response["message"] = "Billing client is not connected"  
                    callback?.invoke(false, response)  
                }  
            } catch (e: Exception) {  
                Log.e(TAG, "Error in getProductInfo: ${e.message}", e)  
                response["message"] = "Exception: ${e.message}"  
                callback?.invoke(false, response)  
            }  
        }  
    }  

    private suspend fun launchBilling(  
        activity: Activity,  
        billingClient: BillingClient,  
        productDetails: ProductDetails,  
        offerToken: String  
    ): BillingResult {  
        return withContext(context = Dispatchers.Main.immediate) {  
            val billingFlowParams = getBillingFlowParams(productDetails, offerToken)  
            billingClient.launchBillingFlow(activity, billingFlowParams)  
        }  
    }  

    private suspend fun getProductDetail(  
        type: String, productIds: UTSArray<String>  
    ): ProductDetailsResult {  
        val productDetail: ArrayList<QueryProductDetailsParams.Product> = ArrayList()  
        for (i in 0 until productIds.size) {  
            val productId = productIds[i]  
            val product: QueryProductDetailsParams.Product =  
                QueryProductDetailsParams.Product.newBuilder().setProductId(productId)  
                    .setProductType(type).build()  
            productDetail.add(product)  
        }  
        val productDetailsParams =  
            QueryProductDetailsParams.newBuilder().setProductList(productDetail).build()  
        return getClient().queryProductDetails(productDetailsParams)  
    }  

    private fun getBillingFlowParams(  
        targetProductDetails: ProductDetails, tranNo: String  
    ): BillingFlowParams {  

        var offerToken: String? = null  
        if (targetProductDetails.productType == BillingClient.ProductType.SUBS) {  
            if (!targetProductDetails.subscriptionOfferDetails.isNullOrEmpty()) {  
                offerToken = targetProductDetails.subscriptionOfferDetails!!.first().offerToken  
            }  
        }  
        val productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder().apply {  
            setProductDetails(targetProductDetails)  
            if (offerToken != null) {  
                setOfferToken(offerToken)  
            }  
        }.build()  

        return BillingFlowParams.newBuilder()  
            .setProductDetailsParamsList(arrayListOf(productDetailsParams))  
            .setObfuscatedAccountId(tranNo).build()  
    }  

    override fun onPurchasesUpdated(  
        billingResult: BillingResult,  
        purchases: List<Purchase>?,  
    ) {  
        val transNo = currentTransNo  
        if (transNo != null) {  
            startStep(transNo, OrderStep.PAYMENT_RESULT)  
        }  

        if (purchasesUpdatedCallback == null) {  
            return  
        }  
        val response = UTSJSONObject()  

        if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {  
            if (transNo != null) {  
                when (billingResult.responseCode) {  
                    BillingClient.BillingResponseCode.USER_CANCELED -> {  
                        // 用户取消 - 直接删除订单记录,不当作错误保存  
                        Log.d(TAG, "User canceled purchase, deleting order record: $transNo")  
                        deleteOrderRecord(transNo)  
                        response["appCode"] = FAIL_CODE_CANCEL  
                    }  
                    else -> {  
                        // 其他错误 - 记录为失败订单  
                        finishStep(transNo, OrderStep.PAYMENT_RESULT, false, FAIL_CODE_SKU_TYPE, billingResult.debugMessage)  
                        response["appCode"] = FAIL_CODE_SKU_TYPE  
                    }  
                }  
            } else {  
                response["appCode"] = FAIL_CODE_SKU_TYPE  
            }  
            response["message"] = billingResult.debugMessage  
            response["googleCode"] = billingResult.responseCode  
            purchasesUpdatedCallback?.invoke(false, response)  
            return  
        }  

        if (purchases.isNullOrEmpty()) {  
            if (transNo != null) {  
                finishStep(transNo, OrderStep.PAYMENT_RESULT, false, FAIL_CODE_GOOGLE_DEF, "No purchases returned")  
            }  
            response["message"] = billingResult.debugMessage  
            response["googleCode"] = FAIL_CODE_GOOGLE_DEF  
            response["appCode"] = FAIL_CODE_SKU_TYPE  
            purchasesUpdatedCallback?.invoke(false, response)  
            return  
        }  

        // 支付成功,保存购买信息到订单记录  
        if (transNo != null && purchases.isNotEmpty()) {  
            val purchase = purchases.first()  
            val orderRecord = getOrderRecord(transNo)  
            if (orderRecord != null) {  
                orderRecord.purchaseToken = purchase.purchaseToken  
                orderRecord.originalJson = purchase.originalJson  
                saveOrderRecord(orderRecord)  
            }  
            finishStep(transNo, OrderStep.PAYMENT_RESULT, true)  
        }  

        response["result"] = purchases.map { purchase ->  
            val purchaseJson = UTSJSONObject()  
            purchaseJson["orderId"] = purchase.orderId  
            purchaseJson["originalJson"] = purchase.originalJson  
            purchaseJson["packageName"] = purchase.packageName  
            purchaseJson["purchaseTime"] = purchase.purchaseTime  
            purchaseJson["purchaseState"] = purchase.purchaseState  
            purchaseJson["purchaseToken"] = purchase.purchaseToken  
            purchaseJson["quantity"] = purchase.quantity  
            purchaseJson["acknowledged"] = purchase.isAcknowledged  
        }  
        purchasesUpdatedCallback?.invoke(true, response)  

    }  

    private suspend fun queryPurchases(type: String): PurchasesResult {  
        val queryPurchasesParams = QueryPurchasesParams.newBuilder().setProductType(type).build()  
        return getClient().queryPurchasesAsync(queryPurchasesParams)  
    }  

    private suspend fun purchaseAcknowledge(purchase: Purchase): BillingResult {  
        val acknowledgePurchaseParams =  
            AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.purchaseToken).build()  
        return getClient().acknowledgePurchase(acknowledgePurchaseParams)  
    }  

    fun purchaseConsume(  
        purchaseToken: String, originalJson: String, callback: ((Boolean, UTSJSONObject) -> Unit)?  
    ) {  
        val orderRecord = findOrderByPurchaseToken(purchaseToken)  
        val transNo = orderRecord?.transNo  

        if (transNo != null) {  
            startStep(transNo, OrderStep.CONSUME_START)  
        }  

        val consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build()  
        getClient().consumeAsync(consumeParams, fun(result: BillingResult, _: String) {  
            val response = UTSJSONObject()  
            val success = result.responseCode == BillingClient.BillingResponseCode.OK  

            if (transNo != null) {  
                if (success) {  
                    finishStep(transNo, OrderStep.CONSUME_COMPLETE, true)  
                    // 消费成功,删除订单记录  
                    deleteOrderRecord(transNo)  
                } else {  
                    finishStep(transNo, OrderStep.CONSUME_COMPLETE, false, FAIL_CODE_CONSUME, result.debugMessage)  
                }  
            }  

            if (success) {  
                response["originalJson"] = originalJson  
                callback?.invoke(true, response)  
            } else {  
                response["message"] = result.debugMessage  
                response["appCode"] = FAIL_CODE_CONSUME  
                response["googleCode"] = result.responseCode  
                callback?.invoke(false, response)  
            }  
        })  
    }  

    fun purchaseAcknowledge(  
        purchaseToken: String, originalJson: String, callback: ((Boolean, UTSJSONObject) -> Unit)?  
    ) {  
        val orderRecord = findOrderByPurchaseToken(purchaseToken)  
        val transNo = orderRecord?.transNo  

        if (transNo != null) {  
            startStep(transNo, OrderStep.ACKNOWLEDGE_START)  
        }  

        val acknowledgePurchaseParams =  
            AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build()  
        getClient().acknowledgePurchase(acknowledgePurchaseParams, fun(result: BillingResult) {  
            val response = UTSJSONObject()  
            val success = result.responseCode == BillingClient.BillingResponseCode.OK  

            if (transNo != null) {  
                if (success) {  
                    finishStep(transNo, OrderStep.ACKNOWLEDGE_COMPLETE, true)  
                    // 确认成功,删除订单记录  
                    deleteOrderRecord(transNo)  
                } else {  
                    finishStep(transNo, OrderStep.ACKNOWLEDGE_COMPLETE, false, FAIL_CODE_ACKNOWLEDGE, result.debugMessage)  
                }  
            }  

            if (success) {  
                response["originalJson"] = originalJson  
                callback?.invoke(true, response)  
            } else {  
                response["message"] = result.debugMessage  
                response["appCode"] = FAIL_CODE_ACKNOWLEDGE  
                response["googleCode"] = result.responseCode  
                callback?.invoke(false, response)  
            }  
        })  
    }  

    fun executePay(  
        activity: Activity,  
        productId: String,  
        tranNo: String,  
        type: String,  
        callback: ((Boolean, UTSJSONObject) -> Unit)?,  
    ) {  
        GlobalScope.launch(Dispatchers.IO) {  
            val result = UTSJSONObject()  

            // 创建订单记录  
            createOrderRecord(tranNo, productId, type)  
            currentTransNo = tranNo  

            // 开始连接Billing服务  
            startStep(tranNo, OrderStep.BILLING_CONNECT)  
            val billingConnected = isBillingConnect()  
            finishStep(tranNo, OrderStep.BILLING_CONNECT, billingConnected,  
                if (!billingConnected) FAIL_CODE_CONNECT else null,  
                if (!billingConnected) "BillingClient connection failed" else null)  

            if (billingConnected) {  
                // 开始查询商品信息  
                startStep(tranNo, OrderStep.PRODUCT_QUERY)  
                val productDetail = getProductDetail(type, UTSArray<String>().apply {  
                    add(productId)  
                })  

                val productQuerySuccess = productDetail.billingResult.responseCode == BillingClient.BillingResponseCode.OK  
                finishStep(tranNo, OrderStep.PRODUCT_QUERY, productQuerySuccess,  
                    if (!productQuerySuccess) FAIL_CODE_SKU_CHECK else null,  
                    if (!productQuerySuccess) productDetail.billingResult.debugMessage else null)  

                if (!productQuerySuccess) {  
                    result["appCode"] = FAIL_CODE_SKU_CHECK  
                    result["googleCode"] = productDetail.billingResult.responseCode  
                    result["message"] = productDetail.billingResult.debugMessage  
                    callback?.invoke(false, result)  
                    return@launch  
                }  
                val productDetails = productDetail.productDetailsList  
                val detail = productDetails?.find { it.productId == productId }  
                if (detail == null) {  
                    finishStep(tranNo, OrderStep.PRODUCT_QUERY, false, FAIL_CODE_DISCONNECT, "Product not found")  
                    result["appCode"] = FAIL_CODE_DISCONNECT  
                    result["googleCode"] = FAIL_CODE_CONNECT  
                    result["message"] = "Product not found"  
                    callback?.invoke(false, result)  
                    return@launch  
                } else {  
                    // 开始启动支付流程  
                    startStep(tranNo, OrderStep.LAUNCH_BILLING)  
                    val billingResult = launchBilling(activity, getClient(), detail, tranNo)  
                    val launchSuccess = billingResult.responseCode == BillingClient.BillingResponseCode.OK  
                    finishStep(tranNo, OrderStep.LAUNCH_BILLING, launchSuccess,  
                        if (!launchSuccess) FAIL_CODE_CALL_PAY else null,  
                        if (!launchSuccess) billingResult.debugMessage else null)  

                    if (!launchSuccess) {  
                        result["appCode"] = FAIL_CODE_CALL_PAY  
                        result["googleCode"] = billingResult.responseCode  
                        result["message"] = billingResult.debugMessage  
                        callback?.invoke(false, result)  
                    } else {  
                        this@Billing.purchasesUpdatedCallback = callback  
                    }  
                }  
            } else {  
                result["appCode"] = FAIL_CODE_CONNECT  
                result["googleCode"] = FAIL_CODE_CONNECT  
                result["message"] = "BillingClient is not connected"  
                callback?.invoke(false, result)  
            }  
        }  

    }  

    fun consumeAllPurchase(callback: ((Boolean, UTSJSONObject) -> Unit)?) {  

        GlobalScope.launch(Dispatchers.IO) {  
            if (!isBillingConnect()) {  
                val result = UTSJSONObject()  
                result["appCode"] = FAIL_CODE_CONNECT  
                result["googleCode"] = FAIL_CODE_CONNECT  
                result["message"] = "BillingClient is not connected"  
                callback?.invoke(false, result)  
                return@launch  
            }  
            val purchasesInApp = queryPurchases(BillingClient.ProductType.INAPP)  
            if (purchasesInApp.billingResult.responseCode == BillingClient.BillingResponseCode.OK) {  
                purchasesInApp.purchasesList?.forEach { purchase ->  
                    purchaseConsume(purchase.purchaseToken, purchase.originalJson, callback)  
                }  
            } else {  
                callback?.invoke(false, UTSJSONObject().apply {  
                    this["appCode"] = FAIL_CODE_CONSUME_QUERY  
                    this["googleCode"] = purchasesInApp.billingResult.responseCode  
                    this["message"] = purchasesInApp.billingResult.debugMessage  
                })  
            }  

            val purchasesSubs = queryPurchases(BillingClient.ProductType.SUBS)  
            if (purchasesSubs.billingResult.responseCode == BillingClient.BillingResponseCode.OK) {  
                purchasesSubs.purchasesList?.forEach { purchase ->  
                    purchaseAcknowledge(purchase.purchaseToken, purchase.originalJson, callback)  
                }  
            } else {  
                callback?.invoke(false, UTSJSONObject().apply {  
                    this["appCode"] = FAIL_CODE_ACKNOWLEDGE_QUERY  
                    this["googleCode"] = purchasesSubs.billingResult.responseCode  
                    this["message"] = purchasesSubs.billingResult.debugMessage  
                })  
            }  

        }  

    }  

    // ===== 上传和分析相关方法 =====  

    // 获取所有订单记录的JSON字符串  
    fun getUnuploadedOrders(): String {  
        return try {  
            val ordersArray = JSONArray()  
            val allKeys = sp?.all?.keys ?: emptySet()  

            for (key in allKeys) {  
                if (key.startsWith("order_")) {  
                    val json = sp?.getString(key, null)  
                    if (json != null) {  
                        val orderRecord = jsonToOrderRecord(json)  
                        if (orderRecord != null) {  
                            val orderJson = JSONObject(orderRecordToJson(orderRecord))  
                            ordersArray.put(orderJson)  
                        }  
                    }  
                }  
            }  

            val result = JSONObject()  
            result.put("orders", ordersArray)  
            result.put("total", ordersArray.length())  
            result.put("timestamp", System.currentTimeMillis())  

            Log.d(TAG, "Found ${ordersArray.length()} orders for export")  
            result.toString()  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to get orders JSON: ${e.message}")  
            JSONObject().apply {  
                put("orders", JSONArray())  
                put("total", 0)  
                put("error", e.message)  
            }.toString()  
        }  
    }  

    // 删除指定订单记录  
    fun deleteOrder(transNo: String) {  
        deleteOrderRecord(transNo)  
    }  

    // 批量删除订单记录  
    fun deleteOrders(transNos: List<String>) {  
        transNos.forEach { deleteOrderRecord(it) }  
    }  

    // 清理所有订单记录  
    fun clearAllOrders() {  
        try {  
            val allKeys = sp?.all?.keys?.toList() ?: emptyList()  
            var deletedCount = 0  

            for (key in allKeys) {  
                if (key.startsWith("order_")) {  
                    sp?.edit()?.remove(key)?.apply()  
                    deletedCount++  
                }  
            }  

            Log.d(TAG, "Cleared $deletedCount order records")  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to clear all orders: ${e.message}")  
        }  
    }  

}

操作步骤:

云打包一直不成功

预期结果:

能正常云打包成功

实际结果:

云打包一直不成功

bug描述:

UTS api插件开发 ,云打包失败:https://app.liuyingyong.cn/build/errorLog/fe5f8a70-6c55-11f0-a0b0-0f9729c8dbeb

上周打包还是成功的,UTS插件没动过。现在打包就失败了。

e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:395:41 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.402 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.402 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:396:41 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.402 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.402 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:397:46 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.402 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.402 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:451:8 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.402 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.403 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:462:28 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.403 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.403 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:462:28 Class 'com.android.billingclient.api.BillingClientKotlinKt' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.403 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/BillingClientKotlinKt.class  
[HBuilder] 17:16:05.403 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:567:47 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.404 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.404 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:569:28 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.404 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.405 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:569:28 Class 'com.android.billingclient.api.BillingClientKotlinKt' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.405 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/BillingClientKotlinKt.class  
[HBuilder] 17:16:05.405 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:575:28 Class 'com.android.billingclient.api.BillingClientKotlinKt' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.405 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/BillingClientKotlinKt.class  
[HBuilder] 17:16:05.405 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:678:37 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.405 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.405 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:682:43 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.405 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.406 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:685:47 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.406 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.406 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:689:44 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.406 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.406 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:690:41 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.406 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.407 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:694:38 Class 'com.android.billingclient.api.ProductDetailsResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.408 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/ProductDetailsResult.class  
[HBuilder] 17:16:05.408 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:743:34 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.408 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.409 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:744:17 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.409 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.409 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:745:17 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.409 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.409 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:751:42 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.410 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.410 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:752:39 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.410 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.410 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:756:33 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.410 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.410 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:757:17 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.410 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.410 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:758:17 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.411 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.411 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:764:42 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.411 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class  
[HBuilder] 17:16:05.411 e: file://uni_modules/native-google/utssdk/app-android/src/Billing.kt:765:39 Class 'com.android.billingclient.api.PurchasesResult' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.0.0, expected version is 1.8.0.  
[HBuilder] 17:16:05.411 The class is loaded from /home/pandora/.gradle/caches/8.11.1/transforms/c2d24a685e83292680c46fa7617c67c7/transformed/jetified-billing-ktx-7.1.1-api.jar!/com/android/billingclient/api/PurchasesResult.class
2025-07-30 09:55 负责人:无 分享
已邀请:
DCloud_App_Array

DCloud_App_Array

这是因为google billing-ktx:7.1.1使用的是kotlin 2.0.0 编译环境,目前云端打包使用的是 kotlin 1.8.0。
建议先试用 google billing-ktx 低版本,HBuilderX4.81版本将会升级云端打包的 kotlin 环境为2.2.0。

要回复问题请先登录注册