1. Pengantar
Sistem penagihan Google Play adalah layanan yang memungkinkan Anda menjual produk dan konten digital di aplikasi Android. Ini adalah cara paling langsung bagi Anda untuk menjual produk dalam aplikasi untuk memonetisasi aplikasi Anda. Codelab ini menunjukkan cara menggunakan Library Layanan Penagihan Google Play untuk menjual langganan di project dengan cara yang merangkum detail seluk-beluk saat mengintegrasikan pembelian dengan aplikasi Anda lainnya.
Rilis ini juga memperkenalkan konsep terkait langganan seperti paket dasar, penawaran, tag, dan paket prabayar. Untuk mempelajari lebih lanjut langganan di Layanan Penagihan Google Play, Anda dapat melihat Pusat Bantuan kami.
Yang akan Anda bangun
Dalam codelab ini, Anda akan menambahkan library penagihan terbaru (versi 5.0.0) ke aplikasi profil berbasis langganan yang sederhana. Aplikasi sudah dibuat untuk Anda, jadi Anda hanya perlu menambahkan bagian penagihan. Seperti yang ditunjukkan pada gambar 1, di aplikasi ini pengguna mendaftar salah satu paket dasar dan/atau penawaran yang ditawarkan melalui dua produk langganan terbaru (dasar dan premium) atau untuk prabayar yang tidak dapat diperbarui. Itu saja. Paket dasar masing-masing adalah langganan bulanan dan tahunan. Pengguna dapat mengupgrade, mendowngrade, atau mengonversi langganan prabayar menjadi langganan yang dapat diperpanjang.
Untuk menggabungkan Google Play Billing Library di aplikasi, Anda akan membuat hal berikut:
BillingClientWrapper
- wrapper untuk library BillingClient. Fungsi ini dimaksudkan untuk mengenkapsulasi interaksi dengan BillingClient Library Layanan Penagihan Play, tetapi tidak diperlukan dalam integrasi Anda sendiri.SubscriptionDataRepository
- repositori penagihan untuk aplikasi Anda yang berisi daftar inventaris produk langganan aplikasi (yaitu apa yang dijual), dan daftar variabel ShareFlow yang membantu mengumpulkan status pembelian dan detail produkMainViewModel
- ViewModel yang digunakan aplikasi Anda untuk berkomunikasi dengan repositori penagihan. Langkah ini membantu meluncurkan alur penagihan di UI menggunakan berbagai metode pembelian.
Setelah selesai, arsitektur aplikasi Anda akan terlihat seperti gambar di bawah ini:
Yang akan Anda pelajari
- Cara mengintegrasikan library Layanan Penagihan Play
- Cara membuat produk langganan, paket dasar, penawaran, dan tag melalui Konsol Play
- Cara mengambil penawaran dan paket dasar yang tersedia dari aplikasi
- Cara meluncurkan alur penagihan dengan parameter yang sesuai
- Cara menawarkan produk langganan prabayar
Codelab ini berfokus pada Layanan Penagihan Google Play. Konsep dan blok kode yang tidak relevan akan dibahas sekilas dan disediakan, jadi Anda cukup menyalin dan menempelkannya.
Yang Anda butuhkan
- Versi terbaru Android Studio (>= Arctic Fox | 2020.3.1)
- Perangkat Android yang menjalankan Android 8.0 atau yang lebih baru
- Kode contoh, yang disediakan untuk Anda di GitHub (petunjuk di bagian selanjutnya)
- Pengetahuan menengah tentang pengembangan Android di Android Studio
- Pengetahuan tentang cara memublikasikan aplikasi ke Google Play Store
- Pengalaman sedang dalam menulis kode Kotlin
- Library Layanan Penagihan Google Play versi 5.0.0
2. Mempersiapkan
Mendapatkan kode dari GitHub
Kami telah memasukkan semua yang Anda perlukan untuk project ini ke dalam repo Git. Untuk memulai, Anda harus mengambil kode dan membukanya di lingkungan pengembangan favorit Anda. Untuk codelab ini, sebaiknya gunakan Android Studio.
Kode untuk memulai disimpan di repositori GitHub. Anda dapat membuat clone repositori melalui perintah berikut:
git clone https://github.com/android/play-billing-samples.git cd PlayBillingCodelab
3. Pekerjaan Dasar
Apa yang menjadi titik awal?
Titik awal kita adalah aplikasi profil pengguna dasar yang dirancang untuk codelab ini. Kode telah disederhanakan untuk menunjukkan konsep yang ingin kita ilustrasikan dan belum dirancang untuk penggunaan produksi. Jika Anda memilih untuk menggunakan kembali bagian mana pun dari kode ini di aplikasi produksi, pastikan untuk mengikuti praktik terbaik dan menguji semua kode Anda sepenuhnya.
Mengimpor project ke Android Studio
PlayBillingCodelab adalah aplikasi dasar yang tidak berisi implementasi Layanan Penagihan Google Play. Mulai Android Studio dan impor codelab penagihan dengan memilih Open > billing/PlayBillingCodelab
Project ini memiliki dua modul:
- start memiliki aplikasi kerangka tetapi tidak memiliki dependensi yang diperlukan dan semua metode yang perlu Anda implementasikan.
- finished telah menyelesaikan project dan dapat digunakan sebagai panduan saat Anda mengalami kebuntuan.
Aplikasi ini terdiri dari delapan file class: BillingClientWrapper
, SubscriptionDataRepository
, Composables
, MainState
, MainViewModel
, MainViewModelFactory
, dan MainActivity
.
- BillingClientWrapper adalah wrapper yang mengisolasi metode [BillingClient] Layanan Penagihan Google Play yang diperlukan untuk memiliki penerapan yang sederhana dan memberikan respons ke repositori data untuk diproses.
- SubscriptionDataRepository digunakan untuk memisahkan sumber data Layanan Penagihan Google Play (yaitu Library Klien Penagihan) dan mengonversi data StateFlow yang dimunculkan di BillingClientWrapper menjadi Alur.
- ButtonModel adalah class data yang digunakan untuk membuat tombol di UI.
- Composables mengekstrak semua metode composable UI ke dalam satu class.
- MainState adalah class data untuk pengelolaan status.
- MainViewModel digunakan untuk menyimpan data dan status terkait penagihan yang digunakan dalam UI. Ini menggabungkan semua alur di SubscriptionDataRepository menjadi satu objek status.
- MainActivity adalah class aktivitas utama yang memuat Composable untuk antarmuka pengguna.
- Constants adalah objek yang memiliki konstanta yang digunakan oleh beberapa class.
Gradle
Anda perlu menambahkan dependensi Gradle untuk menambahkan Layanan Penagihan Google Play ke aplikasi Anda. Buka file build.gradle modul aplikasi, dan tambahkan berikut ini:
dependencies { val billing_version = "5.0.0" implementation("com.android.billingclient:billing:$billing_version") }
Konsol Google Play
Untuk tujuan codelab ini, Anda harus membuat dua penawaran produk langganan berikut di bagian langganan Konsol Google Play:
- 1 langganan dasar dengan ID produk
up_basic_sub
Produk harus memiliki 3 paket dasar (2 perpanjangan otomatis dan 1 prabayar) dengan tag terkait : 1 langganan dasar bulanan dengan tag monthlybasic
, 1 langganan dasar tahunan dengan tag yearlybasic
, dan 1 langganan prabayar dengan tag prepaidbasic
Anda dapat menambahkan penawaran ke paket dasar. Penawaran akan mewarisi tag dari paket dasar yang terkait.
- 1 langganan premium dengan ID produk
up_premium_sub
Produk harus memiliki 3 paket dasar(2 perpanjangan otomatis dan 1 prabayar) dengan tag terkait: 1 langganan dasar bulanan dengan tag monthlypremium
, 1 langganan dasar tahunan dengan tag yearlypremium
, dan 1 langganan prabayar dengan tag prepaidpremium
Anda dapat menambahkan penawaran ke paket dasar. Penawaran akan mewarisi tag dari paket dasar yang terkait.
Untuk mengetahui informasi selengkapnya tentang cara membuat produk langganan, paket dasar, penawaran, dan tag, lihat Pusat Bantuan Google Play.
4. Penyiapan Klien Penagihan
Untuk bagian ini, Anda akan bekerja di class BillingClientWrapper
.
Pada akhirnya, Anda akan memiliki semua yang diperlukan agar Klien Penagihan dapat dibuat instance-nya, dan semua metode terkait.
- Melakukan inisialisasi pada BillingClient
Setelah menambahkan dependensi pada Google Play Billing Library, kita perlu melakukan inisialisasi instance BillingClient
.
BillingClientWrapper.kt
private val billingClient = BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
- Membuat koneksi ke Google Play
Setelah Anda membuat BillingClient, kami perlu membuat koneksi ke Google Play.
Agar terhubung ke Google Play, kita memanggil startConnection()
. Proses koneksi bersifat asinkron, dan kita perlu menerapkan BillingClientStateListener
untuk menerima callback setelah penyiapan klien selesai dan siap untuk membuat permintaan lebih lanjut.
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)
}
})
}
- Mengkueri Layanan Penagihan Google Play untuk pembelian yang sudah ada
Setelah terhubung ke Google Play, kita siap membuat kueri untuk pembelian yang sebelumnya dilakukan pengguna dengan memanggil 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)
}
}
}
- Menampilkan produk yang tersedia untuk dibeli
Sekarang kita dapat membuat kueri untuk produk yang tersedia dan menampilkannya kepada pengguna. Untuk mengkueri Google Play terkait detail produk langganan, kita akan memanggil queryProductDetailsAsync()
. Kueri untuk Detail produk adalah langkah penting sebelum menampilkan produk kepada pengguna, karena informasi produk yang dilokalkan akan ditampilkan.
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)
}
}
}
- Menetapkan Pemroses untuk kueri ProductDetails
Catatan: Metode ini menampilkan hasil kueri dalam Peta ke _productWithProductDetails
.
Perhatikan juga bahwa kueri diperkirakan akan menampilkan ProductDetails
. Jika hal ini tidak terjadi, kemungkinan besar masalah yang disiapkan di konsol Play belum diaktifkan atau Anda belum memublikasikan build dengan dependensi klien penagihan di jalur rilis mana pun.
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")
}
}
}
- Meluncurkan alur pembelian
launchBillingFlow
adalah metode yang dipanggil saat pengguna mengklik untuk membeli item. Pendekatan ini meminta Google Play untuk memulai alur pembelian dengan ProductDetails
produk.
BillingClientWrapper.kt
fun launchBillingFlow(activity: Activity, params: BillingFlowParams) {
if (!billingClient.isReady) {
Log.e(TAG, "launchBillingFlow: BillingClient is not ready")
}
billingClient.launchBillingFlow(activity, params)
}
- Menetapkan pemroses untuk hasil operasi pembelian
Saat pengguna keluar dari layar pembelian Google Play (baik dengan mengetuk tombol 'Beli' untuk menyelesaikan pembelian, atau dengan mengetuk tombol Kembali untuk membatalkan pembelian), callback onPurchaseUpdated()
akan mengirimkan hasil alur pembelian kembali ke aplikasi Anda. Berdasarkan BillingResult.responseCode
, Anda kemudian dapat menentukan apakah pengguna berhasil membeli produk. Jika responseCode == OK
, berarti pembelian berhasil diselesaikan.
onPurchaseUpdated()
meneruskan kembali daftar objek Purchase
yang menyertakan semua pembelian yang dilakukan pengguna melalui aplikasi. Di antara banyak kolom lainnya, setiap objek Purchase berisi atribut ID produk, purchaseToken, dan isAcknowledged
. Dengan menggunakan kolom ini, untuk setiap objek Purchase
, Anda dapat menentukan apakah ini adalah pembelian baru yang perlu diproses atau pembelian yang sudah ada yang tidak perlu diproses lebih lanjut.
Untuk pembelian langganan, pemrosesan mirip dengan mengonfirmasi pembelian baru.
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.
}
}
- Memproses pembelian (Memverifikasi dan Mengonfirmasi pembelian)
Setelah pengguna menyelesaikan pembelian, aplikasi kemudian perlu memproses pembelian tersebut dengan mengonfirmasinya.
Selain itu, nilai _isNewPurchaseAcknowledged
ditetapkan ke true saat konfirmasi berhasil diproses.
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
}
}
}
}
}
- Menghentikan koneksi penagihan
Terakhir, saat aktivitas dihancurkan, Anda ingin menghentikan koneksi ke Google Play, sehingga endConnection()
dipanggil untuk melakukannya.
BillingClientWrapper.kt
fun terminateBillingConnection() {
Log.i(TAG, "Terminating connection")
billingClient.endConnection()
}
5. SubscriptionDataRepository
Di BillingClientWrapper
, respons dari QueryPurchasesAsync
dan QueryProductDetails
masing-masing diposting ke MutableStateFlow
_purchases
dan _productWithProductDetails
yang diekspos di luar class dengan pembelian dan productWithProductDetails
.
Di SubscriptionDataRepository
, pembelian diproses menjadi tiga Alur berdasarkan produk pembelian yang ditampilkan: hasRenewableBasic
, hasPrepaidBasic
, hasRenewablePremium
, dan hasPremiumPrepaid
.
Selain itu, productWithProductDetails
diproses ke dalam Flow basicProductDetails
dan premiumProductDetails
masing-masing.
6. MainViewModel
Bagian yang sulit sudah selesai. Sekarang Anda akan menentukan MainViewModel
, yang hanya merupakan antarmuka publik untuk klien Anda sehingga mereka tidak perlu mengetahui bagian internal BillingClientWrapper
dan SubscriptionDataRepository
.
Pertama di MainViewModel
, kita memulai koneksi penagihan saat viewModel diinisialisasi.
MainViewModel.kt
init {
billingClient.startBillingConnection(billingConnectionState = _billingConnectionState)
}
Kemudian, Flow dari repositori masing-masing digabungkan menjadi productsForSaleFlows
(untuk produk yang tersedia) dan userCurrentSubscriptionFlow
(untuk langganan pengguna saat ini dan aktif) seperti yang diproses di class repo.
Daftar pembelian saat ini juga tersedia untuk UI dengan 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
gabungan dikumpulkan dalam blok init dan nilainya diposting ke objek MutableLiveData yang disebut _destinationScreen
.
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
juga menambahkan beberapa metode yang sangat membantu:
- Pengambilan token Paket Dasar dan Penawaran
Mulai Library Layanan Penagihan Play versi 5.0.0, semua produk langganan dapat memiliki beberapa penawaran dan paket dasar, kecuali paket dasar prabayar yang tidak dapat memiliki penawaran.
Metode ini membantu mengambil semua penawaran dan paket dasar yang dapat digunakan oleh pengguna dengan menggunakan konsep tag yang baru diperkenalkan, yang digunakan untuk mengelompokkan penawaran terkait.
Misalnya, saat pengguna mencoba membeli langganan dasar bulanan, semua penawaran dan paket dasar yang terkait dengan produk langganan dasar bulanan akan diberi tag dengan string 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
}
- Penghitungan penawaran harga terendah
Jika pengguna memenuhi syarat untuk beberapa penawaran, metode leastPricedOfferToken()
digunakan untuk menghitung penawaran terendah di antara penawaran yang ditampilkan oleh retrieveEligibleOffers()
.
Metode ini menampilkan token ID penawaran dari penawaran yang dipilih.
Penerapan ini hanya menampilkan penawaran dengan harga terendah dalam hal kumpulan pricingPhases
dan tidak memperhitungkan rata-rata.
Implementasi lainnya adalah melihat penawaran dengan harga rata-rata terendah.
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
}
- Builder BillingFlowParams
Untuk meluncurkan alur pembelian pada produk tertentu, ProductDetails
dan token penawaran yang dipilih harus ditetapkan dan digunakan untuk membangun BilingFlowParams.
Ada dua metode untuk membantu menanganinya:
upDowngradeBillingFlowParamsBuilder()
membuat parameter untuk upgrade dan downgrade.
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()
membuat parameter untuk pembelian normal.
MainViewModel.kt
private fun billingFlowParamsBuilder(
productDetails: ProductDetails,
offerToken: String
): BillingFlowParams.Builder {
return BillingFlowParams.newBuilder().setProductDetailsParamsList(
listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(offerToken)
.build()
)
)
}
- Metode pembelian
Metode pembelian menggunakan launchBillingFlow()
BillingClientWrapper
dan BillingFlowParams
untuk meluncurkan pembelian.
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.")
}
}
- Menghentikan koneksi penagihan
Terakhir, metode terminateBillingConnection
BillingClientWrapper
dipanggil pada onCleared()
ViewModel.
Fungsi ini untuk menghentikan koneksi penagihan saat ini ketika aktivitas terkait dihancurkan.
7. UI
Sekarang, saatnya menggunakan semua yang telah Anda bangun di UI. Untuk membantu melakukannya, Anda akan menggunakan class Composable dan MainActivity.
Composables.kt
Class Composable disediakan sepenuhnya dan menentukan semua fungsi Compose yang digunakan untuk merender UI dan mekanisme navigasi di antara keduanya.
Fungsi Subscriptions
menampilkan dua tombol: Basic Subscription
, dan Premium Subscription
.
Basic Subscription
dan Premium Subscription
masing-masing memuat metode Compose baru yang menampilkan tiga paket dasar masing-masing: bulanan, tahunan, dan prabayar.
Kemudian, ada tiga kemungkinan fungsi pembuatan profil, masing-masing untuk langganan tertentu yang mungkin dimiliki pengguna: langganan Dasar yang dapat diperbarui, Premium yang dapat diperbarui, dan profil Prabayar Dasar atau Premium Prabayar.
- Jika pengguna memiliki langganan Dasar, profil dasar memungkinkan mereka mengupgrade ke langganan premium bulanan, tahunan, atau prabayar.
- Sebaliknya, saat pengguna memiliki langganan premium, mereka dapat melakukan downgrade ke langganan dasar bulanan, tahunan, atau prabayar.
- Jika memiliki langganan prabayar, pengguna dapat menambah saldo langganannya dengan tombol tambah saldo atau mengonversi langganan prabayar menjadi paket dasar dengan perpanjangan otomatis yang sesuai.
Terakhir, ada fungsi Layar pemuatan yang digunakan saat koneksi dilakukan ke Google Play dan saat profil pengguna dirender.
MainActivity.kt
- Saat
MainActivity
dibuat, viewModel akan dibuat instance-nya dan fungsi Compose yang disebutMainNavHost
akan dimuat. MainNavHost
dimulai dengan variabelisBillingConnected
yang dibuat dari LivedatabillingConnectionSate
viewModel dan diamati adanya perubahan karena saat vieModel dibuat instance-nya, model ini akan meneruskanbillingConnectionSate
ke metode startBillingConnectionBillingClientWrapper
.
isBillingConnected
disetel ke benar (true) saat koneksi dibuat dan salah (false) jika tidak.
Jika salah (false), fungsi penulisan LoadingScreen()
akan dimuat, dan jika benar, fungsi Subscription
atau profil akan dimuat.
val isBillingConnected by viewModel.billingConnectionState.observeAsState()
- Saat koneksi penagihan dibuat:
navController
Compose dibuat instance
val navController = rememberNavController()
Kemudian, flow di MainViewModel
dikumpulkan.
val productsForSale by viewModel.productsForSaleFlows.collectAsState(
initial = MainState()
)
val currentPurchases by viewModel.currentPurchasesFlow.collectAsState(
initial = listOf()
)
Terakhir, variabel LiveData destinationScreen
viewModel diamati.
Berdasarkan status langganan pengguna saat ini, fungsi penulisan yang sesuai akan dirender.
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. Kode Solusi
Kode solusi lengkap dapat ditemukan di modul solusi.
9. Selamat
Selamat, Anda telah berhasil mengintegrasikan produk langganan Google Play Billing Library 5.0.0 ke dalam aplikasi yang sangat sederhana.
Untuk versi terdokumentasi dari aplikasi yang lebih canggih dengan penyiapan server yang aman, lihat contoh resmi.