1. 简介
Google Play 结算系统是一项可让您在 Android 应用中销售数字商品和内容的服务。这是您销售应用内商品并利用应用变现的最直接方式。此 Codelab 将向您介绍如何使用 Google Play 结算库在项目中销售订阅,在将购买交易与应用的其余部分集成时,可以封装购买细节。
还介绍了与订阅相关的概念,例如基础方案、优惠、标签和预付费方案。如需详细了解 Google Play 结算服务上的订阅,您可以访问我们的帮助中心。
构建内容
在此 Codelab 中,您会将最新的结算库(版本 5.0.0)添加到一个简单的基于订阅的个人资料应用中。该应用已为您构建,您只需添加结算部分即可。如图 1 所示,在该应用中,用户通过两种可再生订阅产品(基本版和付费版)或不可续订的预付费方案来订阅任何基础方案和/或优惠。仅此而已。基础方案分别提供包月和包年订阅。用户可以升级或降级预付费订阅,也可以将预付费订阅转换为可续订订阅。
如需将 Google Play 结算库集成到您的应用中,您需要创建以下内容:
BillingClientWrapper
- BillingClient 库的封装容器。它旨在封装与 Play 结算库的 BillingClient 的互动,但这在您自己的集成中并非必需。SubscriptionDataRepository
- 应用的结算代码库,其中包含应用的订阅商品目录(即待售商品)列表,以及有助于收集购买交易状态和商品详情的 ShareFlow 变量列表MainViewModel
- 一个 ViewModel,应用的其余部分通过它与结算代码库进行通信。这有助于使用各种购买方法在界面中启动结算流程。
完成后,应用的架构应如下图所示:
学习内容
- 如何集成 Play 结算库
- 如何通过 Play 管理中心创建订阅商品、基础方案、优惠和标签
- 如何从应用中检索可用的基础方案和优惠
- 如何使用适当的参数启动结算流程
- 如何提供预付费订阅产品
此 Codelab 将重点介绍 Google Play 结算服务。对于不相关的概念,我们仅会略作介绍,但是会提供相应代码块供您复制和粘贴。
所需条件
- 最新版本的 Android Studio (>= Arctic Fox | 2020.3.1)
- 搭载 Android 8.0 或更高版本的 Android 设备
- GitHub 上为您提供的示例代码(将在后面几节中提供说明)
- 适度了解 Android Studio 的 Android 开发知识
- 了解如何将应用发布到 Google Play 商店
- 有中等程度的 Kotlin 代码编写经验
- Google Play 结算库 5.0.0 版
2. 准备工作
从 GitHub 获取代码
我们已将您完成此项目所需的一切都放入一个 Git 代码库中。首先,您需要获取代码,并在您喜爱的开发环境中将其打开。对于此 Codelab,我们建议使用 Android Studio。
入门代码存储在 GitHub 代码库中。您可以通过以下命令克隆代码库:
git clone https://github.com/android/play-billing-samples.git cd PlayBillingCodelab
3. 基础
我们从何处入手?
我们首先为本 Codelab 设计了一个基本用户个人资料应用。代码已经过简化,可显示我们想要说明的概念,但并非专为生产环境而设计。如果您选择在正式版应用中重复使用此代码的任何部分,请务必遵循最佳实践并全面测试您的所有代码。
将项目导入 Android Studio
PlayBillingCodelab 是不包含 Google Play 结算服务实现的基础应用。启动 Android Studio,然后选择 Open > billing/PlayBillingCodelab
以导入 billing-Codelab
该项目包含两个模块:
- start 具有框架应用,但缺少所需的依赖项以及您需要实现的所有方法。
- finished 具有已完成的项目,可以在您遇到困难时用作指导。
该应用包含八个类文件:BillingClientWrapper
、SubscriptionDataRepository
、Composables
、MainState
、MainViewModel
、MainViewModelFactory
和 MainActivity
。
- BillingClientWrapper 是一种封装容器,用于隔离 Google Play 结算服务所需的 [BillingClient] 方法,该方法需要简单实现,并将响应发送到数据存储区进行处理。
- SubscriptionDataRepository 用于抽象化 Google Play 结算服务数据源(即结算客户端库),并将 BillingClientWrapper 中发出的 StateFlow 数据转换为数据流。
- ButtonModel 是用于在界面中构建按钮的数据类。
- Composables会将界面的所有可组合方法提取到一个类中。
- MainState 是用于状态管理的数据类。
- MainViewModel 用于保存界面中使用的结算相关数据和状态。它将 SubscriptionDataRepository 中的所有流合并为一个状态对象。
- MainActivity 是用于为界面加载可组合项的主 activity 类。
- 常量是指包含多个类使用的常量的对象。
Gradle
您需要添加 Gradle 依赖项,才能将 Google Play 结算服务添加到您的应用中。打开应用模块的 build.gradle 文件,并添加以下内容:
dependencies { val billing_version = "5.0.0" implementation("com.android.billingclient:billing:$billing_version") }
Google Play 管理中心
在此 Codelab 中,您需要在 Google Play 管理中心的“订阅”部分创建以下两种订阅产品:
- 1 项基本订阅(产品 ID 为
up_basic_sub
)
该商品应具有 3 个带相关标签的基础方案(2 个自动续订型和 1 个预付费方案)和 1 个带关联标签的基础方案:1 个月度基本订阅,标签为 monthlybasic
;1 个基本年度订阅,标签为 yearlybasic
;1 个预付费订阅,标签为 prepaidbasic
您可以在基础方案中添加优惠。优惠会沿用其关联的基础方案中的标签。
- 1 项产品 ID 为
up_premium_sub
的付费订阅
该商品应有 3 个基础方案(2 个自动续订型订阅和 1 个预付费方案)并带有关联标签:1 个月度基本订阅,标签为 monthlypremium
,1 个基本年度订阅,标签为 yearlypremium
,1 个预付费订阅,标签为 prepaidpremium
您可以在基础方案中添加优惠。优惠会沿用其关联的基础方案中的标签。
如需详细了解如何创建订阅商品、基础方案、优惠和标签,请参阅 Google Play 帮助中心。
4. 结算客户端设置
在本部分,您将在 BillingClientWrapper
类中进行操作。
最终,您将拥有实现结算客户端所需的一切,以及所有相关方法。
- 初始化 BillingClient
添加对 Google Play 结算库的依赖项后,我们需要初始化 BillingClient
实例。
BillingClientWrapper.kt
private val billingClient = BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
- 与 Google Play 建立连接
在您创建 BillingClient 后,我们需要与 Google Play 建立连接。
为了连接到 Google Play,我们会调用 startConnection()
。连接过程是异步进行的,因此我们需要实现一个 BillingClientStateListener
,以便在客户端的设置完成并准备好发出进一步的请求后接收回调。
BillingClientWrapper.kt
fun startBillingConnection(billingConnectionState: MutableLiveData<Boolean>) {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "Billing response OK")
// The BillingClient is ready. You can query purchases and product details here
queryPurchases()
queryProductDetails()
billingConnectionState.postValue(true)
} else {
Log.e(TAG, billingResult.debugMessage)
}
}
override fun onBillingServiceDisconnected() {
Log.i(TAG, "Billing connection disconnected")
startBillingConnection(billingConnectionState)
}
})
}
- 查询 Google Play 结算服务是否存在现有购买交易
与 Google Play 建立连接后,我们就可以通过调用 queryPurchasesAsync()
来查询用户之前进行的购买交易。
BillingClientWrapper.kt
fun queryPurchases() {
if (!billingClient.isReady) {
Log.e(TAG, "queryPurchases: BillingClient is not ready")
}
// Query for existing subscription products that have been purchased.
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build()
) { billingResult, purchaseList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
if (!purchaseList.isNullOrEmpty()) {
_purchases.value = purchaseList
} else {
_purchases.value = emptyList()
}
} else {
Log.e(TAG, billingResult.debugMessage)
}
}
}
- 展示可供购买的商品
我们现在可以查询有货的商品并将其显示给用户。为了向 Google Play 查询订阅商品详情,我们将调用 queryProductDetailsAsync()
。在向用户展示商品之前,查询商品详情是非常重要的一步,因为查询会返回本地化的商品信息。
BillingClientWrapper.kt
fun queryProductDetails() {
val params = QueryProductDetailsParams.newBuilder()
val productList = mutableListOf<QueryProductDetailsParams.Product>()
for (product in LIST_OF_PRODUCTS) {
productList.add(
QueryProductDetailsParams.Product.newBuilder()
.setProductId(product)
.setProductType(BillingClient.ProductType.SUBS)
.build()
)
params.setProductList(productList).let { productDetailsParams ->
Log.i(TAG, "queryProductDetailsAsync")
billingClient.queryProductDetailsAsync(productDetailsParams.build(), this)
}
}
}
- 为 ProductDetails 查询设置监听器
注意:此方法会在映射中发出查询到 _productWithProductDetails
的结果。
另请注意,查询预计会返回 ProductDetails
。如果上述情况并未发生,很可能是因为在 Play 管理中心内设置的产品尚未激活,或者您未在任何发布轨道中发布具有结算客户端依赖项的 build。
BillingClientWrapper.kt
override fun onProductDetailsResponse(
billingResult: BillingResult,
productDetailsList: MutableList<ProductDetails>
) {
val responseCode = billingResult.responseCode
val debugMessage = billingResult.debugMessage
when (responseCode) {
BillingClient.BillingResponseCode.OK -> {
var newMap = emptyMap<String, ProductDetails>()
if (productDetailsList.isNullOrEmpty()) {
Log.e(
TAG,
"onProductDetailsResponse: " +
"Found null or empty ProductDetails. " +
"Check to see if the Products you requested are correctly " +
"published in the Google Play Console."
)
} else {
newMap = productDetailsList.associateBy {
it.productId
}
}
_productWithProductDetails.value = newMap
}
else -> {
Log.i(TAG, "onProductDetailsResponse: $responseCode $debugMessage")
}
}
}
- 启动购买流程
launchBillingFlow
是用户点击购买商品时调用的方法。它会提示 Google Play 使用商品的 ProductDetails
启动购买流程。
BillingClientWrapper.kt
fun launchBillingFlow(activity: Activity, params: BillingFlowParams) {
if (!billingClient.isReady) {
Log.e(TAG, "launchBillingFlow: BillingClient is not ready")
}
billingClient.launchBillingFlow(activity, params)
}
- 设置用于监听购买操作结果的监听器
当用户退出 Google Play 购买屏幕(通过点按“购买”按钮完成购买,或点按“返回”按钮取消购买)时,onPurchaseUpdated()
回调会将购买流程的结果发送回您的应用。然后,您可以根据 BillingResult.responseCode
确定用户是否成功购买了产品。如果为 responseCode == OK
,则表示购买交易已成功完成。
onPurchaseUpdated()
会传回 Purchase
对象列表,其中包含用户通过应用进行的所有购买交易。在许多其他字段中,每个 Purchase 对象都包含商品 ID、purchaseToken 和 isAcknowledged
属性。通过使用这些字段,您可以为每个 Purchase
对象确定是需要处理的新购买交易,还是无需进一步处理的现有购买交易。
对于订阅购买交易,处理过程类似于确认新购买交易。
BillingClientWrapper.kt
override fun onPurchasesUpdated(
billingResult: BillingResult,
purchases: List<Purchase>?
) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK
&& !purchases.isNullOrEmpty()
) {
// Post new purchase List to _purchases
_purchases.value = purchases
// Then, handle the purchases
for (purchase in purchases) {
acknowledgePurchases(purchase)
}
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
Log.e(TAG, "User has cancelled")
} else {
// Handle any other error codes.
}
}
- 处理购买交易(验证和确认购买交易)
用户完成购买后,应用需要确认购买交易,以处理该购买交易。
此外,在成功处理确认后,_isNewPurchaseAcknowledged
的值会设置为 true。
BillingClientWrapper.kt
private fun acknowledgePurchases(purchase: Purchase?) {
purchase?.let {
if (!it.isAcknowledged) {
val params = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(it.purchaseToken)
.build()
billingClient.acknowledgePurchase(
params
) { billingResult ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK &&
it.purchaseState == Purchase.PurchaseState.PURCHASED
) {
_isNewPurchaseAcknowledged.value = true
}
}
}
}
}
- 终止结算连接
最后,当 activity 被销毁时,您需要终止与 Google Play 的连接,因此系统会调用 endConnection()
来执行此操作。
BillingClientWrapper.kt
fun terminateBillingConnection() {
Log.i(TAG, "Terminating connection")
billingClient.endConnection()
}
5. SubscriptionDataRepository
在 BillingClientWrapper
中,来自 QueryPurchasesAsync
和 QueryProductDetails
的响应会分别发布到 MutableStateFlow
_purchases
和 _productWithProductDetails
,它们在班级外公开,并且包含购买和 productWithProductDetails
。
在 SubscriptionDataRepository
中,系统会根据退回的购买交易对应的商品将购买交易处理到三个 Flow 中:hasRenewableBasic
、hasPrepaidBasic
、hasRenewablePremium
和 hasPremiumPrepaid
。
此外,productWithProductDetails
也会处理到相应的 basicProductDetails
和 premiumProductDetails
Flow。
6. MainViewModel
困难的部分已经完成了。现在,您将定义 MainViewModel
,这只是您的客户端的公共接口,因此客户无需了解 BillingClientWrapper
和 SubscriptionDataRepository
的内部构件。
首先,在 MainViewModel
中,我们在初始化 viewModel 时启动结算连接。
MainViewModel.kt
init {
billingClient.startBillingConnection(billingConnectionState = _billingConnectionState)
}
然后,按照 repo 类中处理的要求,仓库中的数据流分别被合并到 productsForSaleFlows
(针对可用产品)和 userCurrentSubscriptionFlow
(针对用户当前和有效订阅)中。
通过 currentPurchasesFlow
,界面也可获取当前购买交易的列表。
MainViewModel.kt
val productsForSaleFlows = combine(
repo.basicProductDetails,
repo.premiumProductDetails
) { basicProductDetails,
premiumProductDetails
->
MainState(
basicProductDetails = basicProductDetails,
premiumProductDetails = premiumProductDetails
)
}
// The userCurrentSubscriptionFlow object combines all the possible subscription flows into one
// for emission.
private val userCurrentSubscriptionFlow = combine(
repo.hasRenewableBasic,
repo.hasPrepaidBasic,
repo.hasRenewablePremium,
repo.hasPrepaidPremium
) { hasRenewableBasic,
hasPrepaidBasic,
hasRenewablePremium,
hasPrepaidPremium
->
MainState(
hasRenewableBasic = hasRenewableBasic,
hasPrepaidBasic = hasPrepaidBasic,
hasRenewablePremium = hasRenewablePremium,
hasPrepaidPremium = hasPrepaidPremium
)
}
// Current purchases.
val currentPurchasesFlow = repo.purchases
组合后的 userCurrentSubscriptionFlow
收集在 init 块中,相应的值会发布到名为 _destinationScreen
的 MutableLiveData 对象。
init {
viewModelScope.launch {
userCurrentSubscriptionFlow.collectLatest { collectedSubscriptions ->
when {
collectedSubscriptions.hasRenewableBasic == true &&
collectedSubscriptions.hasRenewablePremium == false -> {
_destinationScreen.postValue(DestinationScreen.BASIC_RENEWABLE_PROFILE)
}
collectedSubscriptions.hasRenewablePremium == true &&
collectedSubscriptions.hasRenewableBasic == false -> {
_destinationScreen.postValue(DestinationScreen.PREMIUM_RENEWABLE_PROFILE)
}
collectedSubscriptions.hasPrepaidBasic == true &&
collectedSubscriptions.hasPrepaidPremium == false -> {
_destinationScreen.postValue(DestinationScreen.BASIC_PREPAID_PROFILE_SCREEN)
}
collectedSubscriptions.hasPrepaidPremium == true &&
collectedSubscriptions.hasPrepaidBasic == false -> {
_destinationScreen.postValue(
DestinationScreen.PREMIUM_PREPAID_PROFILE_SCREEN
)
}
else -> {
_destinationScreen.postValue(DestinationScreen.SUBSCRIPTIONS_OPTIONS_SCREEN)
}
}
}
}
}
MainViewModel
还添加了一些非常实用的方法:
- 基础方案和优惠令牌检索
从 Play 结算库版本 5.0.0 开始,所有订阅商品都可以有多个基础方案和优惠,但预付费基础方案除外,这类方案不提供优惠。
此方法通过使用新引入的用于对相关优惠进行分组的代码概念,帮助检索用户有资格享受的所有优惠和基础方案。
例如,当用户尝试购买按月基本订阅时,与按月基本订阅商品相关联的所有基础方案和优惠都会使用字符串 monthlyBasic
进行标记。
MainViewModel.kt
private fun retrieveEligibleOffers(
offerDetails: MutableList<ProductDetails.SubscriptionOfferDetails>,
tag: String
): List<ProductDetails.SubscriptionOfferDetails> {
val eligibleOffers = emptyList<ProductDetails.SubscriptionOfferDetails>().toMutableList()
offerDetails.forEach { offerDetail ->
if (offerDetail.offerTags.contains(tag)) {
eligibleOffers.add(offerDetail)
}
}
return eligibleOffers
}
- 最低价格优惠计算方式
当用户有资格享受多项优惠时,系统会使用 leastPricedOfferToken()
方法计算 retrieveEligibleOffers()
返回的优惠中最低的优惠。
该方法会返回所选优惠的优惠 ID 令牌。
这种实现方法仅返回 pricingPhases
集内价格最低的商品,而不考虑平均值。
另一种实现方法是查看平均价格最低的商品。
MainViewModel.kt
private fun leastPricedOfferToken(
offerDetails: List<ProductDetails.SubscriptionOfferDetails>
): String {
var offerToken = String()
var leastPricedOffer: ProductDetails.SubscriptionOfferDetails
var lowestPrice = Int.MAX_VALUE
if (!offerDetails.isNullOrEmpty()) {
for (offer in offerDetails) {
for (price in offer.pricingPhases.pricingPhaseList) {
if (price.priceAmountMicros < lowestPrice) {
lowestPrice = price.priceAmountMicros.toInt()
leastPricedOffer = offer
offerToken = leastPricedOffer.offerToken
}
}
}
}
return offerToken
}
- BillingFlowParams 构建器
为启动某个特定产品的购买流程,该产品的需要设置 ProductDetails
和所选优惠的令牌,并将其用于构建 BilingFlowParams。
您可以采用以下两种方法:
upDowngradeBillingFlowParamsBuilder()
会构建用于升级和降级的参数。
MainViewModel.kt
private fun upDowngradeBillingFlowParamsBuilder(
productDetails: ProductDetails,
offerToken: String,
oldToken: String
): BillingFlowParams {
return BillingFlowParams.newBuilder().setProductDetailsParamsList(
listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(offerToken)
.build()
)
).setSubscriptionUpdateParams(
BillingFlowParams.SubscriptionUpdateParams.newBuilder()
.setOldPurchaseToken(oldToken)
.setReplaceProrationMode(
BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE
)
.build()
).build()
}
billingFlowParamsBuilder()
会为普通购买交易构建参数。
MainViewModel.kt
private fun billingFlowParamsBuilder(
productDetails: ProductDetails,
offerToken: String
): BillingFlowParams.Builder {
return BillingFlowParams.newBuilder().setProductDetailsParamsList(
listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(offerToken)
.build()
)
)
}
- 购买方式
购买方法使用 BillingClientWrapper
的 launchBillingFlow()
和 BillingFlowParams
来启动购买交易。
MainViewModel.kt
fun buy(
productDetails: ProductDetails,
currentPurchases: List<Purchase>?,
activity: Activity,
tag: String
) {
val offers =
productDetails.subscriptionOfferDetails?.let {
retrieveEligibleOffers(
offerDetails = it,
tag = tag.lowercase()
)
}
val offerToken = offers?.let { leastPricedOfferToken(it) }
val oldPurchaseToken: String
// Get current purchase. In this app, a user can only have one current purchase at
// any given time.
if (!currentPurchases.isNullOrEmpty() &&
currentPurchases.size == MAX_CURRENT_PURCHASES_ALLOWED
) {
// This either an upgrade, downgrade, or conversion purchase.
val currentPurchase = currentPurchases.first()
// Get the token from current purchase.
oldPurchaseToken = currentPurchase.purchaseToken
val billingParams = offerToken?.let {
upDowngradeBillingFlowParamsBuilder(
productDetails = productDetails,
offerToken = it,
oldToken = oldPurchaseToken
)
}
if (billingParams != null) {
billingClient.launchBillingFlow(
activity,
billingParams
)
}
} else if (currentPurchases == null) {
// This is a normal purchase.
val billingParams = offerToken?.let {
billingFlowParamsBuilder(
productDetails = productDetails,
offerToken = it
)
}
if (billingParams != null) {
billingClient.launchBillingFlow(
activity,
billingParams.build()
)
}
} else if (!currentPurchases.isNullOrEmpty() &&
currentPurchases.size > MAX_CURRENT_PURCHASES_ALLOWED
) {
// The developer has allowed users to have more than 1 purchase, so they need to
/// implement a logic to find which one to use.
Log.d(TAG, "User has more than 1 current purchase.")
}
}
- 终止结算连接
最后,系统会在 ViewModel 的 onCleared()
上调用 BillingClientWrapper
的 terminateBillingConnection
方法。
这是为了在关联的 activity 被销毁时终止当前的结算连接。
7. 界面
现在,该使用您在界面中构建的所有内容了。为此,您将使用可组合项和 MainActivity 类。
Composables.kt
Composables 类已完全提供,并定义了用于渲染界面的所有 Compose 函数以及这些函数之间的导航机制。
Subscriptions
函数会显示两个按钮:Basic Subscription
和 Premium Subscription
。
Basic Subscription
和 Premium Subscription
均会加载新的 Compose 方法,以显示三个相应的基础方案:包月、包年和预付费。
然后,针对用户可能拥有的特定订阅,有三种可能的配置文件组合函数:可再生的基本版、可再续的 Premium 以及预付费基本版或预付费高级版。
- 如果用户拥有基本版订阅,则可以通过基本个人资料升级到按月、按年或预付费的付费订阅。
- 相反,如果用户拥有付费订阅,则可以降级为按月、按年或预付费的基本订阅。
- 当用户拥有预付费订阅后,可以使用“充值”按钮为订阅充值,或将预付费订阅转换为相应的自动续订型基础方案。
最后是“正在加载”屏幕函数,可在与 Google Play 建立连接以及呈现用户个人资料时使用。
MainActivity.kt
- 创建
MainActivity
后,系统会实例化 viewModel,并加载名为MainNavHost
的 Compose 函数。 MainNavHost
从根据 viewModel 的billingConnectionSate
Livedata 创建的变量isBillingConnected
开始,并观察其变化,因为在实例化 vieModel 时,它会将billingConnectionSate
传递到BillingClientWrapper
的 startBillingConnection 方法。
isBillingConnected
在建立连接时设置为 true,如果未建立连接,则设置为 false。
如果为 false,系统会加载 LoadingScreen()
Compose 函数;如果为 true,则会加载 Subscription
或配置文件函数。
val isBillingConnected by viewModel.billingConnectionState.observeAsState()
- 建立结算连接后:
Compose navController
已实例化
val navController = rememberNavController()
然后,系统会收集 MainViewModel
中的数据流。
val productsForSale by viewModel.productsForSaleFlows.collectAsState(
initial = MainState()
)
val currentPurchases by viewModel.currentPurchasesFlow.collectAsState(
initial = listOf()
)
最后,会观察 viewModel 的 destinationScreen
LiveData 变量。
根据用户的当前订阅状态,呈现相应的 Compose 函数。
val screen by viewModel.destinationScreen.observeAsState()
when (screen) {
// User has a Basic Prepaid subscription
// the corresponding profile is loaded.
MainViewModel.DestinationScreen.BASIC_PREPAID_PROFILE_SCREEN -> {
UserProfile(
buttonModels =
listOf(
ButtonModel(R.string.topup_message) {
productsForSale.basicProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = null,
tag = PREPAID_BASIC_PLANS_TAG,
activity = activity
)
}
},
ButtonModel(R.string.convert_to_basic_monthly_message) {
productsForSale.basicProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = currentPurchases,
tag = MONTHLY_BASIC_PLANS_TAG,
activity = activity
)
}
},
ButtonModel(R.string.convert_to_basic_yearly_message) {
productsForSale.basicProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = currentPurchases,
tag = YEARLY_BASIC_PLANS_TAG,
activity = activity
)
}
},
),
tag = PREPAID_BASIC_PLANS_TAG,
profileTextStringResource = null
)
}
// User has a renewable basic subscription
// the corresponding profile is loaded.
MainViewModel.DestinationScreen.BASIC_RENEWABLE_PROFILE -> {
UserProfile(
buttonModels =
listOf(
ButtonModel(R.string.monthly_premium_upgrade_message) {
productsForSale.premiumProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = currentPurchases,
tag = MONTHLY_PREMIUM_PLANS_TAG,
activity = activity
)
}
},
ButtonModel(R.string.yearly_premium_upgrade_message) {
productsForSale.premiumProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = currentPurchases,
tag = YEARLY_PREMIUM_PLANS_TAG,
activity = activity
)
}
},
ButtonModel(R.string.prepaid_premium_upgrade_message) {
productsForSale.premiumProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = currentPurchases,
tag = PREPAID_PREMIUM_PLANS_TAG,
activity = activity
)
}
}
),
tag = null,
profileTextStringResource = R.string.basic_sub_message
)
}
// User has a prepaid Premium subscription
// the corresponding profile is loaded.
MainViewModel.DestinationScreen.PREMIUM_PREPAID_PROFILE_SCREEN -> {
UserProfile(
buttonModels =
listOf(
ButtonModel(R.string.topup_message) {
productsForSale.premiumProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = null,
tag = PREPAID_PREMIUM_PLANS_TAG,
activity = activity
)
}
},
ButtonModel(R.string.convert_to_premium_monthly_message) {
productsForSale.premiumProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = currentPurchases,
tag = MONTHLY_PREMIUM_PLANS_TAG,
activity = activity
)
}
},
ButtonModel(R.string.convert_to_premium_yearly_message) {
productsForSale.premiumProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = currentPurchases,
tag = YEARLY_PREMIUM_PLANS_TAG,
activity = activity
)
}
},
),
tag = PREPAID_PREMIUM_PLANS_TAG,
profileTextStringResource = null
)
}
// User has a renewable Premium subscription
// the corresponding profile is loaded.
MainViewModel.DestinationScreen.PREMIUM_RENEWABLE_PROFILE -> {
UserProfile(
listOf(
ButtonModel(R.string.monthly_basic_downgrade_message) {
productsForSale.basicProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = currentPurchases,
tag = MONTHLY_BASIC_PLANS_TAG,
activity = activity
)
}
},
ButtonModel(R.string.yearly_basic_downgrade_message) {
productsForSale.basicProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = currentPurchases,
tag = YEARLY_BASIC_PLANS_TAG,
activity = activity
)
}
},
ButtonModel(R.string.prepaid_basic_downgrade_message) {
productsForSale.basicProductDetails?.let {
viewModel.buy(
productDetails = it,
currentPurchases = currentPurchases,
tag = PREPAID_BASIC_PLANS_TAG,
activity = activity
)
}
}
),
tag = null,
profileTextStringResource = R.string.premium_sub_message
)
}
// User has no current subscription - the subscription composable
// is loaded.
MainViewModel.DestinationScreen.SUBSCRIPTIONS_OPTIONS_SCREEN -> {
SubscriptionNavigationComponent(
productsForSale = productsForSale,
navController = navController,
viewModel = viewModel
)
}
}
8. 解决方案代码
完整的解决方案代码可以在解决方案模块中找到。
9. 恭喜
恭喜!您已成功将 Google Play 结算库 5.0.0 订阅产品集成到一个非常简单的应用中!
如需查看具有安全服务器设置的更复杂应用的文档版本,请参阅官方示例。