Play Billing Library 5 की मदद से, ऐप्लिकेशन में सदस्यताएं बेचें

1. परिचय

Google Play का बिलिंग सिस्टम, ऐसी सेवा है जिससे अपने Android ऐप्लिकेशन में डिजिटल प्रॉडक्ट और कॉन्टेंट बेचा जा सकता है. यह अपने ऐप्लिकेशन से कमाई करने के लिए, ऐप्लिकेशन में खरीदे जा सकने वाले प्रॉडक्ट बेचने का सबसे सीधा तरीका है. इस कोडलैब से आपको यह पता चलता है कि अपने प्रोजेक्ट में सदस्यताओं की बिक्री करने के लिए, Google Play Billing Library का इस्तेमाल कैसे करें. इससे, खरीदारी को आपके ऐप्लिकेशन के अन्य ऐप्लिकेशन के साथ इंटिग्रेट करते समय ज़रूरी जानकारी इकट्ठा हो जाती है.

इसमें सदस्यता से जुड़े कॉन्सेप्ट भी शामिल हैं. जैसे, बुनियादी प्लान, ऑफ़र, टैग, और प्रीपेड प्लान. Google Play Billing की सदस्यताओं के बारे में ज़्यादा जानने के लिए, हमारे सहायता केंद्र पर जाएं.

आपको क्या बनाना होगा

इस कोडलैब में, आप सदस्यता पर आधारित आसान प्रोफ़ाइल ऐप्लिकेशन में, बिलिंग लाइब्रेरी का नया वर्शन (वर्शन 5.0.0) जोड़ने जा रहे हैं. यह ऐप्लिकेशन आपके लिए पहले से ही बना है. इसलिए, आपको बिलिंग वाला हिस्सा जोड़ना होगा. जैसा कि पहली इमेज में दिखाया गया है, इस ऐप्लिकेशन में उपयोगकर्ता किसी भी बुनियादी प्लान और/या ऑफ़र के लिए साइन अप करता है. ये उपयोगकर्ता, सदस्यता लेने वाले दो प्रॉडक्ट (बेसिक और प्रीमियम) या रिन्यू न होने वाले प्रीपेड प्लान के ज़रिए ऑफ़र किए जाते हैं. बस इतना ही। बुनियादी प्लान, महीने और साल के हिसाब से ली जाती हैं. उपयोगकर्ता अपनी प्रीपेड सदस्यता को अपग्रेड, डाउनग्रेड या रिन्यू कर सकता है.

d7dba51f800a6cc4.png 2220c15b849d2ead.png

अपने ऐप्लिकेशन में Google Play Billing Library को शामिल करने के लिए, आपको ये आइटम बनाने होंगे:

  • BillingClientWrapper- BillingClient लाइब्रेरी के लिए रैपर. इसका मकसद, Play Billing Library के BillingClient से होने वाले इंटरैक्शन को एन्क्रिप्ट (सुरक्षित) करना है. हालांकि, आपके इंटिग्रेशन में इसकी ज़रूरत नहीं है.
  • SubscriptionDataRepository - यह आपके ऐप्लिकेशन के लिए बिलिंग रिपॉज़िटरी (डेटा स्टोर करने की जगह) है.इसमें शुल्क लेकर सदस्यता देने वाले प्रॉडक्ट की इन्वेंट्री (जैसे कि कौनसे प्रॉडक्ट बिक्री के लिए हैं) और ShareFlow वैरिएबल की सूची होती है. यह वैरिएबल, खरीदारी की स्थिति और प्रॉडक्ट के बारे में जानकारी इकट्ठा करने में मदद करती है
  • MainViewModel - एक ऐसा ViewModel जिसके ज़रिए आपका बाकी ऐप्लिकेशन, बिलिंग रिपॉज़िटरी से संपर्क करता है. इससे यूज़र इंटरफ़ेस (यूआई) में खरीदारी के अलग-अलग तरीकों का इस्तेमाल करके, बिलिंग फ़्लो लॉन्च करने में मदद मिलती है.

पूरा हो जाने पर, आपके ऐप्लिकेशन का आर्किटेक्चर नीचे दिए गए डायग्राम की तरह दिखना चाहिए:

c83bc759f32b0a63.png

आपको इनके बारे में जानकारी मिलेगी

  • Play Billing लाइब्रेरी को इंटिग्रेट करने का तरीका
  • Play Console की मदद से, शुल्क लेकर सदस्यता देने वाले प्रॉडक्ट, बुनियादी प्लान, ऑफ़र, और टैग बनाने का तरीका
  • ऐप्लिकेशन से, उपलब्ध बुनियादी प्लान और ऑफ़र वापस पाने का तरीका
  • सही पैरामीटर के साथ बिलिंग फ़्लो को लॉन्च करने का तरीका
  • शुल्क लेकर सदस्यता देने वाले प्रीपेड प्रॉडक्ट ऑफ़र करने का तरीका

