1. Giriş
Google Play'in faturalandırma sistemi, Android uygulamanızda dijital ürün ve içerik satmanıza olanak tanıyan bir hizmettir. Bu, uygulamanızdan para kazanmak için uygulama içi ürün satmanın en doğrudan yoludur. Bu codelab'de, satın alma işlemlerini uygulamanızın geri kalanıyla entegre ederken temel ayrıntıları içerecek şekilde projenizde abonelik satmak için Google Play Faturalandırma Kitaplığı'nı nasıl kullanacağınız gösterilmektedir.
Ayrıca temel planlar, fırsatlar, etiketler ve ön ödemeli planlar gibi abonelikle ilgili kavramları da tanıtır. Google Play Faturalandırma'daki abonelikler hakkında daha fazla bilgi edinmek için Yardım Merkezimize göz atabilirsiniz.
Oluşturacaklarınız
Bu codelab'de, en yeni faturalandırma kitaplığını (sürüm 5.0.0) abonelik tabanlı basit bir profil uygulamasına ekleyeceksiniz. Uygulama sizin için zaten oluşturulduğundan yalnızca faturalandırma kısmını eklersiniz. Şekil 1'de gösterildiği gibi bu uygulamada kullanıcı, iki yenilenebilir abonelik ürünü (temel ve premium) üzerinden sunulan temel planlar ve/veya fırsatlardan herhangi birine ya da yenilenebilir olmayan ön ödemeli bir abonelik için kaydolur. Hepsi bu kadar. Temel planlar sırasıyla aylık ve yıllık aboneliklerdir. Kullanıcı, ön ödemeli aboneliği yenilenebilir bir aboneliğe yükseltebilir, düşürebilir veya yenilenebilir bir aboneliğe dönüştürebilir.
Google Play Faturalandırma Kitaplığı'nı uygulamanıza dahil etmek için aşağıdakileri oluşturursunuz:
BillingClientWrapper
- BillingClient kitaplığı için bir sarmalayıcı. Play Faturalandırma Kitaplığı'nın BillingClient etkileşimiyle olan etkileşimleri kapsüllemeyi amaçlar ancak kendi entegrasyonunuzda gerekli değildir.SubscriptionDataRepository
: Uygulamanızın abonelik ürünleri envanterinin (ör.satılık olan) listesini ve satın alma işlemleri ile ürün ayrıntılarını toplamaya yardımcı olan ShareFlow değişkenlerinin listesini içeren faturalandırma deposu.MainViewModel
- Uygulamanızın geri kalanının faturalandırma deposuyla iletişim kurmak için kullandığı bir ViewModel. Çeşitli satın alma yöntemlerini kullanarak kullanıcı arayüzünde faturalandırma akışını başlatmaya yardımcı olur.
İşlem tamamlandığında uygulamanızın mimarisi aşağıdaki şekilde görünmelidir:
Neler öğreneceksiniz?
- Play faturalandırma kitaplığını entegre etme
- Play Console üzerinden abonelik ürünleri, temel planlar, fırsatlar ve etiketler oluşturma
- Uygulamadan kullanılabilir temel planları ve fırsatları alma
- Faturalandırma akışını uygun parametrelerle başlatma
- Ön ödemeli abonelik ürünleri nasıl sunulur?
Bu codelab, Google Play Faturalandırma'ya odaklanmaktadır. Alakalı olmayan kavramlar ve kod blokları işaretlenmiştir ve yalnızca kopyalayıp yapıştırmanız için kullanımınıza sunulmuştur.
Gerekenler
- Android Studio'nun yeni sürümü (>= Arctic Fox | 2020.3.1)
- Android 8.0 veya sonraki sürümlere sahip bir Android cihaz
- GitHub'da sizin için sağlanan örnek kod (talimatlar sonraki bölümlerde verilmiştir)
- Android Studio'da Android geliştirme konusunda orta düzeyde bilgi
- Google Play Store'da uygulama yayınlama hakkında bilgi
- Kotlin kodu yazma konusunda orta düzeyde deneyim
- Google Play Faturalandırma kitaplığı 5.0.0 sürümü
2. Kurulum
GitHub'dan kodu alın
Bu proje için ihtiyacınız olan her şeyi bir Git deposuna yerleştirdik. Başlamak için kodu alıp favori geliştirme ortamınızda açmanız gerekecek. Bu codelab için Android Studio'yu kullanmanızı öneririz.
Başlamak için gereken kod, GitHub deposunda saklanır. Depoyu aşağıdaki komutu kullanarak klonlayabilirsiniz:
git clone https://github.com/android/play-billing-samples.git cd PlayBillingCodelab
3. Temel
Başlangıç noktamız nedir?
Başlangıç noktamız, bu codelab için tasarlanmış temel bir kullanıcı profili uygulamasıdır. Örneklendirmek istediğimiz kavramları gösterecek şekilde basitleştirilmiş olan bu kod, üretimde kullanılmak üzere tasarlanmamıştır. Bu kodun herhangi bir bölümünü bir üretim uygulamasında yeniden kullanmayı seçerseniz en iyi uygulamaları izlediğinizden emin olun ve tüm kodunuzu tam olarak test edin.
Projeyi Android Studio'ya aktarma
PlayBillingCodelab, Google Play Faturalandırma uygulaması içermeyen temel uygulamadır. Android Studio'yu başlatın ve Open > billing/PlayBillingCodelab
'i seçerek faturalandırma codelab'ini içe aktarın.
Projenin iki modülü var:
- start'ta iskelet uygulaması mevcut ancak gereken bağımlılıklar ve uygulamanız gereken tüm yöntemler bulunmuyor.
- finished, projeyi tamamlamıştır ve takıldığınızda size yol gösterebilir.
Uygulama sekiz sınıf dosyasından oluşur: BillingClientWrapper
, SubscriptionDataRepository
, Composables
, MainState
, MainViewModel
, MainViewModelFactory
ve MainActivity
.
- BillingClientWrapper, basit bir uygulama için gerekli olan Google Play Faturalandırma [BillingClient] yöntemlerini izole eden ve işlenmek üzere veri deposuna yanıtlar yayan bir sarmalayıcıdır.
- SubscriptionDataRepository, Google Play Faturalandırma veri kaynağını (ör. Faturalandırma İstemci kitaplığı) soyutlamak için kullanılır ve BillingClientWrapper içinde yayınlanan StateFlow verilerini Akışlara dönüştürür.
- ButtonModel, kullanıcı arayüzünde düğme oluşturmak için kullanılan bir veri sınıfıdır.
- Composables, tüm kullanıcı arayüzünün composable yöntemlerini tek bir sınıfa çıkarır.
- MainState, eyalet yönetimi için bir veri sınıfıdır.
- MainViewModel, faturalandırmayla ilgili verileri ve kullanıcı arayüzünde kullanılan durumları tutmak için kullanılır. SubscriptionDataRepository'deki tüm akışları tek bir durum nesnesinde birleştirir.
- MainActivity, kullanıcı arayüzü için Oluşturulabilirleri yükleyen ana etkinlik sınıfıdır.
- Sabit değerler, birden çok sınıf tarafından kullanılan sabit değerlere sahip nesnedir.
Gradle
Google Play Faturalandırma'yı uygulamanıza eklemek için bir Gradle bağımlılığı eklemeniz gerekir. Uygulama modülünün build.gradle dosyasını açın ve şunu ekleyin:
dependencies { val billing_version = "5.0.0" implementation("com.android.billingclient:billing:$billing_version") }
Google Play Konsolu
Bu codelab'in amaçları doğrultusunda, Google Play Console'un abonelikler bölümünde aşağıdaki iki abonelik ürünü teklifini oluşturmanız gerekir:
up_basic_sub
ürün kimliğine sahip 1 temel abonelik
Ürünün, ilişkili etiketleri olan 3 temel planı (2 otomatik yenilenen, 1 ön ödemeli) olmalıdır : monthlybasic
etiketli 1 aylık temel abonelik, yearlybasic
etiketiyle 1 yıllık temel abonelik ve prepaidbasic
etiketli 1 ön ödemeli abonelik
Temel planlara fırsatlar ekleyebilirsiniz. Teklifler, etiketleri ilişkili temel planlarından devralır.
- Ürün kimliği
up_premium_sub
olan 1 premium abonelik
Ürünün, ilişkili etiketleri olan 3 temel planı(2 otomatik yenilenen, 1 ön ödemeli) olmalıdır: monthlypremium
etiketli 1 aylık temel abonelik, yearlypremium
etiketiyle 1 yıllık temel abonelik ve prepaidpremium
etiketli 1 ön ödemeli abonelik
Temel planlara fırsatlar ekleyebilirsiniz. Teklifler, etiketleri ilişkili temel planlarından devralır.
Abonelik ürünlerinin, temel planların, fırsatların ve etiketlerin nasıl oluşturulacağı hakkında daha ayrıntılı bilgi için lütfen Google Play Yardım Merkezi'ne bakın.
4. Faturalandırma Müşterisi kurulumu
Bu bölüm için BillingClientWrapper
sınıfında çalışacaksınız.
Kılavuzun sonunda, Faturalandırma Müşterisi örneklendirmesi için gereken her şeye ve ilgili tüm yöntemlere sahip olacaksınız.
- BillingClient'ı başlatma
Google Play Faturalandırma Kitaplığı'na bir bağımlılık ekledikten sonra BillingClient
örneğini başlatmamız gerekir.
BillingClientWrapper.kt
private val billingClient = BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
- Google Play ile bağlantı oluşturun
BillingClient öğesini oluşturduktan sonra Google Play ile bağlantı kurmamız gerekir.
Google Play'e bağlanmak için startConnection()
adını veriyoruz. Bağlantı süreci eşzamansızdır. İstemci kurulumu tamamlanıp başka istekler göndermeye hazır olduğunda geri çağırma almak için bir BillingClientStateListener
uygulamamız gerekir.
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)
}
})
}
- Mevcut satın alma işlemleri için Google Play Faturalandırma'yı sorgulama
Google Play ile bağlantı kurulduktan sonra, kullanıcının daha önce queryPurchasesAsync()
numaralı telefonu arayarak yaptığı satın alma işlemlerini sorgulamaya hazırız.
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)
}
}
}
- Satın alınabilecek ürünleri gösterme
Artık mevcut ürünleri sorgulayıp kullanıcılara gösterebiliriz. Google Play'de abonelik ürünü ayrıntılarını sorgulamak için queryProductDetailsAsync()
numaralı telefonu arayacağız. Ürün ayrıntılarını sorgulamak, yerelleştirilmiş ürün bilgileri döndürdüğünden ürünleri kullanıcılara göstermeden önce önemli bir adımdır.
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 sorgusu için İşleyici'yi ayarlama
Not: Bu yöntem, sorgunun sonucunu _productWithProductDetails
haritasına yayar.
Ayrıca sorgunun ProductDetails
döndürmesi beklenmektedir. Böyle bir durum yoksa sorunun nedeni büyük olasılıkla Play Console'da ayarlanan ürünlerin etkinleştirilmemesidir veya hiçbir sürüm kanalında, faturalandırma istemcisi bağımlılığı olan bir derleme yayınlamamış olmanızdır.
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")
}
}
}
- Satın alma akışını başlatma
launchBillingFlow
, kullanıcı bir öğeyi satın almak için tıkladığında çağrılan yöntemdir. Google Play'den ürünün ProductDetails
ile satın alma akışını başlatması istenir.
BillingClientWrapper.kt
fun launchBillingFlow(activity: Activity, params: BillingFlowParams) {
if (!billingClient.isReady) {
Log.e(TAG, "launchBillingFlow: BillingClient is not ready")
}
billingClient.launchBillingFlow(activity, params)
}
- Satın alma işleminin sonucu için işleyiciyi ayarlama
Kullanıcı Google Play satın alma ekranından çıktığında (satın alma işlemini tamamlamak için "Satın al" düğmesine veya Geri düğmesine dokunarak satın alma işlemini iptal ederek), onPurchaseUpdated()
geri çağırması satın alma akışının sonucunu uygulamanıza geri gönderir. BillingResult.responseCode
verilerine dayanarak, kullanıcının ürünü başarılı bir şekilde satın alıp almadığını belirleyebilirsiniz. Bu değer responseCode == OK
ise satın alma işleminin başarıyla tamamlandığı anlamına gelir.
onPurchaseUpdated()
, kullanıcının uygulama üzerinden yaptığı tüm satın alma işlemlerini içeren Purchase
nesne listesini geri verir. Diğer birçok alanın yanı sıra her Purchase nesnesi ürün kimliği, purchaseToken ve isAcknowledged
özelliklerini içerir. Ardından bu alanları kullanarak her Purchase
nesnesi için bunun işlenmesi gereken yeni bir satın alma işlemi mi yoksa başka işlem yapılmasına gerek olmayan mevcut bir satın alma işlemi mi olduğunu belirleyebilirsiniz.
Abonelik satın alma işlemlerinde işlem, yeni satın alma işleminin onaylanmasına benzer.
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.
}
}
- Satın alma işlemlerini işleme (satın alma işlemlerini doğrulama ve onaylama)
Kullanıcı bir satın alma işlemini tamamladıktan sonra, uygulamanın bunu onaylayarak satın alması gerekir.
Ayrıca, onay başarıyla işlendiğinde _isNewPurchaseAcknowledged
değeri doğru olarak ayarlanır.
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
}
}
}
}
}
- Faturalandırma bağlantısını sonlandırma
Son olarak, bir etkinlik imha edildiğinde Google Play bağlantısını sonlandırmak istersiniz. Bu nedenle endConnection()
bunu yapması için çağrılır.
BillingClientWrapper.kt
fun terminateBillingConnection() {
Log.i(TAG, "Terminating connection")
billingClient.endConnection()
}
5. SubscriptionDataRepository
BillingClientWrapper
içinde, QueryPurchasesAsync
ve QueryProductDetails
adlı kullanıcıların yanıtları sırasıyla MutableStateFlow
_purchases
ve _productWithProductDetails
için, satın alma işlemiyle ve productWithProductDetails
ile sınıf dışında gösterilen yanıtlara gönderilir.
SubscriptionDataRepository
ayında, satın alma işlemleri, iade edilen satın alınan ürünün ürününe göre üç Akışa dönüştürülür: hasRenewableBasic
, hasPrepaidBasic
, hasRenewablePremium
ve hasPremiumPrepaid
.
Ayrıca productWithProductDetails
, ilgili basicProductDetails
ve premiumProductDetails
Akışlarına işlenir.
6. MainViewModel
Zor kısmı tamamladınız. Şimdi, müşterileriniz için herkese açık bir arayüz olan MainViewModel
'ı tanımlayacaksınız. Böylece, BillingClientWrapper
ve SubscriptionDataRepository
'nin dahili özelliklerini bilmek zorunda kalmazlar.
İlk olarak MainViewModel
içinde, viewModel başlatıldığında faturalandırma bağlantısını başlatırız.
MainViewModel.kt
init {
billingClient.startBillingConnection(billingConnectionState = _billingConnectionState)
}
Ardından kod deposundaki Akışlar, depo sınıfında işlendiği şekilde productsForSaleFlows
(kullanılabilir ürünler için) ve userCurrentSubscriptionFlow
(kullanıcının mevcut ve etkin aboneliği için) altında birleştirilir.
Mevcut satın alma işlemlerinin listesi de currentPurchasesFlow
ile kullanıcı arayüzüne sunulur.
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
Birleştirilmiş userCurrentSubscriptionFlow
, bir başlangıç bloğunda toplanır ve değer, _destinationScreen
adlı bir MutableLiveData nesnesine yayınlanır.
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
, oldukça faydalı bazı yöntemler de ekler:
- Temel Plan ve Teklif jetonları alma
Play Faturalandırma Kitaplığı'nın 5.0.0 sürümünden itibaren tüm abonelik ürünlerinde, fırsatları olmayan ön ödemeli temel planlar hariç birden fazla temel plan ve fırsat bulunabilir.
Bu yöntem, ilgili teklifleri gruplandırmak için kullanılan yeni kullanıma sunulan etiket kavramını kullanarak kullanıcının uygun olduğu tüm fırsatların ve temel planları almaya yardımcı olur.
Örneğin, bir kullanıcı aylık temel abonelik satın almaya çalıştığında aylık temel abonelik ürünüyle ilişkilendirilen tüm temel planlar ve teklifler monthlyBasic
dizesiyle etiketlenir.
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
}
- En düşük fiyatlı teklif hesaplaması
Bir kullanıcı birden fazla teklif için uygun olduğunda retrieveEligibleOffers()
tarafından döndürülen teklifler arasından en düşük teklifi hesaplamak için leastPricedOfferToken()
yöntemi kullanılır.
Yöntem, seçilen teklifin teklif kimliği jetonunu döndürür.
Bu uygulama, pricingPhases
grubu açısından en düşük fiyatlı teklifleri döndürür ve ortalamaları hesaba katmaz.
Başka bir uygulama, bunun yerine en düşük ortalama fiyatlı teklifi değerlendirebilir.
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 oluşturucular
Belirli bir ürün için satın alma akışını başlatmak s ProductDetails
ve seçilen teklifin jetonunun ayarlanıp BilingFlowParams oluşturmak için kullanılması gerekir.
Bu konuda yardımcı olacak iki yöntem vardır:
upDowngradeBillingFlowParamsBuilder()
, yükseltme ve düşürme için parametreleri oluşturur.
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()
, normal satın alma işlemleri için parametreleri oluşturur.
MainViewModel.kt
private fun billingFlowParamsBuilder(
productDetails: ProductDetails,
offerToken: String
): BillingFlowParams.Builder {
return BillingFlowParams.newBuilder().setProductDetailsParamsList(
listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(offerToken)
.build()
)
)
}
- Satın alma yöntemi
Satın alma yöntemi, satın alma işlemlerini başlatmak için BillingClientWrapper
adlı kullanıcının launchBillingFlow()
ve BillingFlowParams
yöntemini kullanır.
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.")
}
}
- Faturalandırma bağlantısını sonlandırma
Son olarak, BillingClientWrapper
öğesinin terminateBillingConnection
yöntemi, ViewModel'in onCleared()
üzerinde çağrılır.
Bunun amacı, ilişkili etkinlik kaldırıldığında mevcut faturalandırma bağlantısını sonlandırmaktır.
7. Kullanıcı arayüzü
Şimdi sıra, kullanıcı arayüzünde oluşturduğunuz her şeyi kullanmaya geldi. Bu konuda yardımcı olması için Composables ve MainActivity sınıflarıyla çalışacaksınız.
Composables.kt
Oluşturulabilirler sınıfı tam olarak sağlanır ve kullanıcı arayüzünü ve bunlar arasındaki gezinme mekanizmasını oluşturmak için kullanılan tüm Compose işlevlerini tanımlar.
Subscriptions
işlevi iki düğme gösterir: Basic Subscription
ve Premium Subscription
.
Basic Subscription
ve Premium Subscription
öğelerinin her biri, ilgili üç temel planı gösteren yeni Oluşturma yöntemleri yükler: aylık, yıllık ve ön ödemeli.
Ardından, kullanıcının belirli bir aboneliğe sahip olabileceği üç olası profil oluşturma işlevi vardır: yenilenebilir Basic, yenilenebilir Premium ve Ön Ödemeli Temel veya Ön Ödemeli Premium profili.
- Kullanıcı, Basic aboneliğine sahip olduğunda temel profil, kullanıcının aylık, yıllık veya ön ödemeli premium aboneliğe geçmesine olanak tanır.
- Buna karşılık, premium aboneliği olan kullanıcılar aylık, yıllık veya ön ödemeli temel aboneliğe geçebilirler.
- Ön ödemeli aboneliği olan kullanıcılar, para ekleme düğmesini kullanarak aboneliğine para ekleyebilir veya ön ödemeli aboneliğini uygun bir otomatik yenilenen temel plana dönüştürebilir.
Son olarak, Google Play ile bağlantı kurulurken ve bir kullanıcı profili oluşturulurken kullanılan Yükleme ekranı işlevi vardır.
MainActivity.kt
MainActivity
oluşturulduğunda, viewModel örneklenir veMainNavHost
adlı bir oluşturma işlevi yüklenir.MainNavHost
, viewModel'inbillingConnectionSate
Livedata parametresinden oluşturulan birisBillingConnected
değişkeniyle başlar ve vieModel örneklendirildiğindeBillingClientWrapper
öğesinin startBillingConnection yönteminebillingConnectionSate
iletildiği için değişiklikler için gözlemlenir.
Bağlantı kurulduğunda isBillingConnected
doğru, kurulmazsa yanlış olarak ayarlanır.
Yanlış değerine ayarlandığında LoadingScreen()
oluştur işlevi, doğru değerine ise Subscription
veya profil işlevleri yüklenir.
val isBillingConnected by viewModel.billingConnectionState.observeAsState()
- Faturalandırma bağlantısı kurulduğunda:
Oluştur navController
örneklendirildi
val navController = rememberNavController()
Daha sonra, MainViewModel
içindeki akışlar toplanır.
val productsForSale by viewModel.productsForSaleFlows.collectAsState(
initial = MainState()
)
val currentPurchases by viewModel.currentPurchasesFlow.collectAsState(
initial = listOf()
)
Son olarak, viewModel'in destinationScreen
LiveData değişkeni gözlemlenir.
Kullanıcının mevcut abonelik durumuna göre ilgili oluşturma işlevi oluşturulur.
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. Çözüm Kodu
Çözüm kodunun tamamı çözüm modülünde bulunabilir.
9. Tebrikler
Tebrikler, Google Play Faturalandırma Kitaplığı 5.0.0 abonelik ürünlerini çok basit bir uygulamaya başarıyla entegre ettiniz.
Daha gelişmiş bir uygulamanın güvenli sunucu kurulumuna sahip belgelenmiş bir sürümü için resmi örneği inceleyin.