Play Faturalandırma Kitaplığı ile uygulama içi abonelik satma 5

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.

d7dba51f800a6cc4.png 2220c15b849d2ead.png

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:

c83bc759f32b0a63.png

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

a9f6fd6e70e69fed.png

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.

  1. 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()
  1. 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)
       }
   })
}
  1. 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)
       }
   }
}
  1. 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)
       }
   }
}
  1. 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")
       }
   }
}
  1. 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)

}
  1. 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.
   }
}
  1. 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
               }
           }
       }
   }
}
  1. 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:

  1. 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
}
  1. 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
}
  1. 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()
       )
   )
}
  1. 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.")
   }
}

d726a27add092140.png

  1. 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

  1. MainActivity oluşturulduğunda, viewModel örneklenir ve MainNavHost adlı bir oluşturma işlevi yüklenir.
  2. MainNavHost, viewModel'in billingConnectionSate Livedata parametresinden oluşturulan bir isBillingConnected değişkeniyle başlar ve vieModel örneklendirildiğinde BillingClientWrapper öğesinin startBillingConnection yöntemine billingConnectionSate 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()
  1. 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.

Daha fazla bilgi