इस कोडलैब का फ़ोकस, Google Play Billing पर है. ऐसे कॉन्सेप्ट और कोड ब्लॉक कर दिए जाते हैं जो काम के नहीं होते. वे आपको दिए जाते हैं, ताकि आप उन्हें आसानी से कॉपी करके चिपका सकें.

आपको इन चीज़ों की ज़रूरत होगी

  • Android Studio का नया वर्शन (>= Arctic Fox | 2020.3.1)
  • Android 8.0 या उसके बाद के वर्शन वाला Android डिवाइस
  • GitHub पर आपके लिए दिया गया सैंपल कोड (निर्देश बाद के सेक्शन में दिए गए हैं)
  • Android Studio पर Android डेवलपमेंट के बारे में सामान्य जानकारी
  • किसी ऐप्लिकेशन को Google Play Store पर पब्लिश करने के तरीके की जानकारी
  • Kotlin कोड लिखने का ठीक-ठाक अनुभव
  • Google Play Billing लाइब्रेरी का 5.0.0 वर्शन

2. सेट अप किया जा रहा है

GitHub से कोड पाएं

हमने इस प्रोजेक्ट के लिए आपकी ज़रूरत की हर चीज़ को Git रेपो में डाल दिया है. शुरू करने के लिए, आपको कोड लेना होगा और उसे अपने पसंदीदा डेवलपर एनवायरमेंट में खोलना होगा. हमारा सुझाव है कि इस कोडलैब के लिए, Android Studio का इस्तेमाल करें.

शुरू करने के लिए, कोड को GitHub रिपॉज़िटरी में सेव किया जाता है. इस कमांड की मदद से, रिपॉज़िटरी का क्लोन बनाया जा सकता है:

git clone https://github.com/android/play-billing-samples.git
cd PlayBillingCodelab

3. बुनियाद का निर्माण

शुरुआत करने के लिए हम क्या करना चाहते हैं?

हमारा शुरुआती पॉइंट, उपयोगकर्ता की प्रोफ़ाइल का बेसिक ऐप्लिकेशन है. इसे इस कोडलैब के लिए डिज़ाइन किया गया है. इस कोड को आसान बना दिया गया है, ताकि वीडियो में दिखाए गए कॉन्सेप्ट को आसानी से समझा जा सके. हालांकि, कोड को प्रोडक्शन के लिए डिज़ाइन नहीं किया गया है. अगर आपको प्रोडक्शन ऐप्लिकेशन में इस कोड के किसी भी हिस्से का फिर से इस्तेमाल करना है, तो सबसे सही तरीके अपनाएं. साथ ही, अपने सभी कोड की पूरी तरह से जांच करें.

प्रोजेक्ट को Android Studio में इंपोर्ट करें

Play BillingCodelab, एक ऐसा बेस ऐप्लिकेशन है जिसमें Google Play Billing को लागू नहीं किया जाता. Android Studio शुरू करें और Open > billing/PlayBillingCodelab चुनकर बिलिंग-कोडलैब इंपोर्ट करें

इस प्रोजेक्ट में दो मॉड्यूल हैं:

  • start में स्केलेटन ऐप्लिकेशन है, लेकिन इसमें ज़रूरी डिपेंडेंसी और लागू करने के लिए ज़रूरी सभी तरीके मौजूद नहीं हैं.
  • finished में पूरा प्रोजेक्ट होता है. किसी समस्या में फंसने पर, यह गाइड की तरह काम कर सकता है.

ऐप्लिकेशन में आठ क्लास फ़ाइलें हैं: BillingClientWrapper, SubscriptionDataRepository, Composables, MainState, MainViewModel, MainViewModelFactory, और MainActivity.

  • BillingClientWrapper, एक ऐसा रैपर है जो Google Play Billing के [बिलिंग क्लाइंट] से जुड़े तरीकों को अलग करता है, ताकि उन्हें आसान बनाया जा सके. साथ ही, डेटा को प्रोसेस करने के लिए डेटा स्टोर करने की जगह को रिस्पॉन्स भेजा जाता है.
  • SubscriptionDataRepository का इस्तेमाल, Google Play Billing डेटा सोर्स (उदाहरण के लिए, बिलिंग क्लाइंट लाइब्रेरी) को ऐब्स्ट्रैक्ट करने के लिए किया जाता है. साथ ही, यह BillingClientWrapper में जनरेट होने वाले StateFlow डेटा को फ़्लो में बदल देता है.
  • ButtonModel एक डेटा क्लास है, जिसका इस्तेमाल यूज़र इंटरफ़ेस (यूआई) में बटन बनाने के लिए किया जाता है.
  • Composables, यूज़र इंटरफ़ेस (यूआई) के कंपोज़ेबल तरीकों को एक क्लास में एक्सट्रैक्ट करता है.
  • MainState, राज्य मैनेजमेंट के लिए एक डेटा क्लास है.
  • MainViewModel का इस्तेमाल, यूज़र इंटरफ़ेस (यूआई) में इस्तेमाल होने वाले बिलिंग से जुड़े डेटा और स्टेट को होल्ड करने के लिए किया जाता है. यह SubscriptionDataRepository के सभी फ़्लो को एक स्टेट ऑब्जेक्ट में जोड़ता है.
  • MainActivity मुख्य गतिविधि की क्लास होती है, जो यूज़र इंटरफ़ेस के लिए कंपोज़ेबल को लोड करती है.
  • कॉन्सटेंट वह ऑब्जेक्ट है जिसमें ऐसे कॉन्सटेंट होते हैं जिनका इस्तेमाल कई क्लास में किया जाता है.

