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 dalam project Anda dengan cara yang merangkum detail mendetail saat mengintegrasikan pembelian dengan bagian aplikasi lainnya.
Panduan ini juga memperkenalkan konsep terkait langganan seperti paket dasar, penawaran, tag, dan paket prabayar. Untuk mempelajari langganan di Layanan Penagihan Google Play lebih lanjut, Anda dapat membuka Pusat Bantuan kami.
Yang akan Anda build
Dalam codelab ini, Anda akan menambahkan library penagihan terbaru (versi 5.0.0) ke aplikasi profil berbasis langganan sederhana. Aplikasi ini sudah dibuat untuk Anda, jadi Anda hanya akan menambahkan bagian penagihan. Seperti yang ditampilkan dalam gambar 1, dalam aplikasi ini pengguna mendaftar ke paket dasar dan/atau penawaran apa pun yang ditawarkan melalui dua produk langganan yang dapat diperpanjang (dasar dan premium) atau untuk prabayar yang tidak dapat diperpanjang. Itu saja. Paket dasar masing-masing adalah langganan bulanan dan tahunan. Pengguna dapat mengupgrade, mendowngrade, atau mengonversi langganan prabayar ke langganan yang dapat diperpanjang.
Untuk memasukkan Library Layanan Penagihan Google Play di aplikasi, Anda harus membuat hal berikut:
BillingClientWrapper
: wrapper untuk library BillingClient. Library ini bermaksud 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. Hal ini membantu meluncurkan alur penagihan di UI menggunakan berbagai metode pembelian.
Setelah selesai, arsitektur aplikasi Anda akan terlihat mirip dengan 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 dasar dan penawaran 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 dipoles dan disediakan sehingga Anda cukup salin dan tempel.
Yang Anda butuhkan
- Versi terbaru Android Studio (>= Arctic Fox | 2020.3.1)
- Perangkat Android dengan Android 8.0 atau lebih baru
- Kode contoh, yang disediakan untuk Anda di GitHub (petunjuk di bagian selanjutnya)
- Pengetahuan sedang tentang pengembangan Android di Android Studio
- Pengetahuan tentang cara memublikasikan aplikasi ke Google Play Store
- Pengalaman menengah dalam menulis kode Kotlin
2. Mempersiapkan
Dapatkan kode dari GitHub
Kami telah memasukkan semua yang Anda perlukan untuk project ini ke dalam repo Git. Untuk memulai, Anda harus mengambil kode tersebut dan membukanya di lingkungan pengembangan favorit Anda. Untuk codelab ini, sebaiknya gunakan Android Studio.
Kode untuk memulai disimpan di repositori GitHub. Anda dapat meng-clone repositori melalui perintah berikut:
git clone https://github.com/android/play-billing-codelabs
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 dalam aplikasi produksi, pastikan untuk mengikuti praktik terbaik dan sepenuhnya menguji semua kode Anda.
Mengimpor project ke Android Studio
Codelab penagihan adalah aplikasi dasar yang tidak berisi penerapan Layanan Penagihan Google Play. Mulai Android Studio dan impor codelab penagihan dengan memilih **Open > billing-codelab/build.gradle**
Project ini memiliki dua paket:
- codelab memiliki aplikasi kerangka, tetapi tidak memiliki dependensi yang diperlukan dan semua metode yang perlu Anda terapkan
- solution sudah selesai dan dapat berfungsi sebagai panduan saat Anda mengalami masalah
Aplikasi ini terdiri dari tujuh 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 implementasi 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 ditampilkan di BillingClientWrapper menjadi Alur.
- ButtonModel adalah class data yang digunakan untuk membuat tombol di UI.
- Composable mengekstrak semua metode composable UI ke dalam satu class.
- MainState adalah class data untuk pengelolaan status.
- MainViewModel digunakan untuk menyimpan status dan data terkait penagihan yang digunakan di UI. Ini menggabungkan semua alur di SubscriptionDataRepository menjadi satu objek status.
- MainViewModelFactory adalah implementasi antarmuka ViewModelProviders.Factory yang digunakan untuk membuat instance MainViewModel.
- Konstanta adalah objek yang memiliki konstanta yang digunakan oleh beberapa class.
- MainActivity adalah class aktivitas utama yang memuat Composable untuk antarmuka pengguna.
Gradle
Anda perlu menambahkan dependensi Gradle untuk menambahkan Layanan Penagihan Google Play ke aplikasi Anda. Buka file build.gradle modul aplikasi, dan tambahkan yang berikut ini:
dependencies { val billing_version = "5.0.0" implementation("com.android.billingclient:billing:$billing_version") }
Konsol
Untuk keperluan codelab ini, Anda perlu membuat dua penawaran produk langganan berikut di 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 informasi yang lebih mendetail tentang cara membuat produk langganan, paket dasar, penawaran, dan tag, lihat Pusat Bantuan Google Play di sini.
4. Penyiapan Klien Penagihan
Untuk bagian ini, Anda akan mengerjakan class BillingClientWrapper
.
Pada akhirnya, Anda akan memiliki semua yang diperlukan untuk membuat instance Klien Penagihan, dan semua metode terkait.
- Menginisialisasi BillingClient
Setelah menambahkan dependensi pada Library Layanan Penagihan Google Play, kita perlu menginisialisasi 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 menghubungkan Anda ke Google Play.
Agar terhubung ke Google Play, kami memanggil startConnection()
. Proses koneksi bersifat asinkron, dan kita perlu mengimplementasikan 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)
}
})
}
- Membuat kueri Layanan Penagihan Google Play untuk pembelian yang ada
Setelah terhubung ke Google Play, kami 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 mengkueri produk yang tersedia dan menampilkannya kepada pengguna. Untuk mengkueri detail produk langganan di Google Play, kita akan memanggil queryProductDetailsAsync()
. Kueri untuk detail Produk merupakan langkah penting sebelum menampilkan produk kepada pengguna, karena proses ini akan menampilkan informasi produk yang dilokalkan.
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 memunculkan hasil kueri dalam Peta ke _productWithProductDetails
.
Perhatikan juga bahwa kueri ini diharapkan menampilkan ProductDetails
. Jika hal ini tidak terjadi, kemungkinan besar masalahnya adalah produk 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. Hal 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 (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 atau tidak. Jika responseCode == OK
, artinya pembelian berhasil diselesaikan.
onPurchaseUpdated()
memberikan daftar objek Purchase
yang mencakup semua pembelian yang dilakukan pengguna melalui aplikasi. Di antara banyak kolom lainnya, setiap objek Pembelian berisi atribut id produk, purchaseToken, dan isAcknowledged
. Dengan kolom ini, untuk setiap objek Purchase
, Anda dapat menentukan apakah ini merupakan pembelian baru yang perlu diproses atau pembelian yang sudah ada yang tidak memerlukan pemrosesan lebih lanjut.
Untuk pembelian langganan, pemrosesannya sama 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 harus memproses pembelian tersebut dengan mengonfirmasinya.
Selain itu, nilai _isNewPurchaseAcknowledged
ditetapkan ke benar 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 ditampilkan di luar class dengan pembelian dan productWithProductDetails
.
Pada bulan SubscriptionDataRepository
, pembelian diproses menjadi tiga Alur berdasarkan produk pembelian yang dikembalikan: hasRenewableBasic
, hasPrepaidBasic
, hasRenewablePremium
, dan hasPremiumPrepaid
.
Selain itu, productWithProductDetails
diproses menjadi Flow basicProductDetails
, dan premiumProductDetails
masing-masing.
6. MainViewModel
Bagian yang sulit sudah selesai. Sekarang Anda akan menentukan MainViewModel
, yang merupakan antarmuka publik untuk klien Anda sehingga mereka tidak perlu mengetahui internal BillingClientWrapper
dan SubscriptionDataRepository
.
Pertama di MainViewModel
, kita memulai koneksi penagihan saat viewModel diinisialisasi.
MainViewModel.kt
init {
billingClient.startBillingConnection(billingConnectionState = _billingConnectionState)
}
Kemudian, Alur dari repositori masing-masing digabungkan menjadi productsForSaleFlows
(untuk produk yang tersedia) dan userCurrentSubscriptionFlow
(untuk langganan saat ini dan aktif milik pengguna) seperti yang diproses di class repo.
Daftar pembelian saat ini juga tersedia untuk UI di 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 nilai 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 berguna:
- Pengambilan token Paket Dasar dan Penawaran
Dimulai dengan Library Layanan Penagihan Play versi 5.0.0, semua produk langganan dapat memiliki beberapa paket dasar dan penawaran kecuali untuk paket dasar prabayar yang tidak dapat memiliki penawaran.
Metode ini membantu mengambil semua penawaran dan paket dasar yang valid bagi pengguna dengan menggunakan konsep tag yang baru diperkenalkan yang digunakan untuk mengelompokkan penawaran terkait.
Misalnya, saat pengguna mencoba membeli langganan dasar bulanan, semua paket dasar dan penawaran yang terkait dengan produk langganan dasar bulanan 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 dengan 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.
Penerapan lainnya dapat berupa 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 produk tertentu, ProductDetails
produk dan token penawaran yang dipilih perlu ditetapkan dan digunakan untuk membuat BilingFlowParams.
Ada dua metode untuk membantu hal ini:
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.
Hal ini untuk menghentikan koneksi penagihan saat ini ketika aktivitas terkait diakhiri.
7. UI
Sekarang, saatnya menggunakan semua yang telah Anda buat di UI. Untuk membantu hal tersebut, Anda akan menggunakan class Composable dan MainActivity.
Composable.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 tulis profil yang dapat digunakan oleh setiap pelanggan untuk langganan tertentu yang mungkin dimiliki pengguna: profil Dasar yang dapat diperpanjang, Premium yang dapat diperpanjang, dan profil Dasar Prabayar atau Premium Prabayar.
- Jika pengguna memiliki langganan Dasar, profil dasar memungkinkan mereka mengupgrade ke langganan premium bulanan, tahunan, atau prabayar.
- Sebaliknya, jika pengguna memiliki langganan premium, mereka dapat mendowngrade langganan bulanan, tahunan, atau prabayar.
- Jika pengguna memiliki langganan prabayar, mereka dapat meningkatkan langganan mereka dengan tombol tambah saldo atau mengonversi langganan prabayar mereka menjadi paket dasar dengan perpanjangan otomatis.
Terakhir, ada fungsi Pemuatan layar yang digunakan saat koneksi dilakukan ke Google Play dan saat profil pengguna dirender.
MainActivity.kt
- Saat
MainActivity
dibuat, viewModel dibuat instance-nya dan fungsi compose yang disebutMainNavHost
dimuat. MainNavHost
dimulai dengan variabelisBillingConnected
yang dibuat dari LivedatabillingConnectionSate
viewModel dan diamati perubahannya karena saat instance vieModel dibuat instance-nya, variabel tersebut meneruskanbillingConnectionSate
ke metode startBillingConnectionBillingClientWrapper
.
isBillingConnected
disetel ke benar (true) saat koneksi dibuat dan salah (false) jika tidak.
Jika salah, fungsi LoadingScreen()
compose akan dimuat, dan jika benar, fungsi Subscription
atau profil akan dimuat.
val isBillingConnected by viewModel.billingConnectionState.observeAsState()
- Saat koneksi penagihan dibuat:
Instance navController
sudah dibuat
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 tulis 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 Library Layanan Penagihan Google Play 5.0.0 ke dalam aplikasi yang sangat sederhana!
Untuk versi terdokumentasi aplikasi yang lebih canggih dengan penyiapan server yang aman, lihat contoh resmi.