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}")
}
}
}
1 个回复
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。