ग्रेडल

अपने ऐप्लिकेशन में Google Play Billing को जोड़ने के लिए, आपको Gradle डिपेंडेंसी जोड़नी होगी. ऐप्लिकेशन मॉड्यूल की create.gradle फ़ाइल खोलें और इन्हें जोड़ें:

dependencies {
    val billing_version = "5.0.0"

    implementation("com.android.billingclient:billing:$billing_version")
}

Google Play कंसोल

इस कोडलैब के लिए, आपको Google Play Console के सदस्यता सेक्शन में, शुल्क लेकर सदस्यता देने वाले ये दो प्रॉडक्ट बनाने होंगे:

  • प्रॉडक्ट आईडी up_basic_sub वाली 1 सामान्य सदस्यता

प्रॉडक्ट के तीन बुनियादी प्लान होने चाहिए, जिनमें से दो अपने-आप रिन्यू होने वाले और एक प्रीपेड) टैग हों : monthlybasic टैग वाली एक महीने की बेसिक सदस्यता, yearlybasic टैग वाली एक सालाना बेसिक सदस्यता, और prepaidbasic टैग वाली एक प्रीपेड सदस्यता

आपके पास बुनियादी प्लान में ऑफ़र जोड़ने का विकल्प होता है. ऑफ़र पर, उनसे जुड़े बुनियादी प्लान के टैग लागू होंगे.

  • प्रॉडक्ट आईडी up_premium_sub वाली एक प्रीमियम सदस्यता

प्रॉडक्ट में तीन ऐसे बुनियादी प्लान होने चाहिए जो अपने-आप रिन्यू होते हों और एक प्रीपेड) टैग हों: monthlypremium टैग वाली एक महीने की बेसिक सदस्यता, yearlypremium टैग वाली एक सालाना बेसिक सदस्यता, और prepaidpremium टैग वाली एक प्रीपेड सदस्यता

a9f6fd6e70e69fed.png

आपके पास बुनियादी प्लान में ऑफ़र जोड़ने का विकल्प होता है. ऑफ़र पर, उनसे जुड़े बुनियादी प्लान के टैग लागू होंगे.

शुल्क लेकर सदस्यता देने वाले प्रॉडक्ट, बुनियादी प्लान, ऑफ़र, और टैग बनाने के तरीके के बारे में ज़्यादा जानकारी के लिए, कृपया Google Play के सहायता केंद्र पर जाएं.

4. बिलिंग क्लाइंट सेट अप

इस सेक्शन के लिए आपको BillingClientWrapper क्लास में काम करना होगा.

आखिर में, आपके पास बिलिंग क्लाइंट को इंस्टैंशिएट करने के लिए सभी ज़रूरी चीज़ें और उनसे जुड़े सभी तरीके होंगे.

  1. बिलिंग क्लाइंट शुरू करना

Google Play Billing Library पर डिपेंडेंसी जोड़ने के बाद, हमें BillingClient इंस्टेंस शुरू करना होगा.

BillingClientWrapper.kt

private val billingClient = BillingClient.newBuilder(context)
   .setListener(this)
   .enablePendingPurchases()
   .build()
  1. Google Play से कनेक्ट करना

बिलिंग क्लाइंट बनाने के बाद, हमें Google Play से कनेक्ट करना होगा.

Google Play से कनेक्ट करने के लिए, हम startConnection() पर कॉल करते हैं. कनेक्शन की प्रोसेस एसिंक्रोनस होती है. क्लाइंट का सेटअप पूरा होने और आने वाले समय में अनुरोध करने के लिए तैयार होने पर, हमें कॉलबैक पाने के लिए, BillingClientStateListener लागू करना होगा.

BillingClientWrapper.kt

fun startBillingConnection(billingConnectionState: MutableLiveData<Boolean>) {

   billingClient.startConnection(object : BillingClientStateListener {
       override fun onBillingSetupFinished(billingResult: BillingResult) {
           if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
               Log.d(TAG, "Billing response OK")
               // The BillingClient is ready. You can query purchases and product details here
               queryPurchases()
               queryProductDetails()
               billingConnectionState.postValue(true)
           } else {
               Log.e(TAG, billingResult.debugMessage)
           }
       }

       override fun onBillingServiceDisconnected() {
           Log.i(TAG, "Billing connection disconnected")
           startBillingConnection(billingConnectionState)
       }
   })
}
  1. मौजूदा खरीदारी के लिए, Google Play Billing के बारे में क्वेरी करना

Google Play से कनेक्ट हो जाने के बाद, हम उन खरीदारी के बारे में क्वेरी करने के लिए तैयार हैं जो उपयोगकर्ता ने पहले queryPurchasesAsync() पर कॉल करके की थीं.

