如何通过Google Play Billing Library识别订阅时长(月付/年付)
判断Google Play Billing订阅的时长(月付/年付)
我在Kotlin应用中使用Google Play Billing Library实现订阅功能,同一个产品提供月付和年付两种订阅方案。用户完成购买后,通过BillingClient.queryPurchasesAsync()能获取购买详情,但无法直接得知订阅时长,希望判断用户购买的是月付还是年付订阅。
已尝试操作
- 通过
BillingClient.queryProductDetailsAsync()查询ProductDetails以获取SubscriptionOfferDetails,尝试对比Purchase对象与SubscriptionOfferDetails中的offerToken,但二者不匹配。
问题
如何正确判断已完成购买的订阅时长?有没有合适的方法将购买详情与具体优惠详情进行映射?恳请提供相关指导或示例!
现有相关代码
fun startBillingConnection() { billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { Log.d(TAG, "Billing response OK") queryPurchases() queryProductDetails() _billingState.update { it.copy(isClientConnected = true) } } else { Log.e(TAG, billingResult.debugMessage) handleBillingError(billingResult, ErrorContext.CONNECTION) _billingState.update { it.copy(isClientConnected = false) } } } override fun onBillingServiceDisconnected() { Log.i(TAG, "Billing connection disconnected") _billingState.update { it.copy(isClientConnected = false) } } }) } private fun querySubscriptionProducts() { val params = QueryProductDetailsParams.newBuilder() val productList = SUBS_PRODUCTS.map { productId -> QueryProductDetailsParams.Product.newBuilder() .setProductId(productId) .setProductType(BillingClient.ProductType.SUBS) .build() } params.setProductList(productList).let { productDetailsParams -> billingClient.queryProductDetailsAsync(productDetailsParams.build(), this) } } override fun onProductDetailsResponse( billingResult: BillingResult, productDetailsList: MutableList<ProductDetails> ) { when (billingResult.responseCode) { BillingClient.BillingResponseCode.OK -> { if (productDetailsList.isEmpty()) { Log.e(TAG, "onProductDetailsResponse: Found null or empty ProductDetails.") } else { val newProducts = productDetailsList.associateBy { it.productId } _billingState.update { it.copy(products = it.products + newProducts) } } } else -> { handleBillingError(billingResult, ErrorContext.PRODUCT_DETAILS) } } }
解决方案
1. 核心思路:通过Purchase的offerToken匹配对应SubscriptionOfferDetails
你之前的问题可能出在对比逻辑或购买发起时的参数传递——只要发起购买时正确传入了目标SubscriptionOfferDetails的offerToken,Purchase对象中的offerToken就会和该优惠详情的offerToken完全匹配。
匹配后,可从SubscriptionOfferDetails的pricingPhases中提取订阅周期信息,该字段采用ISO 8601格式(如P1M代表1个月,P1Y代表1年)。
2. 示例代码:匹配并判断订阅时长
假设你已经维护了products: Map<String, ProductDetails>的全局/局部映射,可添加以下逻辑:
// 根据Purchase判断订阅时长 fun getSubscriptionDuration(purchase: Purchase): String? { // 通过productId找到对应的ProductDetails val productDetails = products[purchase.productId] ?: return null // 匹配Purchase的offerToken与SubscriptionOfferDetails的offerToken val matchedOffer = productDetails.subscriptionOfferDetails?.find { offer -> offer.offerToken == purchase.offerToken } ?: return null // 获取订阅周期:如果有试用/优惠阶段,取最后一个阶段(常规订阅周期),否则取第一个 val targetPhase = if (matchedOffer.pricingPhases.pricingPhaseList.size > 1) { matchedOffer.pricingPhases.pricingPhaseList.last() } else { matchedOffer.pricingPhases.pricingPhaseList.firstOrNull() } ?: return null // 解析ISO 8601格式的周期字符串 return when (targetPhase.billingPeriod) { "P1M" -> "月付" "P1Y" -> "年付" "P3M" -> "季付" "P6M" -> "半年付" else -> "未知周期(${targetPhase.billingPeriod})" } } // 在查询购买记录的回调中调用 private fun queryPurchases() { billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder() .setProductType(BillingClient.ProductType.SUBS) .build()) { billingResult, purchases -> if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { purchases.forEach { purchase -> val duration = getSubscriptionDuration(purchase) Log.d(TAG, "用户订阅类型:$duration") } } } }
3. 关键注意事项
- 发起购买时,必须传入对应
SubscriptionOfferDetails的offerToken,否则Purchase中的offerToken无法匹配:// 发起购买的示例代码片段 val offerToken = matchedSubscriptionOffer.offerToken val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf( BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setOfferToken(offerToken) .build() )) .build() billingClient.launchBillingFlow(activity, billingFlowParams) - 若订阅包含试用/ introductory price,
pricingPhaseList会有多个阶段,优先取最后一个阶段作为常规订阅周期。
内容的提问来源于stack exchange,提问作者TheDragoner
相关产品推荐
相关产品推荐