BillingClientWrapper.kt

fun queryPurchases() {
   if (!billingClient.isReady) {
       Log.e(TAG, "queryPurchases: BillingClient is not ready")
   }
   // Query for existing subscription products that have been purchased.
   billingClient.queryPurchasesAsync(
       QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build()
   ) { billingResult, purchaseList ->
       if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
           if (!purchaseList.isNullOrEmpty()) {
               _purchases.value = purchaseList
           } else {
               _purchases.value = emptyList()
           }

       } else {
           Log.e(TAG, billingResult.debugMessage)
       }
   }
}
  1. खरीदने के लिए उपलब्ध प्रॉडक्ट दिखाना

अब हम उपलब्ध प्रॉडक्ट के लिए क्वेरी करके उन्हें उपयोगकर्ताओं को दिखा सकते हैं. शुल्क लेकर सदस्यता देने वाले प्रॉडक्ट की जानकारी के लिए, Google Play से क्वेरी करने के लिए, हम queryProductDetailsAsync() पर कॉल करेंगे. लोगों को प्रॉडक्ट दिखाने से पहले, प्रॉडक्ट की जानकारी के लिए क्वेरी करना एक अहम चरण है. ऐसा इसलिए, क्योंकि इससे प्रॉडक्ट की स्थानीय भाषा में जानकारी मिलती है.

BillingClientWrapper.kt

fun queryProductDetails() {
   val params = QueryProductDetailsParams.newBuilder()
   val productList = mutableListOf<QueryProductDetailsParams.Product>()
   for (product in LIST_OF_PRODUCTS) {

       productList.add(
           QueryProductDetailsParams.Product.newBuilder()
               .setProductId(product)
               .setProductType(BillingClient.ProductType.SUBS)
               .build()
       )

       params.setProductList(productList).let { productDetailsParams ->
           Log.i(TAG, "queryProductDetailsAsync")
           billingClient.queryProductDetailsAsync(productDetailsParams.build(), this)
       }
   }
}
  1. ProductDetails क्वेरी के लिए लिसनर सेट करें

ध्यान दें: यह तरीका, क्वेरी के नतीजे को _productWithProductDetails के लिए मैप में दिखाता है.

यह भी ध्यान रखें कि क्वेरी में ProductDetails दिख सकता है. अगर ऐसा नहीं होता है, तो हो सकता है कि Play Console में सेट अप किए गए प्रॉडक्ट चालू न हुए हों. इसके अलावा, यह भी हो सकता है कि आपने किसी भी रिलीज़ ट्रैक में, बिलिंग क्लाइंट डिपेंडेंसी के साथ बिल्ड पब्लिश न किया हो.

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. परचेज़ फ़्लो लॉन्च करना

जब उपयोगकर्ता किसी आइटम को खरीदने के लिए क्लिक करता है, तब launchBillingFlow को कॉल किया जाता है. यह Google Play को निर्देश देता है कि वह प्रॉडक्ट के ProductDetails के साथ खरीदारी की प्रोसेस शुरू करे.

BillingClientWrapper.kt

fun launchBillingFlow(activity: Activity, params: BillingFlowParams) {
   if (!billingClient.isReady) {
       Log.e(TAG, "launchBillingFlow: BillingClient is not ready")
   }
   billingClient.launchBillingFlow(activity, params)

}
  1. परचेज़ ऑपरेशन के नतीजे के लिए, लिसनर सेट करना

जब कोई उपयोगकर्ता Google Play पर खरीदारी की स्क्रीन से बाहर निकल जाता है, तब onPurchaseUpdated() कॉलबैक, खरीदारी के फ़्लो के नतीजों को वापस आपके ऐप्लिकेशन पर भेजता है. खरीदारी की प्रोसेस पूरी करने के लिए, 'खरीदें' बटन पर टैप करके या खरीदारी रद्द करने के लिए 'वापस जाएं' बटन पर टैप किया जाता है. इसके बाद, BillingResult.responseCode के आधार पर यह पता लगाया जा सकता है कि उपयोगकर्ता ने प्रॉडक्ट खरीदा है या नहीं. अगर responseCode == OK है, तो इसका मतलब है कि खरीदारी पूरी हो गई है.

onPurchaseUpdated(), Purchase ऑब्जेक्ट की सूची पास करता है. इसमें वे सभी खरीदारी शामिल होती हैं जो उपयोगकर्ता ने ऐप्लिकेशन से की हैं. कई अन्य फ़ील्ड में, हर खरीदारी ऑब्जेक्ट में प्रॉडक्ट आईडी, purchaseToken, और isAcknowledged एट्रिब्यूट शामिल होते हैं. इन फ़ील्ड का इस्तेमाल करके, हर Purchase ऑब्जेक्ट के लिए यह तय किया जा सकता है कि क्या यह ऐसी नई खरीदारी है जिसे प्रोसेस करने की ज़रूरत है या यह ऐसी मौजूदा खरीदारी है जिसे प्रोसेस करने की ज़रूरत नहीं है.

सदस्यता की खरीदारी को प्रोसेस करने का मतलब, नई खरीदारी को स्वीकार करना है.

BillingClientWrapper.kt

override fun onPurchasesUpdated(
   billingResult: BillingResult,
   purchases: List<Purchase>?
) {
   if (billingResult.responseCode == BillingClient.BillingResponseCode.OK
       && !purchases.isNullOrEmpty()
   ) {
       // Post new purchase List to _purchases
       _purchases.value = purchases

       // Then, handle the purchases
       for (purchase in purchases) {
           acknowledgePurchases(purchase)
       }
   } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
       // Handle an error caused by a user cancelling the purchase flow.
       Log.e(TAG, "User has cancelled")
   } else {
       // Handle any other error codes.
   }
}
  1. खरीदारी को प्रोसेस करना (खरीदारी की पुष्टि करना और उन्हें स्वीकार करना)

जब कोई उपयोगकर्ता खरीदारी पूरी करता है, तो ऐप्लिकेशन को खरीदारी को स्वीकार करके, खरीदारी को प्रोसेस करना होता है.

इसके अलावा, शिकायत को स्वीकार करने की प्रोसेस पूरी होने के बाद, _isNewPurchaseAcknowledged की वैल्यू 'सही' पर सेट हो जाती है.

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. बिलिंग कनेक्शन खत्म करना

आखिर में, जब कोई गतिविधि खत्म की जाती है, तो आपको Google Play से अपना कनेक्शन खत्म करना होता है. इसलिए, endConnection() को ऐसा करने के लिए कहा जाता है.

BillingClientWrapper.kt

fun terminateBillingConnection() {
   Log.i(TAG, "Terminating connection")
   billingClient.endConnection()
}

5. SubscriptionDataRepository

BillingClientWrapper में, QueryPurchasesAsync और QueryProductDetails के जवाब क्रम से MutableStateFlow _purchases और _productWithProductDetails पर पोस्ट किए जाते हैं. ये जवाब, खरीदारी और productWithProductDetails की मदद से क्लास से बाहर दिखाए जाते हैं.

SubscriptionDataRepository में, खरीदे गए प्रॉडक्ट के आधार पर खरीदारी को तीन फ़्लो में प्रोसेस किया जाता है: hasRenewableBasic, hasPrepaidBasic, hasRenewablePremium, और hasPremiumPrepaid.

इसके अलावा, productWithProductDetails को संबंधित basicProductDetails और premiumProductDetails फ़्लो में प्रोसेस किया जाता है.

6. MainViewModel

सबसे मुश्किल काम हो गया. अब आप MainViewModel तय करेंगे, जो आपके क्लाइंट के लिए सिर्फ़ एक सार्वजनिक इंटरफ़ेस है. इस वजह से, उन्हें BillingClientWrapper और SubscriptionDataRepository के बारे में जानने की ज़रूरत नहीं होती.

सबसे पहले, MainViewModel में, viewModel के शुरू होने पर हम बिलिंग कनेक्शन शुरू करते हैं.

MainViewModel.kt

init {
   billingClient.startBillingConnection(billingConnectionState = _billingConnectionState)
}

इसके बाद, रेपो क्लास के हिसाब से डेटा स्टोर करने के फ़्लो को productsForSaleFlows (उपलब्ध प्रॉडक्ट के लिए) और userCurrentSubscriptionFlow (उपयोगकर्ता की मौजूदा और चालू सदस्यता के लिए) में मिला दिया जाता है.

मौजूदा खरीदारी की सूची, currentPurchasesFlow वाले यूज़र इंटरफ़ेस (यूआई) में भी उपलब्ध कराई जाती है.

MainViewModel.kt

val productsForSaleFlows = combine(
   repo.basicProductDetails,
   repo.premiumProductDetails
) { basicProductDetails,
   premiumProductDetails
   ->
   MainState(
       basicProductDetails = basicProductDetails,
       premiumProductDetails = premiumProductDetails
   )
}

// The userCurrentSubscriptionFlow object combines all the possible subscription flows into one
// for emission.
private val userCurrentSubscriptionFlow = combine(
   repo.hasRenewableBasic,
   repo.hasPrepaidBasic,
   repo.hasRenewablePremium,
   repo.hasPrepaidPremium
) { hasRenewableBasic,
   hasPrepaidBasic,
   hasRenewablePremium,
   hasPrepaidPremium
   ->
   MainState(
       hasRenewableBasic = hasRenewableBasic,
       hasPrepaidBasic = hasPrepaidBasic,
       hasRenewablePremium = hasRenewablePremium,
       hasPrepaidPremium = hasPrepaidPremium
   )
}

// Current purchases.
val currentPurchasesFlow = repo.purchases

मिले-जुले userCurrentSubscriptionFlow को एक इनिट ब्लॉक में इकट्ठा किया जाता है और वैल्यू को _destinationScreen नाम के म्यूट करने लायक LiveData ऑब्जेक्ट में पोस्ट किया जाता है.

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 कुछ बहुत ही मददगार तरीके भी जोड़ता है:

  1. बुनियादी प्लान और ऑफ़र टोकन पाना

Play Billing Library के वर्शन 5.0.0 और इसके बाद के वर्शन में, शुल्क लेकर सदस्यता देने वाले सभी प्रॉडक्ट में कई बुनियादी प्लान और ऑफ़र हो सकते हैं. हालांकि, प्रीपेड बुनियादी प्लान और ऑफ़र उपलब्ध नहीं होंगे.

इस तरीके से, उन सभी ऑफ़र और बुनियादी प्लान की जानकारी हासिल करने में मदद मिलती है जिनके लिए उपयोगकर्ता ज़रूरी शर्तें पूरी करता है. इसके लिए, टैग के नए कॉन्सेप्ट का इस्तेमाल किया जाता है, जिसे मिलते-जुलते ऑफ़र का ग्रुप बनाने के लिए इस्तेमाल किया जाता है.

उदाहरण के लिए, जब कोई उपयोगकर्ता महीने के हिसाब से बुनियादी सदस्यता खरीदने की कोशिश करता है, तो शुल्क लेकर सदस्यता देने वाले बेसिक प्रॉडक्ट से जुड़े सभी बुनियादी प्लान और ऑफ़र, monthlyBasic स्ट्रिंग के साथ टैग कर दिए जाते हैं.

MainViewModel.kt

private fun retrieveEligibleOffers(
   offerDetails: MutableList<ProductDetails.SubscriptionOfferDetails>,
   tag: String
): List<ProductDetails.SubscriptionOfferDetails> {
   val eligibleOffers = emptyList<ProductDetails.SubscriptionOfferDetails>().toMutableList()
   offerDetails.forEach { offerDetail ->
       if (offerDetail.offerTags.contains(tag)) {
           eligibleOffers.add(offerDetail)
       }
   }

   return eligibleOffers
}
  1. सबसे कम कीमत वाले ऑफ़र का हिसाब लगाना

जब कोई उपयोगकर्ता एक से ज़्यादा ऑफ़र पाने की ज़रूरी शर्तें पूरी करता है, तो leastPricedOfferToken() तरीके का इस्तेमाल करके, retrieveEligibleOffers() से लौटाए गए ऑफ़र में से सबसे कम ऑफ़र का हिसाब लगाया जाता है.

यह तरीका, चुने गए ऑफ़र का ऑफ़र आईडी टोकन दिखाता है.

इसे लागू करने से, pricingPhases सेट के हिसाब से सबसे कम कीमत वाले ऑफ़र दिखते हैं. इसमें औसत ऑफ़र को शामिल नहीं किया जाता है.

इसके अलावा, एक और विकल्प के तौर पर, सबसे कम औसत कीमत वाला ऑफ़र हासिल किया जा सकता है.

MainViewModel.kt

private fun leastPricedOfferToken(
   offerDetails: List<ProductDetails.SubscriptionOfferDetails>
): String {
   var offerToken = String()
   var leastPricedOffer: ProductDetails.SubscriptionOfferDetails
   var lowestPrice = Int.MAX_VALUE

   if (!offerDetails.isNullOrEmpty()) {
       for (offer in offerDetails) {
           for (price in offer.pricingPhases.pricingPhaseList) {
               if (price.priceAmountMicros < lowestPrice) {
                   lowestPrice = price.priceAmountMicros.toInt()
                   leastPricedOffer = offer
                   offerToken = leastPricedOffer.offerToken
               }
           }
       }
   }
   return offerToken
}
  1. BillingFlowParams बिल्डर

किसी खास प्रॉडक्ट के परचेज़ फ़्लो को लॉन्च करने के लिए, प्रॉडक्ट' ProductDetails के साथ-साथ, चुने गए ऑफ़र का टोकन सेट करना और BilingFlowParams बनाने के लिए उसका इस्तेमाल करना ज़रूरी है.

इसमें मदद करने के दो तरीके हैं:

upDowngradeBillingFlowParamsBuilder(), अपग्रेड और डाउनग्रेड के लिए पैरामीटर बनाता है.

MainViewModel.kt

private fun upDowngradeBillingFlowParamsBuilder(
   productDetails: ProductDetails,
   offerToken: String,
   oldToken: String
): BillingFlowParams {
   return BillingFlowParams.newBuilder().setProductDetailsParamsList(
       listOf(
           BillingFlowParams.ProductDetailsParams.newBuilder()
               .setProductDetails(productDetails)
               .setOfferToken(offerToken)
               .build()
       )
   ).setSubscriptionUpdateParams(
       BillingFlowParams.SubscriptionUpdateParams.newBuilder()
           .setOldPurchaseToken(oldToken)
           .setReplaceProrationMode(
               BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE
           )
           .build()
   ).build()
}

billingFlowParamsBuilder(), सामान्य खरीदारी के लिए पैरामीटर बनाता है.

MainViewModel.kt

private fun billingFlowParamsBuilder(
   productDetails: ProductDetails,
   offerToken: String
): BillingFlowParams.Builder {
   return BillingFlowParams.newBuilder().setProductDetailsParamsList(
       listOf(
           BillingFlowParams.ProductDetailsParams.newBuilder()
               .setProductDetails(productDetails)
               .setOfferToken(offerToken)
               .build()
       )
   )
}
  1. खरीदारी का तरीका

खरीदारी का तरीका, खरीदारी शुरू करने के लिए BillingClientWrapper की launchBillingFlow() और BillingFlowParams का इस्तेमाल करता है.

MainViewModel.kt

fun buy(
   productDetails: ProductDetails,
   currentPurchases: List<Purchase>?,
   activity: Activity,
   tag: String
) {
   val offers =
       productDetails.subscriptionOfferDetails?.let {
           retrieveEligibleOffers(
               offerDetails = it,
               tag = tag.lowercase()
           )
       }
   val offerToken = offers?.let { leastPricedOfferToken(it) }
   val oldPurchaseToken: String

   // Get current purchase. In this app, a user can only have one current purchase at
   // any given time.
   if (!currentPurchases.isNullOrEmpty() &&
       currentPurchases.size == MAX_CURRENT_PURCHASES_ALLOWED
   ) {
       // This either an upgrade, downgrade, or conversion purchase.
       val currentPurchase = currentPurchases.first()

       // Get the token from current purchase.
       oldPurchaseToken = currentPurchase.purchaseToken

       val billingParams = offerToken?.let {
           upDowngradeBillingFlowParamsBuilder(
               productDetails = productDetails,
               offerToken = it,
               oldToken = oldPurchaseToken
           )
       }

       if (billingParams != null) {
           billingClient.launchBillingFlow(
               activity,
               billingParams
           )
       }
   } else if (currentPurchases == null) {
       // This is a normal purchase.
       val billingParams = offerToken?.let {
           billingFlowParamsBuilder(
               productDetails = productDetails,
               offerToken = it
           )
       }

       if (billingParams != null) {
           billingClient.launchBillingFlow(
               activity,
               billingParams.build()
           )
       }
   } else if (!currentPurchases.isNullOrEmpty() &&
       currentPurchases.size > MAX_CURRENT_PURCHASES_ALLOWED
   ) {
       // The developer has allowed users  to have more than 1 purchase, so they need to
       /// implement a logic to find which one to use.
       Log.d(TAG, "User has more than 1 current purchase.")
   }
}

d726a27add092140.png

  1. बिलिंग कनेक्शन खत्म करना

आखिर में, BillingClientWrapper के terminateBillingConnection तरीके को ViewModel के onCleared() पर कॉल किया जाता है.

इससे जुड़ी गतिविधि खत्म होने पर, मौजूदा बिलिंग कनेक्शन खत्म कर दिया जाता है.

7. यूज़र इंटरफ़ेस (यूआई)

अब, यूज़र इंटरफ़ेस (यूआई) में बनाई गई सभी चीज़ों का इस्तेमाल करने का समय आ गया है. इसमें मदद करने के लिए, आपको Composables और MainActivity क्लास के साथ काम करना होगा.

Composables.kt

Composables क्लास पूरी तरह से उपलब्ध है और यह उन सभी Compose के फ़ंक्शन के बारे में बताती है जिनका इस्तेमाल, यूज़र इंटरफ़ेस (यूआई) और उनके बीच नेविगेशन के तरीके को रेंडर करने के लिए किया जाता है.

Subscriptions फ़ंक्शन दो बटन दिखाता है: Basic Subscription और Premium Subscription.

Basic Subscription और Premium Subscription में से हर एक पर, Compose के नए तरीके लोड किए जाते हैं. इनमें हर महीने, सालाना, और प्रीपेड प्लान वाले तीन बुनियादी प्लान शामिल होते हैं.

इसके बाद, उपयोगकर्ता की किसी खास सदस्यता के लिए प्रोफ़ाइल बनाने की सुविधा तीन तरह की हो सकती है: एक अक्षय बेसिक, अक्षय प्रीमियम, और प्रीपेड बेसिक या प्रीपेड प्रीमियम.

  • अगर किसी उपयोगकर्ता के पास बेसिक सदस्यता है, तो बेसिक प्रोफ़ाइल से उसे महीने, साल या प्रीपेड प्रीमियम सदस्यता में अपग्रेड किया जा सकता है.
  • वहीं, जब किसी उपयोगकर्ता के पास प्रीमियम सदस्यता है, तो वह अपने प्लान को महीने, सालाना या प्रीपेड बुनियादी सदस्यता में डाउनग्रेड कर सकता है.
  • अगर किसी उपयोगकर्ता के पास प्रीपेड सदस्यता है, तो वह टॉप अप बटन का इस्तेमाल करके अपनी सदस्यता को टॉप अप कर सकता है. इसके अलावा, वह अपनी प्रीपेड सदस्यता को अपने-आप रिन्यू होने वाले किसी बुनियादी प्लान में बदल सकता है.

आखिर में, लोडिंग स्क्रीन फ़ंक्शन का इस्तेमाल तब किया जाता है, जब Google Play से कनेक्शन बनाया जा रहा हो और उपयोगकर्ता की प्रोफ़ाइल रेंडर की जा रही हो.

MainActivity.kt

  1. MainActivity बनाने के बाद, viewModel इंस्टैंशिएट हो जाता है और MainNavHost नाम का कंपोज़ फ़ंक्शन लोड होता है.
  2. MainNavHost, viewModel के billingConnectionSate Livedata से बनाए गए वैरिएबल isBillingConnected से शुरू होता है और बदलावों का पता लगाता है. इसकी वजह यह है कि जब ViModel इंस्टैंशिएट किया जाता है, तो यह billingConnectionSate को BillingClientWrapper के start BillingConnection तरीके में पास कर देता है.

isBillingConnected, कनेक्शन के जुड़ने पर 'सही है' पर सेट है. अगर कनेक्ट नहीं है, तो 'गलत' पर सेट है.

गलत होने पर, LoadingScreen() कंपोज़ फ़ंक्शन लोड हो जाता है और सही होने पर, Subscription या प्रोफ़ाइल फ़ंक्शन लोड हो जाते हैं.

val isBillingConnected by viewModel.billingConnectionState.observeAsState()
  1. बिलिंग कनेक्शन स्थापित होने पर:

लिखें navController इंस्टैंशिएट हो जाता है

val navController = rememberNavController()

इसके बाद, MainViewModel के फ़्लो इकट्ठा किए जाते हैं.

val productsForSale by viewModel.productsForSaleFlows.collectAsState(
   initial = MainState()
)

val currentPurchases by viewModel.currentPurchasesFlow.collectAsState(
   initial = listOf()
)

आखिर में, viewModel का destinationScreen LiveData वैरिएबल देखा गया.

उपयोगकर्ता की मौजूदा सदस्यता की स्थिति के आधार पर, इससे जुड़ा कंपोज़ फ़ंक्शन रेंडर किया जाता है.

val screen by viewModel.destinationScreen.observeAsState()
when (screen) {
   // User has a Basic Prepaid subscription
   // the corresponding profile is loaded.
   MainViewModel.DestinationScreen.BASIC_PREPAID_PROFILE_SCREEN -> {
       UserProfile(
           buttonModels =
           listOf(
               ButtonModel(R.string.topup_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = null,
                           tag = PREPAID_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.convert_to_basic_monthly_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = MONTHLY_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.convert_to_basic_yearly_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = YEARLY_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
           ),
           tag = PREPAID_BASIC_PLANS_TAG,
           profileTextStringResource = null
       )
   }
   // User has a renewable basic subscription
   // the corresponding profile is loaded.
   MainViewModel.DestinationScreen.BASIC_RENEWABLE_PROFILE -> {
       UserProfile(
           buttonModels =
           listOf(
               ButtonModel(R.string.monthly_premium_upgrade_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = MONTHLY_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.yearly_premium_upgrade_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = YEARLY_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.prepaid_premium_upgrade_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = PREPAID_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               }
           ),
           tag = null,
           profileTextStringResource = R.string.basic_sub_message
       )
   }
   // User has a prepaid Premium subscription
   // the corresponding profile is loaded.
   MainViewModel.DestinationScreen.PREMIUM_PREPAID_PROFILE_SCREEN -> {
       UserProfile(
           buttonModels =
           listOf(
               ButtonModel(R.string.topup_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = null,
                           tag = PREPAID_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.convert_to_premium_monthly_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = MONTHLY_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.convert_to_premium_yearly_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = YEARLY_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
           ),
           tag = PREPAID_PREMIUM_PLANS_TAG,
           profileTextStringResource = null
       )
   }
   // User has a renewable Premium subscription
   // the corresponding profile is loaded.
   MainViewModel.DestinationScreen.PREMIUM_RENEWABLE_PROFILE -> {
       UserProfile(
           listOf(
               ButtonModel(R.string.monthly_basic_downgrade_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = MONTHLY_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.yearly_basic_downgrade_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = YEARLY_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.prepaid_basic_downgrade_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = PREPAID_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               }
           ),
           tag = null,
           profileTextStringResource = R.string.premium_sub_message
       )
   }
   // User has no current subscription - the subscription composable
   // is loaded.
   MainViewModel.DestinationScreen.SUBSCRIPTIONS_OPTIONS_SCREEN -> {
       SubscriptionNavigationComponent(
           productsForSale = productsForSale,
           navController = navController,
           viewModel = viewModel
       )
   }
}

8. समाधान कोड

समाधान मॉड्यूल में पूरा समाधान कोड देखा जा सकता है.

9. बधाई हो

बधाई हो, आपने Google Play Billing Library 5.0.0 के, शुल्क लेकर सदस्यता देने वाले प्रॉडक्ट को एक बेहद आसान ऐप्लिकेशन में इंटिग्रेट कर दिया है!

सुरक्षित सर्वर सेटअप वाले ज़्यादा बेहतर ऐप्लिकेशन के दस्तावेज़ वाले वर्शन के लिए, आधिकारिक नमूना देखें.

आगे पढ़ें