आपके Flutter ऐप्लिकेशन में इन-ऐप्लिकेशन खरीदारी जोड़ना

1. परिचय

किसी Flutter ऐप्लिकेशन में इन-ऐप्लिकेशन खरीदारी की सुविधा जोड़ने के लिए, App Store और Play Store को सही तरीके से सेट अप करना, खरीदारी की पुष्टि करना, और ज़रूरी अनुमतियां देना ज़रूरी है. जैसे, सदस्यता के फ़ायदे.

इस कोडलैब में, आपको दिए गए ऐप्लिकेशन में तीन तरह की इन-ऐप्लिकेशन खरीदारी जोड़ने का तरीका बताया जाएगा. साथ ही, Firebase के साथ Dart बैकएंड का इस्तेमाल करके, इन खरीदारी की पुष्टि करने का तरीका बताया जाएगा. Dash Clicker ऐप्लिकेशन में एक ऐसा गेम शामिल है जिसमें Dash के मैस्कॉट को मुद्रा के तौर पर इस्तेमाल किया जाता है. आपको खरीदारी के ये विकल्प जोड़ने होंगे:

  1. एक बार में 2,000 डैश खरीदने का विकल्प, जिसे बार-बार इस्तेमाल किया जा सकता है.
  2. डैश के पुराने स्टाइल को मॉडर्न स्टाइल में बदलने के लिए, एक बार अपग्रेड खरीदना होगा.
  3. यह एक ऐसी सदस्यता है जिसमें अपने-आप जनरेट होने वाले क्लिक की संख्या दोगुनी हो जाती है.

पहली बार खरीदारी करने पर, उपयोगकर्ता को सीधे तौर पर 2,000 डैश का फ़ायदा मिलता है. ये सीधे तौर पर उपयोगकर्ता के लिए उपलब्ध होते हैं और इन्हें कई बार खरीदा जा सकता है. इसे इस्तेमाल की जा सकने वाली चीज़ कहा जाता है, क्योंकि इसका इस्तेमाल सीधे तौर पर किया जाता है और इसे कई बार इस्तेमाल किया जा सकता है.

दूसरे विकल्प से, डैश को ज़्यादा बेहतर बनाया जाता है. इसे सिर्फ़ एक बार खरीदना होता है और यह हमेशा के लिए उपलब्ध होता है. इस तरह की खरीदारी को नॉन-कंज्यूमेबल कहा जाता है, क्योंकि इसे ऐप्लिकेशन में इस्तेमाल नहीं किया जा सकता. हालांकि, यह हमेशा के लिए मान्य होती है.

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

बैकएंड सेवा (यह भी आपके लिए उपलब्ध कराई गई है) एक Dart ऐप्लिकेशन के तौर पर काम करती है. यह पुष्टि करती है कि खरीदारी की गई है और उन्हें Firestore का इस्तेमाल करके सेव करती है. Firestore का इस्तेमाल, प्रोसेस को आसान बनाने के लिए किया जाता है. हालांकि, प्रोडक्शन ऐप्लिकेशन में किसी भी तरह की बैकएंड सेवा का इस्तेमाल किया जा सकता है.

300123416ebc8dc1.png 7145d0fffe6ea741.png 646317a79be08214.png

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

  • आपको ऐप्लिकेशन को इस तरह से बढ़ाना होगा कि उसमें इस्तेमाल की जा सकने वाली चीज़ों की खरीदारी और सदस्यताएं काम करें.
  • खरीदे गए आइटम की पुष्टि करने और उन्हें सेव करने के लिए, Dart बैकएंड ऐप्लिकेशन को भी बढ़ाया जाएगा.

आपको क्या सीखने को मिलेगा

  • खरीदे जा सकने वाले प्रॉडक्ट के साथ App Store और Play Store को कॉन्फ़िगर करने का तरीका.
  • खरीदारी की पुष्टि करने और उन्हें Firestore में सेव करने के लिए, स्टोर से कैसे संपर्क करें.
  • ऐप्लिकेशन में की गई खरीदारी को मैनेज करने का तरीका.

ज़रूरी शर्तें

  • Android Studio
  • Xcode (iOS डेवलपमेंट के लिए)
  • Flutter SDK

2. डेवलपमेंट एनवायरमेंट सेट अप करना

इस कोडलैब को शुरू करने के लिए, कोड डाउनलोड करें. साथ ही, iOS के लिए बंडल आईडी और Android के लिए पैकेज का नाम बदलें.

कोड डाउनलोड करना

कमांड लाइन से GitHub रिपॉज़िटरी को क्लोन करने के लिए, इस कमांड का इस्तेमाल करें:

git clone https://github.com/flutter/codelabs.git flutter-codelabs

इसके अलावा, अगर आपने GitHub का cli टूल इंस्टॉल किया है, तो इस कमांड का इस्तेमाल करें:

gh repo clone flutter/codelabs flutter-codelabs

सैंपल कोड को flutter-codelabs डायरेक्ट्री में क्लोन किया जाता है. इसमें कोडलब की एक सीरीज़ के लिए कोड होता है. इस कोडलैब का कोड flutter-codelabs/in_app_purchases में है.

flutter-codelabs/in_app_purchases में मौजूद डायरेक्ट्री स्ट्रक्चर में, हर चरण के आखिर में आपको कहां होना चाहिए, इसके स्नैपशॉट शामिल होते हैं. स्टार्टर कोड, चरण 0 में है. इसलिए, इस पर जाने के लिए यह तरीका अपनाएं:

cd flutter-codelabs/in_app_purchases/step_00

अगर आपको किसी चरण को आगे बढ़ाना है या यह देखना है कि किसी चरण के बाद कोई चीज़ कैसी दिखनी चाहिए, तो उस चरण के नाम वाली डायरेक्ट्री में देखें. आखिरी चरण का कोड, complete फ़ोल्डर में मौजूद है.

स्टार्टर प्रोजेक्ट सेट अप करना

अपने पसंदीदा IDE में, step_00/app से स्टार्टर प्रोजेक्ट खोलें. हमने स्क्रीनशॉट के लिए Android Studio का इस्तेमाल किया है. हालांकि, Visual Studio Code भी एक बेहतरीन विकल्प है. किसी भी एडिटर का इस्तेमाल करते समय, पक्का करें कि Dart और Flutter के नए प्लगिन इंस्टॉल किए गए हों.

आपको जिन ऐप्लिकेशन को बनाना है उन्हें App Store और Play Store से कम्यूनिकेट करना होगा. इससे यह पता चलेगा कि कौनसे प्रॉडक्ट उपलब्ध हैं और उनकी कीमत क्या है. हर ऐप्लिकेशन की पहचान एक यूनीक आईडी से होती है. iOS App Store के लिए इसे बंडल आईडी और Android Play Store के लिए इसे ऐप्लिकेशन आईडी कहा जाता है. इन आइडेंटिफ़ायर को आम तौर पर, रिवर्स डोमेन नेम नोटेशन का इस्तेमाल करके बनाया जाता है. उदाहरण के लिए, flutter.dev के लिए इन ऐप्लिकेशन खरीदारी वाला ऐप्लिकेशन बनाते समय, dev.flutter.inapppurchase का इस्तेमाल किया जाएगा. अपने ऐप्लिकेशन के लिए कोई आइडेंटिफ़ायर सोचें. अब आपको उसे प्रोजेक्ट की सेटिंग में सेट करना होगा.

सबसे पहले, iOS के लिए बंडल आइडेंटिफ़ायर सेट अप करें. इसके लिए, Xcode ऐप्लिकेशन में Runner.xcworkspace फ़ाइल खोलें.

a9fbac80a31e28e0.png

Xcode के फ़ोल्डर स्ट्रक्चर में, Runner प्रोजेक्ट सबसे ऊपर होता है. साथ ही, Flutter, Runner, और Products टारगेट, Runner प्रोजेक्ट के नीचे होते हैं. प्रोजेक्ट की सेटिंग में बदलाव करने के लिए, Runner पर दो बार क्लिक करें. इसके बाद, Signing & Capabilities पर क्लिक करें. अपनी टीम सेट करने के लिए, टीम फ़ील्ड में वह बंडल आइडेंटिफ़ायर डालें जिसे आपने अभी चुना है.

812f919d965c649a.jpeg

अब Xcode को बंद करें और Android Studio पर वापस जाएं. इसके बाद, Android के लिए कॉन्फ़िगरेशन पूरा करें. इसके लिए, android/app, में जाकर build.gradle.kts फ़ाइल खोलें. इसके बाद, अपने applicationId (नीचे दिए गए स्क्रीनशॉट में 24वीं लाइन पर) को ऐप्लिकेशन आईडी में बदलें. यह आईडी, iOS बंडल आइडेंटिफ़ायर के जैसा ही होता है. ध्यान दें कि iOS और Android स्टोर के आईडी एक जैसे होने ज़रूरी नहीं हैं. हालांकि, इन्हें एक जैसा रखने से गड़बड़ियां कम होती हैं. इसलिए, इस कोडलैब में हम एक जैसे आइडेंटिफ़ायर का इस्तेमाल करेंगे.

e320a49ff2068ac2.png

3. प्लगिन इंस्टॉल करना

इस कोडलैब के इस हिस्से में, आपको in_app_purchase प्लगिन इंस्टॉल करना होगा.

pubspec में डिपेंडेंसी जोड़ना

अपने प्रोजेक्ट की डिपेंडेंसी में in_app_purchase जोड़कर, pubspec में in_app_purchase जोड़ें:

$ cd app
$ flutter pub add in_app_purchase dev:in_app_purchase_platform_interface
Resolving dependencies... 
Downloading packages... 
  characters 1.4.0 (1.4.1 available)
  flutter_lints 5.0.0 (6.0.0 available)
+ in_app_purchase 3.2.3
+ in_app_purchase_android 0.4.0+3
+ in_app_purchase_platform_interface 1.4.0
+ in_app_purchase_storekit 0.4.4
+ json_annotation 4.9.0
  lints 5.1.1 (6.0.0 available)
  material_color_utilities 0.11.1 (0.13.0 available)
  meta 1.16.0 (1.17.0 available)
  provider 6.1.5 (6.1.5+1 available)
  test_api 0.7.6 (0.7.7 available)
Changed 5 dependencies!
7 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

अपना pubspec.yaml खोलें. इसके बाद, पुष्टि करें कि dependencies में in_app_purchase और dev_dependencies में in_app_purchase_platform_interface को एंट्री के तौर पर शामिल कर लिया गया है.

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cloud_firestore: ^6.0.0
  cupertino_icons: ^1.0.8
  firebase_auth: ^6.0.1
  firebase_core: ^4.0.0
  google_sign_in: ^7.1.1
  http: ^1.5.0
  intl: ^0.20.2
  provider: ^6.1.5
  logging: ^1.3.0
  in_app_purchase: ^3.2.3

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0
  in_app_purchase_platform_interface: ^1.4.0

4. App Store सेट अप करना

iOS पर इन-ऐप्लिकेशन खरीदारी की सुविधा सेट अप करने और उसे आज़माने के लिए, आपको App Store में एक नया ऐप्लिकेशन बनाना होगा. साथ ही, वहां खरीदारी के लिए उपलब्ध प्रॉडक्ट बनाने होंगे. आपको कुछ भी पब्लिश करने या ऐप्लिकेशन को Apple को समीक्षा के लिए भेजने की ज़रूरत नहीं है. इसके लिए, आपके पास डेवलपर खाता होना चाहिए. अगर आपके पास खाता नहीं है, तो Apple डेवलपर प्रोग्राम में रजिस्टर करें.

इन-ऐप्लिकेशन खरीदारी की सुविधा का इस्तेमाल करने के लिए, आपके पास App Store Connect में पैसे चुकाकर डाउनलोड किए जाने वाले ऐप्लिकेशन के लिए, चालू कानूनी समझौता होना चाहिए. https://appstoreconnect.apple.com/ पर जाएं और Agreements, Tax, and Banking पर क्लिक करें.

11db9fca823e7608.png

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

74c73197472c9aec.png

सब कुछ सही तरीके से सेट होने पर, पैसे चुकाकर डाउनलोड किए जाने वाले ऐप्लिकेशन का स्टेटस 'चालू है' के तौर पर दिखेगा. यह बेहद ज़रूरी है, क्योंकि चालू समझौते के बिना इन-ऐप्लिकेशन खरीदारी की सुविधा आज़माई नहीं जा सकती.

4a100bbb8cafdbbf.jpeg

ऐप्लिकेशन आईडी रजिस्टर करें

Apple Developer Portal में नया आइडेंटिफ़ायर बनाएं. developer.apple.com/account/resources/identifiers/list पर जाएं. इसके बाद, पहचानकर्ता हेडर के बगल में मौजूद "प्लस" आइकॉन पर क्लिक करें.

55d7e592d9a3fc7b.png

ऐप्लिकेशन के आईडी चुनना

13f125598b72ca77.png

ऐप्लिकेशन चुनें

41ac4c13404e2526.png

कुछ ब्यौरा दें और बंडल आईडी को XCode में पहले से सेट की गई वैल्यू से मैच करने के लिए सेट करें.

9d2c940ad80deeef.png

नया ऐप्लिकेशन आईडी बनाने के बारे में ज़्यादा जानकारी के लिए, डेवलपर खाते से जुड़ी सहायता देखें.

नया ऐप्लिकेशन बनाना

App Store Connect में, अपने यूनीक बंडल आइडेंटिफ़ायर का इस्तेमाल करके नया ऐप्लिकेशन बनाएं.

10509b17fbf031bd.png

5b7c0bb684ef52c7.png

नया ऐप्लिकेशन बनाने और कानूनी समझौतों को मैनेज करने के बारे में ज़्यादा जानकारी पाने के लिए, App Store Connect का सहायता केंद्र देखें.

इन-ऐप्लिकेशन खरीदारी की सुविधा को टेस्ट करने के लिए, आपको सैंडबॉक्स टेस्ट उपयोगकर्ता की ज़रूरत होगी. यह टेस्ट उपयोगकर्ता, iTunes से कनेक्ट नहीं होना चाहिए. इसका इस्तेमाल सिर्फ़ इन-ऐप्लिकेशन खरीदारी की जांच करने के लिए किया जाता है. ऐसे ईमेल पते का इस्तेमाल नहीं किया जा सकता जिसका इस्तेमाल पहले से ही किसी Apple खाते के लिए किया जा रहा हो. नया सैंडबॉक्स खाता बनाने या मौजूदा सैंडबॉक्स Apple आईडी मैनेज करने के लिए, उपयोगकर्ता और ऐक्सेस में जाकर सैंडबॉक्स पर जाएं.

2ba0f599bcac9b36.png

अब अपने iPhone पर सैंडबॉक्स उपयोगकर्ता को सेट अप किया जा सकता है. इसके लिए, सेटिंग > डेवलपर > सैंडबॉक्स Apple खाता पर जाएं.

74a545210b282ad8.png eaa67752f2350f74.png

इन-ऐप्लिकेशन खरीदारी की सुविधा कॉन्फ़िगर करना

अब खरीदने के लिए उपलब्ध तीन आइटम कॉन्फ़िगर करें:

  • dash_consumable_2k: यह एक ऐसी खरीदारी है जिसे कई बार किया जा सकता है. इससे उपयोगकर्ता को हर खरीदारी पर 2, 000 डैश (ऐप्लिकेशन में इस्तेमाल होने वाली मुद्रा) मिलते हैं.
  • dash_upgrade_3d: यह एक बार किया जाने वाला "अपग्रेड" है. इसे सिर्फ़ एक बार खरीदा जा सकता है. इससे उपयोगकर्ता को क्लिक करने के लिए, कॉस्मेटिक तौर पर अलग डैश मिलता है.
  • dash_subscription_doubler: यह एक ऐसी सदस्यता है जो उपयोगकर्ता को सदस्यता की अवधि के दौरान, हर क्लिक पर दो गुना डैश देती है.

a118161fac83815a.png

इन-ऐप्लिकेशन खरीदारी पर जाएं.

बताए गए आईडी का इस्तेमाल करके, इन-ऐप्लिकेशन खरीदारी की सुविधा बनाएं:

  1. dash_consumable_2k को इस्तेमाल की जा सकने वाली चीज़ के तौर पर सेट अप करें. प्रॉडक्ट आईडी के तौर पर dash_consumable_2k का इस्तेमाल करें. रेफ़रंस नाम का इस्तेमाल सिर्फ़ App Store Connect में किया जाता है. इसे dash consumable 2k पर सेट करें. 1f8527fc03902099.png उपलब्धता सेट अप करें. प्रॉडक्ट, सैंडबॉक्स उपयोगकर्ता के देश में उपलब्ध होना चाहिए. bd6b2ce2d9314e6e.png कीमत जोड़ें और उसे $1.99 या अन्य मुद्रा में इसके बराबर की कीमत पर सेट करें. 926b03544ae044c4.png खरीदारी के लिए, अपनी स्थानीय भाषाएं जोड़ें. खरीदारी को Spring is in the air के तौर पर कॉल करें. साथ ही, 2000 dashes fly out को ब्यौरे के तौर पर इस्तेमाल करें. e26dd4f966dcfece.png समीक्षा का स्क्रीनशॉट जोड़ें. जब तक प्रॉडक्ट को समीक्षा के लिए नहीं भेजा जाता, तब तक कॉन्टेंट से कोई फ़र्क़ नहीं पड़ता. हालांकि, प्रॉडक्ट को "सबमिट करने के लिए तैयार" स्थिति में होना ज़रूरी है. यह तब ज़रूरी होता है, जब ऐप्लिकेशन App Store से प्रॉडक्ट फ़ेच करता है. 25171bfd6f3a033a.png
  2. dash_upgrade_3d को नॉन-कन्ज़्यूमेबल के तौर पर सेट अप करें. प्रॉडक्ट आईडी के तौर पर dash_upgrade_3d का इस्तेमाल करें. रेफ़रंस का नाम dash upgrade 3d पर सेट करें. खरीदारी को 3D Dash के तौर पर कॉल करें. साथ ही, Brings your dash back to the future को ब्यौरे के तौर पर इस्तेमाल करें. किराया $0.99 पर सेट करें. dash_consumable_2k प्रॉडक्ट के लिए, उपलब्धता की जानकारी कॉन्फ़िगर करें और समीक्षा का स्क्रीनशॉट उसी तरह अपलोड करें. 83878759f32a7d4a.png
  3. dash_subscription_doubler को अपने-आप रिन्यू होने वाली सदस्यता के तौर पर सेट अप करें. सदस्यताओं के लिए, यह तरीका थोड़ा अलग है. सबसे पहले, आपको सदस्यता ग्रुप बनाना होगा. जब एक ही ग्रुप में कई सदस्यताएं शामिल होती हैं, तो कोई उपयोगकर्ता एक बार में इनमें से सिर्फ़ एक सदस्यता ले सकता है. हालांकि, वह इन सदस्यताओं के बीच अपग्रेड या डाउनग्रेड कर सकता है. बस इस ग्रुप subscriptions को कॉल करो. 393a44b09f3cd8bf.png इसके बाद, सदस्यता ग्रुप के लिए स्थानीय भाषा जोड़ें. 595aa910776349bd.png इसके बाद, सदस्यता बनाएं. रेफ़रंस नाम को dash subscription doubler और प्रॉडक्ट आईडी को dash_subscription_doubler पर सेट करें. 7bfff7bbe11c8eec.png इसके बाद, सदस्यता की अवधि के तौर पर एक हफ़्ता और स्थानीय भाषाएं चुनें. इस सदस्यता Jet Engine का नाम Doubles your clicks के तौर पर सेट करो. किराया $0.49 पर सेट करें. dash_consumable_2k प्रॉडक्ट के लिए, उपलब्धता की जानकारी कॉन्फ़िगर करें और समीक्षा का स्क्रीनशॉट उसी तरह अपलोड करें. 44d18e02b926a334.png

अब आपको इन सूचियों में प्रॉडक्ट दिखने चाहिए:

17f242b5c1426b79.png d71da951f595054a.png

5. Play Store सेट अप करना

App Store की तरह ही, आपको Play Store के लिए भी डेवलपर खाते की ज़रूरत होगी. अगर आपके पास अब तक कोई खाता नहीं है, तो खाता रजिस्टर करें.

नया ऐप्लिकेशन बनाना

Google Play Console में नया ऐप्लिकेशन बनाएं:

  1. Play Console खोलें.
  2. सभी ऐप्लिकेशन > ऐप्लिकेशन बनाएं चुनें.
  3. कोई डिफ़ॉल्ट भाषा चुनें और अपने ऐप्लिकेशन के लिए टाइटल जोड़ें. अपने ऐप्लिकेशन का वह नाम डालें जिसे Google Play पर दिखाना है. नाम को बाद में बदला जा सकता है.
  4. यह बताएं कि आपका ऐप्लिकेशन एक गेम है. हालांकि, इसे बाद में बदला जा सकता है.
  5. यह बताएं कि आपका ऐप्लिकेशन बिना शुल्क के उपलब्ध है या इसके लिए पैसे चुकाने होंगे.
  6. कॉन्टेंट से जुड़े दिशा-निर्देशों और अमेरिका के एक्सपोर्ट कानूनों की शर्तें पूरी करें.
  7. ऐप्लिकेशन बनाएं को चुनें.

ऐप्लिकेशन बन जाने के बाद, डैशबोर्ड पर जाएं और ऐप्लिकेशन सेट अप करें सेक्शन में दिए गए सभी टास्क पूरे करें. यहां आपको अपने ऐप्लिकेशन के बारे में कुछ जानकारी देनी होती है. जैसे, कॉन्टेंट रेटिंग और स्क्रीनशॉट. 13845badcf9bc1db.png

ऐप्लिकेशन पर हस्ताक्षर करना

इन-ऐप्लिकेशन खरीदारी की जांच करने के लिए, आपको Google Play पर कम से कम एक बिल्ड अपलोड करना होगा.

इसके लिए, आपको अपने रिलीज़ बिल्ड पर डीबग कुंजियों के अलावा किसी और कुंजी से साइन करना होगा.

कीस्टोर बनाना

अगर आपके पास पहले से कोई कीस्टोर है, तो अगले चरण पर जाएं. अगर ऐसा नहीं है, तो कमांड लाइन पर यहां दिया गया निर्देश चलाकर एक बनाएं.

Mac/Linux पर, इस कमांड का इस्तेमाल करें:

keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

Windows पर, इस कमांड का इस्तेमाल करें:

keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias key

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

keystore

फ़ाइल निजी है; इसे सार्वजनिक सोर्स कंट्रोल में शामिल न करें!

ऐप्लिकेशन से कीस्टोर को रेफ़रंस करें

<your app dir>/android/key.properties नाम की एक फ़ाइल बनाएं. इसमें अपने कीस्टोर का रेफ़रंस शामिल करें:

storePassword=<password from previous step>
keyPassword=<password from previous step>
keyAlias=key
storeFile=<location of the key store file, such as /Users/<user name>/key.jks>

Gradle में साइन इन करने की सुविधा कॉन्फ़िगर करना

<your app dir>/android/app/build.gradle.kts फ़ाइल में बदलाव करके, अपने ऐप्लिकेशन के लिए साइनिंग की सुविधा कॉन्फ़िगर करें.

android ब्लॉक से पहले, अपनी प्रॉपर्टी फ़ाइल में कीस्टोर की जानकारी जोड़ें:

import java.util.Properties
import java.io.FileInputStream

plugins {
    // omitted
}

val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}

android {
    // omitted
}

key.properties फ़ाइल को keystoreProperties ऑब्जेक्ट में लोड करें.

buildTypes ब्लॉक को इस तरह अपडेट करें:

   buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
        }
    }

साइनिंग कॉन्फ़िगरेशन की जानकारी के साथ, अपने मॉड्यूल की build.gradle.kts फ़ाइल में signingConfigs ब्लॉक को कॉन्फ़िगर करें:

   signingConfigs {
        create("release") {
            keyAlias = keystoreProperties["keyAlias"] as String
            keyPassword = keystoreProperties["keyPassword"] as String
            storeFile = keystoreProperties["storeFile"]?.let { file(it) }
            storePassword = keystoreProperties["storePassword"] as String
        }
    }

    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
        }
    }

अब आपके ऐप्लिकेशन की रिलीज़ बिल्ड पर अपने-आप हस्ताक्षर हो जाएंगे.

अपने ऐप्लिकेशन पर हस्ताक्षर करने के बारे में ज़्यादा जानने के लिए, developer.android.com पर अपने ऐप्लिकेशन पर हस्ताक्षर करना लेख पढ़ें.

अपना पहला बिल्ड अपलोड करना

साइनिंग के लिए ऐप्लिकेशन कॉन्फ़िगर होने के बाद, आपको यह कमांड चलाकर अपना ऐप्लिकेशन बनाना चाहिए:

flutter build appbundle

इस कमांड से, डिफ़ॉल्ट रूप से रिलीज़ बिल्ड जनरेट होता है. इसका आउटपुट <your app dir>/build/app/outputs/bundle/release/ पर देखा जा सकता है

Google Play Console के डैशबोर्ड में जाकर, ऐप्लिकेशन की रिलीज़ और टेस्टिंग > टेस्टिंग > क्लोज़्ड टेस्टिंग पर जाएं. इसके बाद, क्लोज़्ड टेस्टिंग के लिए नई रिलीज़ बनाएं.

इसके बाद, बिल्ड कमांड से जनरेट किया गया app-release.aab ऐप्लिकेशन बंडल अपलोड करें.

सेव करें पर क्लिक करें. इसके बाद, रिलीज़ की समीक्षा करें पर क्लिक करें.

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

टेस्ट यूज़र सेट अप करना

इन-ऐप्लिकेशन खरीदारी की जांच करने के लिए, टेस्टर के Google खातों को Google Play Console में दो जगहों पर जोड़ना होगा:

  1. किसी खास टेस्ट ट्रैक (इंटरनल टेस्टिंग) के लिए
  2. लाइसेंस टेस्टर के तौर पर

सबसे पहले, टेस्टर को इंटरनल टेस्टिंग ट्रैक में जोड़ें. जांच करें और रिलीज़ करें > टेस्टिंग > इंटरनल टेस्टिंग पर वापस जाएं. इसके बाद, टेस्टर टैब पर क्लिक करें.

a0d0394e85128f84.png

ईमेल सूची बनाएं पर क्लिक करके, नई ईमेल सूची बनाएं. सूची को कोई नाम दें. साथ ही, उन Google खातों के ईमेल पते जोड़ें जिन्हें ऐप्लिकेशन में खरीदारी की सुविधा की जांच करने का ऐक्सेस चाहिए.

इसके बाद, सूची के लिए चेकबॉक्स चुनें और बदलाव सेव करें पर क्लिक करें.

इसके बाद, लाइसेंस की जांच करने वाले लोगों के ईमेल पते जोड़ें:

  1. Google Play Console के सभी ऐप्लिकेशन व्यू पर वापस जाएं.
  2. सेटिंग > लाइसेंस टेस्टिंग पर जाएं.
  3. जांच करने वाले उन लोगों के ईमेल पते जोड़ें जिन्हें इन-ऐप्लिकेशन खरीदारी की सुविधा की जांच करनी है.
  4. लाइसेंस की स्थिति को RESPOND_NORMALLY पर सेट करें.
  5. बदलाव सेव करें पर क्लिक करें.

a1a0f9d3e55ea8da.png

इन-ऐप्लिकेशन खरीदारी की सुविधा कॉन्फ़िगर करना

अब आपको उन आइटम को कॉन्फ़िगर करना होगा जिन्हें ऐप्लिकेशन में खरीदा जा सकता है.

App Store की तरह ही, आपको तीन अलग-अलग खरीदारी तय करनी होंगी:

  • dash_consumable_2k: यह एक ऐसी खरीदारी है जिसे कई बार किया जा सकता है. इससे उपयोगकर्ता को हर खरीदारी पर 2, 000 डैश (ऐप्लिकेशन में इस्तेमाल होने वाली मुद्रा) मिलते हैं.
  • dash_upgrade_3d: यह एक बार की जाने वाली "अपग्रेड" खरीदारी है. इससे उपयोगकर्ता को क्लिक करने के लिए, कॉस्मेटिक तौर पर अलग डैश मिलता है.
  • dash_subscription_doubler: यह एक ऐसी सदस्यता है जो उपयोगकर्ता को सदस्यता की अवधि के दौरान, हर क्लिक पर दो गुना डैश देती है.

सबसे पहले, इस्तेमाल किए जा सकने वाले और इस्तेमाल न किए जा सकने वाले प्रॉडक्ट जोड़ें.

  1. Google Play Console पर जाएं और अपना ऐप्लिकेशन चुनें.
  2. कमाई करें > प्रॉडक्ट > ऐप्लिकेशन में खरीदे जा सकने वाले प्रॉडक्ट पर जाएं.
  3. प्रॉडक्ट बनाएंc8d66e32f57dee21.png पर क्लिक करें
  4. अपने प्रॉडक्ट की सभी ज़रूरी जानकारी डालें. पक्का करें कि प्रॉडक्ट आईडी, उस आईडी से मेल खाता हो जिसका आपको इस्तेमाल करना है.
  5. सेव करें पर क्लिक करें.
  6. चालू करें पर क्लिक करें.
  7. "अपग्रेड" करने के लिए की गई ऐसी खरीदारी के लिए यह प्रोसेस दोहराएं जिसे इस्तेमाल नहीं किया जा सकता.

इसके बाद, सदस्यता जोड़ें:

  1. Google Play Console पर जाएं और अपना ऐप्लिकेशन चुनें.
  2. कमाई करना > प्रॉडक्ट > सदस्यताएं पर जाएं.
  3. सदस्यता बनाएं32a6a9eefdb71dd0.png पर क्लिक करें
  4. अपनी सदस्यता के लिए सभी ज़रूरी जानकारी डालें. पक्का करें कि प्रॉडक्ट आईडी, उस आईडी से मेल खाता हो जिसका आपको इस्तेमाल करना है.
  5. सेव करें पर क्लिक करें

अब आपकी खरीदारी, Play Console में सेट अप हो जानी चाहिए.

6. Firebase सेट अप करना

इस कोडलैब में, उपयोगकर्ताओं की खरीदारी की पुष्टि करने और उन्हें ट्रैक करने के लिए, बैकएंड सेवा का इस्तेमाल किया जाएगा.

बैकएंड सेवा का इस्तेमाल करने के कई फ़ायदे हैं:

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

बैकएंड सेवा को सेट अप करने के कई तरीके हैं. हालांकि, इस ट्यूटोरियल में आपको Google के Firebase का इस्तेमाल करके, क्लाउड फ़ंक्शन और Firestore का इस्तेमाल करना होगा.

इस कोडलैब में बैकएंड लिखने के बारे में नहीं बताया गया है. इसलिए, स्टार्टर कोड में पहले से ही एक Firebase प्रोजेक्ट शामिल है. यह प्रोजेक्ट, बुनियादी खरीदारी को मैनेज करता है, ताकि आप आसानी से शुरुआत कर सकें.

स्टार्टर ऐप्लिकेशन में Firebase प्लगिन भी शामिल होते हैं.

अब आपको अपना Firebase प्रोजेक्ट बनाना है. साथ ही, Firebase के लिए ऐप्लिकेशन और बैकएंड, दोनों को कॉन्फ़िगर करना है. इसके बाद, बैकएंड को डिप्लॉय करना है.

Firebase प्रोजेक्ट बनाना

Firebase कंसोल पर जाएं और नया Firebase प्रोजेक्ट बनाएं. इस उदाहरण के लिए, प्रोजेक्ट को Dash Clicker नाम दें.

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

  1. Firebase डैशबोर्ड में जाकर, Authentication पर जाएं और अगर ज़रूरी हो, तो इसे चालू करें.
  2. साइन-इन करने का तरीका टैब पर जाएं और Google साइन-इन की सुविधा देने वाली कंपनी को चालू करें.

fe2e0933d6810888.png

आपको Firebase के Firestore डेटाबेस का भी इस्तेमाल करना होगा. इसलिए, इसे भी चालू करें.

d02d641821c71e2c.png

Cloud Firestore के नियमों को इस तरह सेट करें:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /purchases/{purchaseId} {
      allow read: if request.auth != null && request.auth.uid == resource.data.userId
    }
  }
}

Flutter के लिए Firebase सेट अप करना

हमारा सुझाव है कि Flutter ऐप्लिकेशन पर Firebase इंस्टॉल करने के लिए, FlutterFire CLI का इस्तेमाल करें. सेटअप पेज पर दिए गए निर्देशों का पालन करें.

flutterfire configure कमांड चलाने के दौरान, वह प्रोजेक्ट चुनें जिसे आपने पिछले चरण में बनाया था.

$ flutterfire configure

i Found 5 Firebase projects.
? Select a Firebase project to configure your Flutter application with ›
❯ in-app-purchases-1234 (in-app-purchases-1234)
  other-flutter-codelab-1 (other-flutter-codelab-1)
  other-flutter-codelab-2 (other-flutter-codelab-2)
  other-flutter-codelab-3 (other-flutter-codelab-3)
  other-flutter-codelab-4 (other-flutter-codelab-4)
  <create a new project>

इसके बाद, दोनों प्लैटफ़ॉर्म चुनकर iOS और Android को चालू करें.

? Which platforms should your configuration support (use arrow keys & space to select)? ›
✔ android
✔ ios
  macos
  web

जब आपसे firebase_options.dart को बदलने के बारे में पूछा जाए, तो हां चुनें.

? Generated FirebaseOptions file lib/firebase_options.dart already exists, do you want to override it? (y/n) › yes

Android के लिए Firebase सेट अप करना: अन्य चरण

Firebase डैशबोर्ड में, प्रोजेक्ट की खास जानकारी पर जाएं. इसके बाद,सेटिंग चुनें और सामान्य टैब को चुनें.

नीचे की ओर स्क्रोल करके आपके ऐप्लिकेशन पर जाएं. इसके बाद, dashclicker (android) ऐप्लिकेशन को चुनें.

b22d46a759c0c834.png

डीबग मोड में Google साइन-इन की सुविधा चालू करने के लिए, आपको अपने डीबग सर्टिफ़िकेट का SHA-1 हैश फ़िंगरप्रिंट देना होगा.

डीबग करने के लिए इस्तेमाल किए जाने वाले सर्टिफ़िकेट के हस्ताक्षर का हैश पाना

अपने Flutter ऐप्लिकेशन प्रोजेक्ट के रूट में, डायरेक्ट्री को android/ फ़ोल्डर में बदलें. इसके बाद, हस्ताक्षर करने की रिपोर्ट जनरेट करें.

cd android
./gradlew :app:signingReport

आपको साइनिंग की की एक बड़ी सूची दिखेगी. आपको डीबग सर्टिफ़िकेट का हैश चाहिए. इसलिए, ऐसे सर्टिफ़िकेट को ढूंढें जिसकी Variant और Config प्रॉपर्टी debug पर सेट हो. कीस्टोर, आपके होम फ़ोल्डर में .android/debug.keystore के नीचे मौजूद होता है.

> Task :app:signingReport
Variant: debug
Config: debug
Store: /<USER_HOME_FOLDER>/.android/debug.keystore
Alias: AndroidDebugKey
MD5: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
SHA1: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
SHA-256: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
Valid until: Tuesday, January 19, 2038

SHA-1 हैश कॉपी करें और ऐप्लिकेशन सबमिट करने वाले मोडल डायलॉग में मौजूद आखिरी फ़ील्ड में इसे भरें.

आखिर में, ऐप्लिकेशन को अपडेट करने के लिए flutterfire configure कमांड को फिर से चलाएं, ताकि साइनिंग कॉन्फ़िगरेशन शामिल किया जा सके.

$ flutterfire configure
? You have an existing `firebase.json` file and possibly already configured your project for Firebase. Would you prefer to reuse the valus in your existing `firebase.json` file to configure your project? (y/n) › yes
✔ You have an existing `firebase.json` file and possibly already configured your project for Firebase. Would you prefer to reuse the values in your existing `firebase.json` file to configure your project? · yes

iOS के लिए Firebase सेट अप करना: अन्य चरण

Xcode की मदद से ios/Runner.xcworkspace खोलें. इसके अलावा, अपनी पसंद के आईडीई का इस्तेमाल करके भी ऐसा किया जा सकता है.

VSCode में, ios/ फ़ोल्डर पर राइट क्लिक करें. इसके बाद, open in xcode पर क्लिक करें.

Android Studio में, ios/ फ़ोल्डर पर राइट क्लिक करें. इसके बाद, flutter पर क्लिक करें. इसके बाद, open iOS module in Xcode विकल्प पर क्लिक करें.

iOS पर Google साइन-इन की सुविधा चालू करने के लिए, अपनी बिल्ड plist फ़ाइलों में CFBundleURLTypes कॉन्फ़िगरेशन विकल्प जोड़ें. (ज़्यादा जानकारी के लिए, google_sign_in पैकेज के दस्तावेज़ देखें.) इस मामले में, फ़ाइल ios/Runner/Info.plist है.

की-वैल्यू पेयर पहले से जोड़ा गया है, लेकिन उनकी वैल्यू बदलनी होंगी:

  1. GoogleService-Info.plist फ़ाइल से REVERSED_CLIENT_ID की वैल्यू पाएं. इसके लिए, <string>..</string> एलिमेंट का इस्तेमाल न करें.
  2. CFBundleURLTypes कुंजी के तहत, अपनी ios/Runner/Info.plist फ़ाइल में मौजूद वैल्यू बदलें.
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <!-- TODO Replace this value: -->
            <!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
            <string>com.googleusercontent.apps.REDACTED</string>
        </array>
    </dict>
</array>

अब Firebase सेटअप हो गया है.

7. खरीदारी के अपडेट सुनना

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

खरीदारी से जुड़े अपडेट सुनना

main.dart, में, MyHomePage विजेट ढूंढें. इसमें Scaffold है और इसमें दो पेज वाला BottomNavigationBar है. यह पेज, DashCounter, DashUpgrades,, और DashPurchases के लिए तीन Provider भी बनाता है. DashCounter डैश की मौजूदा संख्या को ट्रैक करता है और उन्हें अपने-आप बढ़ाता है. DashUpgrades, डैश का इस्तेमाल करके खरीदे जा सकने वाले अपग्रेड मैनेज करता है. यह कोडलैब, DashPurchases पर फ़ोकस करता है.

डिफ़ॉल्ट रूप से, किसी प्रोवाइडर के ऑब्जेक्ट को तब तय किया जाता है, जब उस ऑब्जेक्ट का पहली बार अनुरोध किया जाता है. जब ऐप्लिकेशन शुरू होता है, तब यह ऑब्जेक्ट सीधे तौर पर खरीदारी से जुड़े अपडेट सुनता है. इसलिए, lazy: false का इस्तेमाल करके, इस ऑब्जेक्ट पर लेज़ी लोडिंग की सुविधा बंद करें:

lib/main.dart

ChangeNotifierProvider<DashPurchases>(
  create: (context) => DashPurchases(
    context.read<DashCounter>(),
  ),
  lazy: false,                                             // Add this line
),

आपको InAppPurchaseConnection का एक इंस्टेंस भी चाहिए. हालांकि, ऐप्लिकेशन को टेस्ट करने के लिए, आपको कनेक्शन को मॉक करने का कोई तरीका चाहिए. इसके लिए, एक इंस्टेंस मेथड बनाएं, जिसे टेस्ट में बदला जा सकता है. साथ ही, इसे main.dart में जोड़ें.

lib/main.dart

// Gives the option to override in tests.
class IAPConnection {
  static InAppPurchase? _instance;
  static set instance(InAppPurchase value) {
    _instance = value;
  }

  static InAppPurchase get instance {
    _instance ??= InAppPurchase.instance;
    return _instance!;
  }
}

टेस्ट को इस तरह अपडेट करें:

test/widget_test.dart

import 'package:dashclicker/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase/in_app_purchase.dart';     // Add this import
import 'package:in_app_purchase_platform_interface/src/in_app_purchase_platform_addition.dart'; // And this import

void main() {
  testWidgets('App starts', (tester) async {
    IAPConnection.instance = TestIAPConnection();          // Add this line
    await tester.pumpWidget(const MyApp());
    expect(find.text('Tim Sneath'), findsOneWidget);
  });
}

class TestIAPConnection implements InAppPurchase {         // Add from here
  @override
  Future<bool> buyConsumable({
    required PurchaseParam purchaseParam,
    bool autoConsume = true,
  }) {
    return Future.value(false);
  }

  @override
  Future<bool> buyNonConsumable({required PurchaseParam purchaseParam}) {
    return Future.value(false);
  }

  @override
  Future<void> completePurchase(PurchaseDetails purchase) {
    return Future.value();
  }

  @override
  Future<bool> isAvailable() {
    return Future.value(false);
  }

  @override
  Future<ProductDetailsResponse> queryProductDetails(Set<String> identifiers) {
    return Future.value(
      ProductDetailsResponse(productDetails: [], notFoundIDs: []),
    );
  }

  @override
  T getPlatformAddition<T extends InAppPurchasePlatformAddition?>() {
    // TODO: implement getPlatformAddition
    throw UnimplementedError();
  }

  @override
  Stream<List<PurchaseDetails>> get purchaseStream =>
      Stream.value(<PurchaseDetails>[]);

  @override
  Future<void> restorePurchases({String? applicationUserName}) {
    // TODO: implement restorePurchases
    throw UnimplementedError();
  }

  @override
  Future<String> countryCode() {
    // TODO: implement countryCode
    throw UnimplementedError();
  }
}                                                          // To here.

lib/logic/dash_purchases.dart में, DashPurchasesChangeNotifier के कोड पर जाएं. फ़िलहाल, खरीदे गए डैश में सिर्फ़ DashCounter जोड़ा जा सकता है.

स्ट्रीम की सदस्यता वाली प्रॉपर्टी, _subscription (टाइप StreamSubscription<List<PurchaseDetails>> _subscription;), IAPConnection.instance,, और इंपोर्ट जोड़ें. नतीजे में मिला कोड ऐसा दिखना चाहिए:

lib/logic/dash_purchases.dart

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';           // Add this import

import '../main.dart';                                           // And this import
import '../model/purchasable_product.dart';
import '../model/store_state.dart';
import 'dash_counter.dart';

class DashPurchases extends ChangeNotifier {
  DashCounter counter;
  StoreState storeState = StoreState.available;
  late StreamSubscription<List<PurchaseDetails>> _subscription;  // Add this line
  List<PurchasableProduct> products = [
    PurchasableProduct(
      'Spring is in the air',
      'Many dashes flying out from their nests',
      '\$0.99',
    ),
    PurchasableProduct(
      'Jet engine',
      'Doubles you clicks per second for a day',
      '\$1.99',
    ),
  ];

  bool get beautifiedDash => false;

  final iapConnection = IAPConnection.instance;                  // And this line

  DashPurchases(this.counter);

  Future<void> buy(PurchasableProduct product) async {
    product.status = ProductStatus.pending;
    notifyListeners();
    await Future<void>.delayed(const Duration(seconds: 5));
    product.status = ProductStatus.purchased;
    notifyListeners();
    await Future<void>.delayed(const Duration(seconds: 5));
    product.status = ProductStatus.purchasable;
    notifyListeners();
  }
}

late कीवर्ड को _subscription में जोड़ा गया है, क्योंकि _subscription को कंस्ट्रक्टर में शुरू किया गया है. इस प्रोजेक्ट को डिफ़ॉल्ट रूप से नॉन-नलेबल बाय डिफ़ॉल्ट (एनएनबीडी) के तौर पर सेट अप किया गया है. इसका मतलब है कि जिन प्रॉपर्टी को नलेबल के तौर पर एलान नहीं किया गया है उनकी वैल्यू नॉन-नल होनी चाहिए. late क्वालिफ़ायर की मदद से, इस वैल्यू को बाद में तय किया जा सकता है.

कंस्ट्रक्टर में, purchaseUpdated स्ट्रीम पाएं और स्ट्रीम सुनना शुरू करें. dispose() तरीके में, स्ट्रीम की सदस्यता रद्द करें.

lib/logic/dash_purchases.dart

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';

import '../main.dart';
import '../model/purchasable_product.dart';
import '../model/store_state.dart';
import 'dash_counter.dart';

class DashPurchases extends ChangeNotifier {
  DashCounter counter;
  StoreState storeState = StoreState.notAvailable;         // Modify this line
  late StreamSubscription<List<PurchaseDetails>> _subscription;
  List<PurchasableProduct> products = [
    PurchasableProduct(
      'Spring is in the air',
      'Many dashes flying out from their nests',
      '\$0.99',
    ),
    PurchasableProduct(
      'Jet engine',
      'Doubles you clicks per second for a day',
      '\$1.99',
    ),
  ];

  bool get beautifiedDash => false;

  final iapConnection = IAPConnection.instance;

  DashPurchases(this.counter) {                            // Add from here
    final purchaseUpdated = iapConnection.purchaseStream;
    _subscription = purchaseUpdated.listen(
      _onPurchaseUpdate,
      onDone: _updateStreamOnDone,
      onError: _updateStreamOnError,
    );
  }

  @override
  void dispose() {
    _subscription.cancel();
    super.dispose();
  }                                                        // To here.

  Future<void> buy(PurchasableProduct product) async {
    product.status = ProductStatus.pending;
    notifyListeners();
    await Future<void>.delayed(const Duration(seconds: 5));
    product.status = ProductStatus.purchased;
    notifyListeners();
    await Future<void>.delayed(const Duration(seconds: 5));
    product.status = ProductStatus.purchasable;
    notifyListeners();
  }
                                                           // Add from here
  void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
    // Handle purchases here
  }

  void _updateStreamOnDone() {
    _subscription.cancel();
  }

  void _updateStreamOnError(dynamic error) {
    //Handle error here
  }                                                        // To here.
}

अब ऐप्लिकेशन को खरीदारी से जुड़े अपडेट मिल रहे हैं. इसलिए, अगले सेक्शन में आपको खरीदारी करनी होगी!

आगे बढ़ने से पहले, "flutter test" का इस्तेमाल करके टेस्ट चलाएं. इससे यह पुष्टि की जा सकेगी कि सब कुछ सही तरीके से सेट अप किया गया है.

$ flutter test

00:01 +1: All tests passed!

8. ख़रीदारी करना

इस कोडलैब के इस हिस्से में, आपको मौजूदा मॉक प्रॉडक्ट को खरीदने के लिए उपलब्ध असली प्रॉडक्ट से बदलना होगा. ये प्रॉडक्ट, स्टोर से लोड किए जाते हैं और इन्हें सूची में दिखाया जाता है. इन प्रॉडक्ट पर टैप करके इन्हें खरीदा जाता है.

Adapt PurchasableProduct

PurchasableProduct में नकली प्रॉडक्ट दिखाया गया है. PurchasableProduct क्लास में मौजूद purchasable_product.dart को इस कोड से बदलकर, इसे अपडेट करें, ताकि असली कॉन्टेंट दिखाया जा सके:

lib/model/purchasable_product.dart

import 'package:in_app_purchase/in_app_purchase.dart';

enum ProductStatus { purchasable, purchased, pending }

class PurchasableProduct {
  String get id => productDetails.id;
  String get title => productDetails.title;
  String get description => productDetails.description;
  String get price => productDetails.price;
  ProductStatus status;
  ProductDetails productDetails;

  PurchasableProduct(this.productDetails) : status = ProductStatus.purchasable;
}

dash_purchases.dart, डमी खरीदारी हटाएं और उन्हें खाली सूची से बदलें List<PurchasableProduct> products = [];.

उपलब्ध खरीदारी लोड करें

किसी उपयोगकर्ता को खरीदारी करने की सुविधा देने के लिए, स्टोर से खरीदारी की जानकारी लोड करें. सबसे पहले, देखें कि स्टोर उपलब्ध है या नहीं. स्टोर उपलब्ध न होने पर, storeState को notAvailable पर सेट करने से, उपयोगकर्ता को गड़बड़ी का मैसेज दिखता है.

lib/logic/dash_purchases.dart

  Future<void> loadPurchases() async {
    final available = await iapConnection.isAvailable();
    if (!available) {
      storeState = StoreState.notAvailable;
      notifyListeners();
      return;
    }
  }

स्टोर उपलब्ध होने पर, खरीदारी के लिए उपलब्ध आइटम लोड करें. Google Play और App Store के पिछले सेटअप के हिसाब से, आपको storeKeyConsumable, storeKeySubscription,, और storeKeyUpgrade दिखेंगे. जब कोई खरीदारी उपलब्ध न हो, तो इस जानकारी को कंसोल पर प्रिंट करें. ऐसा भी हो सकता है कि आपको यह जानकारी बैकएंड सेवा को भेजनी हो.

await iapConnection.queryProductDetails(ids) तरीके से, न मिलने वाले आईडी और खरीदारी के लिए उपलब्ध प्रॉडक्ट, दोनों की जानकारी मिलती है. जवाब से मिले productDetails का इस्तेमाल करके, यूज़र इंटरफ़ेस (यूआई) को अपडेट करें. साथ ही, StoreState को available पर सेट करें.

lib/logic/dash_purchases.dart

import '../constants.dart';

// ...

  Future<void> loadPurchases() async {
    final available = await iapConnection.isAvailable();
    if (!available) {
      storeState = StoreState.notAvailable;
      notifyListeners();
      return;
    }
    const ids = <String>{
      storeKeyConsumable,
      storeKeySubscription,
      storeKeyUpgrade,
    };
    final response = await iapConnection.queryProductDetails(ids);
    products = response.productDetails
        .map((e) => PurchasableProduct(e))
        .toList();
    storeState = StoreState.available;
    notifyListeners();
  }

कंस्ट्रक्टर में loadPurchases() फ़ंक्शन को कॉल करें:

lib/logic/dash_purchases.dart

  DashPurchases(this.counter) {
    final purchaseUpdated = iapConnection.purchaseStream;
    _subscription = purchaseUpdated.listen(
      _onPurchaseUpdate,
      onDone: _updateStreamOnDone,
      onError: _updateStreamOnError,
    );
    loadPurchases();                                       // Add this line
  }

आखिर में, storeState फ़ील्ड की वैल्यू को StoreState.available से बदलकर StoreState.loading: करें

lib/logic/dash_purchases.dart

StoreState storeState = StoreState.loading;

खरीदे जा सकने वाले प्रॉडक्ट दिखाना

purchase_page.dart फ़ाइल को ध्यान में रखें. PurchasePage विजेट में, StoreState के हिसाब से _PurchasesLoading, _PurchaseList, या _PurchasesNotAvailable, दिखता है. यह विजेट, उपयोगकर्ता की पिछली खरीदारी की जानकारी भी दिखाता है. इसका इस्तेमाल अगले चरण में किया जाता है.

_PurchaseList विजेट, खरीदने के लिए उपलब्ध प्रॉडक्ट की सूची दिखाता है. साथ ही, DashPurchases ऑब्जेक्ट को खरीदारी का अनुरोध भेजता है.

lib/pages/purchase_page.dart

class _PurchaseList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var purchases = context.watch<DashPurchases>();
    var products = purchases.products;
    return Column(
      children: products
          .map(
            (product) => _PurchaseWidget(
              product: product,
              onPressed: () {
                purchases.buy(product);
              },
            ),
          )
          .toList(),
    );
  }
}

अगर प्रॉडक्ट ठीक से कॉन्फ़िगर किए गए हैं, तो आपको Android और iOS स्टोर पर उपलब्ध प्रॉडक्ट दिखेंगे. ध्यान दें कि खरीदारी की जानकारी को संबंधित कंसोल में डालने के बाद, उसे उपलब्ध होने में कुछ समय लग सकता है.

ca1a9f97c21e552d.png

dash_purchases.dart पर वापस जाएं और प्रॉडक्ट खरीदने के लिए फ़ंक्शन लागू करें. आपको सिर्फ़ इस्तेमाल की जाने वाली चीज़ों को, इस्तेमाल न की जाने वाली चीज़ों से अलग करना होगा. अपग्रेड और सदस्यता वाले प्रॉडक्ट, इस्तेमाल न किए जा सकने वाले प्रॉडक्ट हैं.

lib/logic/dash_purchases.dart

  Future<void> buy(PurchasableProduct product) async {
    final purchaseParam = PurchaseParam(productDetails: product.productDetails);
    switch (product.id) {
      case storeKeyConsumable:
        await iapConnection.buyConsumable(purchaseParam: purchaseParam);
      case storeKeySubscription:
      case storeKeyUpgrade:
        await iapConnection.buyNonConsumable(purchaseParam: purchaseParam);
      default:
        throw ArgumentError.value(
          product.productDetails,
          '${product.id} is not a known product',
        );
    }
  }

आगे बढ़ने से पहले, _beautifiedDashUpgrade वैरिएबल बनाएं और इसे रेफ़रंस करने के लिए, beautifiedDash गेटर को अपडेट करें.

lib/logic/dash_purchases.dart

  bool get beautifiedDash => _beautifiedDashUpgrade;
  bool _beautifiedDashUpgrade = false;

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

lib/logic/dash_purchases.dart

  Future<void> _onPurchaseUpdate(
    List<PurchaseDetails> purchaseDetailsList,
  ) async {
    for (var purchaseDetails in purchaseDetailsList) {
      await _handlePurchase(purchaseDetails);
    }
    notifyListeners();
  }

  Future<void> _handlePurchase(PurchaseDetails purchaseDetails) async {
    if (purchaseDetails.status == PurchaseStatus.purchased) {
      switch (purchaseDetails.productID) {
        case storeKeySubscription:
          counter.applyPaidMultiplier();
        case storeKeyConsumable:
          counter.addBoughtDashes(2000);
        case storeKeyUpgrade:
          _beautifiedDashUpgrade = true;
      }
    }

    if (purchaseDetails.pendingCompletePurchase) {
      await iapConnection.completePurchase(purchaseDetails);
    }
  }

9. बैकएंड सेट अप करना

खरीदारी को ट्रैक करने और उसकी पुष्टि करने से पहले, ऐसा करने के लिए Dart बैकएंड सेट अप करें.

इस सेक्शन में, dart-backend/ फ़ोल्डर को रूट के तौर पर इस्तेमाल करें.

पक्का करें कि आपने ये टूल इंस्टॉल किए हों:

बुनियादी प्रोजेक्ट की खास जानकारी

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

यह बैकएंड कोड, आपकी मशीन पर स्थानीय तौर पर चल सकता है. इसे इस्तेमाल करने के लिए, आपको इसे डिप्लॉय करने की ज़रूरत नहीं है. हालांकि, आपको अपने डेवलपमेंट डिवाइस (Android या iPhone) से उस मशीन से कनेक्ट करना होगा जिस पर सर्वर चलेगा. इसके लिए, दोनों डिवाइसों का एक ही नेटवर्क से कनेक्ट होना ज़रूरी है. साथ ही, आपको अपने डिवाइस का आईपी पता पता होना चाहिए.

इस कमांड का इस्तेमाल करके सर्वर चलाने की कोशिश करें:

$ dart ./bin/server.dart

Serving at http://0.0.0.0:8080

डार्ट बैकएंड, एपीआई एंडपॉइंट को दिखाने के लिए shelf और shelf_router का इस्तेमाल करता है. डिफ़ॉल्ट रूप से, सर्वर कोई भी रास्ता नहीं दिखाता है. बाद में, खरीदारी की पुष्टि करने की प्रोसेस को मैनेज करने के लिए, एक रूट बनाया जाएगा.

स्टार्टर कोड में पहले से शामिल एक हिस्सा, IapRepository में lib/iap_repository.dart है. Firestore या डेटाबेस के साथ इंटरैक्ट करने का तरीका सीखना, इस कोडलैब के लिए ज़रूरी नहीं है. इसलिए, स्टार्टर कोड में ऐसे फ़ंक्शन शामिल हैं जिनकी मदद से, Firestore में खरीदारी की जानकारी बनाई या अपडेट की जा सकती है. साथ ही, इसमें उन खरीदारी के लिए सभी क्लास भी शामिल हैं.

Firebase का ऐक्सेस सेट अप करना

Firebase Firestore को ऐक्सेस करने के लिए, आपको सेवा खाते की ऐक्सेस कुंजी की ज़रूरत होगी. इसे जनरेट करने के लिए, Firebase प्रोजेक्ट की सेटिंग खोलें और सेवा खाते सेक्शन पर जाएं. इसके बाद, नया निजी पासकोड जनरेट करें चुनें.

27590fc77ae94ad4.png

डाउनलोड की गई JSON फ़ाइल को assets/ फ़ोल्डर में कॉपी करें और उसका नाम बदलकर service-account-firebase.json करें.

Google Play का ऐक्सेस सेट अप करना

खरीदारियों की पुष्टि करने के लिए Play Store को ऐक्सेस करने के लिए, आपको इन अनुमतियों के साथ एक सेवा खाता जनरेट करना होगा. साथ ही, इसके लिए JSON क्रेडेंशियल डाउनलोड करने होंगे.

  1. Google Cloud Console में, Google Play Android Developer API पेज पर जाएं. 629f0bd8e6b50be8.png अगर Google Play Console आपसे कोई नया प्रोजेक्ट बनाने या किसी मौजूदा प्रोजेक्ट से लिंक करने का अनुरोध करता है, तो पहले ऐसा करें. इसके बाद, इस पेज पर वापस आएं.
  2. इसके बाद, सेवा खाते वाले पेज पर जाएं और + सेवा खाता बनाएं पर क्लिक करें. 8dc97e3b1262328a.png
  3. सेवा खाते का नाम डालें और बनाएं और जारी रखें पर क्लिक करें. 4fe8106af85ce75f.png
  4. Pub/Sub सब्सक्राइबर की भूमिका चुनें और हो गया पर क्लिक करें. a5b6fa6ea8ee22d.png
  5. खाता बन जाने के बाद, कुंजियां मैनेज करें पर जाएं. eb36da2c1ad6dd06.png
  6. कुंजी जोड़ें > नई कुंजी बनाएं चुनें. e92db9557a28a479.png
  7. JSON कुंजी बनाएं और डाउनलोड करें. 711d04f2f4176333.png
  8. डाउनलोड की गई फ़ाइल का नाम बदलकर service-account-google-play.json, करें और उसे assets/ डायरेक्ट्री में ले जाएं.
  9. इसके बाद, Play Console में उपयोगकर्ता और अनुमतियां पेज पर जाएं28fffbfc35b45f97.png
  10. नए उपयोगकर्ताओं को न्योता भेजें पर क्लिक करें. इसके बाद, पहले बनाए गए सेवा खाते का ईमेल पता डालें. आपको यह ईमेल, सेवा खाते वाले पेज पर मौजूद टेबल में मिल सकता हैe3310cc077f397d.png
  11. ऐप्लिकेशन के लिए, वित्तीय डेटा देखें और ऑर्डर और सदस्यताएं मैनेज करें अनुमतियां दें. a3b8cf2b660d1900.png
  12. उपयोगकर्ता को न्योता भेजें पर क्लिक करें.

हमें एक और काम करना है. lib/constants.dart, खोलें और androidPackageId की वैल्यू को उस पैकेज आईडी से बदलें जिसे आपने अपने Android ऐप्लिकेशन के लिए चुना है.

Apple App Store का ऐक्सेस सेट अप करना

खरीदारियों की पुष्टि करने के लिए App Store को ऐक्सेस करने के लिए, आपको शेयर किया गया सीक्रेट सेट अप करना होगा:

  1. App Store Connect खोलें.
  2. मेरे ऐप्लिकेशन पर जाएं और अपना ऐप्लिकेशन चुनें.
  3. साइडबार नेविगेशन में, सामान्य > ऐप्लिकेशन की जानकारी पर जाएं.
  4. ऐप्लिकेशन के हिसाब से शेयर किया गया सीक्रेट हेडर में जाकर, मैनेज करें पर क्लिक करें. ad419782c5fbacb2.png
  5. नया सीक्रेट जनरेट करें और उसे कॉपी करें. b5b72a357459b0e5.png
  6. lib/constants.dart, खोलें और appStoreSharedSecret की वैल्यू को, अभी जनरेट किए गए शेयर किए गए सीक्रेट से बदलें.

कॉन्स्टैंट कॉन्फ़िगरेशन फ़ाइल

आगे बढ़ने से पहले, पक्का करें कि lib/constants.dart फ़ाइल में ये कॉन्स्टेंट कॉन्फ़िगर किए गए हों:

  • androidPackageId: Android पर इस्तेमाल किया गया पैकेज आईडी, जैसे कि com.example.dashclicker
  • appStoreSharedSecret: खरीदारी की पुष्टि करने के लिए, App Store Connect को ऐक्सेस करने का शेयर किया गया सीक्रेट.
  • bundleId: iOS पर इस्तेमाल किया गया बंडल आईडी, जैसे कि com.example.dashclicker

फ़िलहाल, अन्य सभी कॉन्स्टेंट को अनदेखा किया जा सकता है.

10. खरीदारियों की पुष्टि करना

खरीदारी की पुष्टि करने का सामान्य फ़्लो, iOS और Android के लिए एक जैसा होता है.

दोनों स्टोर के लिए, खरीदारी होने पर आपके ऐप्लिकेशन को एक टोकन मिलता है.

इस टोकन को ऐप्लिकेशन, आपकी बैकएंड सेवा को भेजता है. इसके बाद, यह सेवा दिए गए टोकन का इस्तेमाल करके, संबंधित स्टोर के सर्वर से खरीदारी की पुष्टि करती है.

इसके बाद, बैकएंड सेवा खरीदारी को सेव कर सकती है. साथ ही, ऐप्लिकेशन को यह बता सकती है कि खरीदारी मान्य है या नहीं.

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

Flutter साइड को सेट अप करना

पुष्टि करने की सुविधा सेट अप करना

आपको खरीदारी की जानकारी अपनी बैकएंड सेवा को भेजनी है. इसलिए, आपको यह पक्का करना होगा कि खरीदारी करते समय उपयोगकर्ता की पुष्टि हो गई हो. स्टार्टर प्रोजेक्ट में, पुष्टि करने से जुड़ा ज़्यादातर लॉजिक पहले से ही जोड़ दिया गया है. आपको बस यह पक्का करना है कि जब उपयोगकर्ता ने लॉग इन न किया हो, तब PurchasePage में लॉगिन बटन दिखे. PurchasePage की build मेथड की शुरुआत में यह कोड जोड़ें:

lib/pages/purchase_page.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../logic/dash_purchases.dart';
import '../logic/firebase_notifier.dart';                  // Add this import
import '../model/firebase_state.dart';                     // And this import
import '../model/purchasable_product.dart';
import '../model/store_state.dart';
import '../repo/iap_repo.dart';
import 'login_page.dart';                                  // And this one as well

class PurchasePage extends StatelessWidget {
  const PurchasePage({super.key});

  @override
  Widget build(BuildContext context) {                     // Update from here
    var firebaseNotifier = context.watch<FirebaseNotifier>();
    if (firebaseNotifier.state == FirebaseState.loading) {
      return _PurchasesLoading();
    } else if (firebaseNotifier.state == FirebaseState.notAvailable) {
      return _PurchasesNotAvailable();
    }

    if (!firebaseNotifier.loggedIn) {
      return const LoginPage();
    }                                                      // To here.

    // ...

ऐप्लिकेशन से कॉल की पुष्टि करने वाले एंडपॉइंट को कॉल करना

ऐप्लिकेशन में, _verifyPurchase(PurchaseDetails purchaseDetails) फ़ंक्शन बनाएं. यह http पोस्ट कॉल का इस्तेमाल करके, आपके Dart बैकएंड पर मौजूद /verifypurchase एंडपॉइंट को कॉल करता है.

चुने गए स्टोर (Play Store के लिए google_play या App Store के लिए app_store), serverVerificationData, और productID भेजें. सर्वर, स्टेटस कोड दिखाता है. इससे पता चलता है कि खरीदारी की पुष्टि हुई है या नहीं.

ऐप्लिकेशन कॉन्स्टेंट में, सर्वर आईपी को अपने कंप्यूटर के आईपी पते पर कॉन्फ़िगर करें.

lib/logic/dash_purchases.dart

import 'dart:async';
import 'dart:convert';                                     // Add this import

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;                   // And this import
import 'package:in_app_purchase/in_app_purchase.dart';

import '../constants.dart';
import '../main.dart';
import '../model/purchasable_product.dart';
import '../model/store_state.dart';
import 'dash_counter.dart';
import 'firebase_notifier.dart';                           // And this one

class DashPurchases extends ChangeNotifier {
  DashCounter counter;
  FirebaseNotifier firebaseNotifier;                       // Add this line
  StoreState storeState = StoreState.loading;
  late StreamSubscription<List<PurchaseDetails>> _subscription;
  List<PurchasableProduct> products = [];

  bool get beautifiedDash => _beautifiedDashUpgrade;
  bool _beautifiedDashUpgrade = false;

  final iapConnection = IAPConnection.instance;

  DashPurchases(this.counter, this.firebaseNotifier) {     // Update this line
    final purchaseUpdated = iapConnection.purchaseStream;
    _subscription = purchaseUpdated.listen(
      _onPurchaseUpdate,
      onDone: _updateStreamOnDone,
      onError: _updateStreamOnError,
    );
    loadPurchases();
  }

main.dart: में DashPurchases बनाते समय firebaseNotifier जोड़ना

lib/main.dart

        ChangeNotifierProvider<DashPurchases>(
          create: (context) => DashPurchases(
            context.read<DashCounter>(),
            context.read<FirebaseNotifier>(),
          ),
          lazy: false,
        ),

FirebaseNotifier में उपयोगकर्ता के लिए एक गेटर जोड़ें, ताकि खरीदारी की पुष्टि करने वाले फ़ंक्शन को User-ID पास किया जा सके.

lib/logic/firebase_notifier.dart

  Future<FirebaseFirestore> get firestore async {
    var isInitialized = await _isInitialized.future;
    if (!isInitialized) {
      throw Exception('Firebase is not initialized');
    }
    return FirebaseFirestore.instance;
  }

  User? get user => FirebaseAuth.instance.currentUser;     // Add this line

  Future<void> load() async {
    // ...

DashPurchases क्लास में _verifyPurchase फ़ंक्शन जोड़ें. यह async फ़ंक्शन, बूलियन वैल्यू दिखाता है. इससे पता चलता है कि खरीदारी की पुष्टि हुई है या नहीं.

lib/logic/dash_purchases.dart

  Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) async {
    final url = Uri.parse('http://$serverIp:8080/verifypurchase');
    const headers = {
      'Content-type': 'application/json',
      'Accept': 'application/json',
    };
    final response = await http.post(
      url,
      body: jsonEncode({
        'source': purchaseDetails.verificationData.source,
        'productId': purchaseDetails.productID,
        'verificationData':
            purchaseDetails.verificationData.serverVerificationData,
        'userId': firebaseNotifier.user?.uid,
      }),
      headers: headers,
    );
    if (response.statusCode == 200) {
      return true;
    } else {
      return false;
    }
  }

खरीदारी लागू करने से ठीक पहले, _handlePurchase में _verifyPurchase फ़ंक्शन को कॉल करें. खरीदारी की पुष्टि हो जाने के बाद ही, आपको उसे लागू करना चाहिए. प्रोडक्शन ऐप्लिकेशन में, इसे और भी बेहतर तरीके से तय किया जा सकता है. उदाहरण के लिए, जब स्टोर कुछ समय के लिए उपलब्ध न हो, तब बिना किसी शुल्क के आज़माने की सदस्यता लागू की जा सकती है. हालांकि, इस उदाहरण के लिए, खरीदारी की पुष्टि होने के बाद ही खरीदारी को लागू करें.

lib/logic/dash_purchases.dart

  Future<void> _onPurchaseUpdate(
    List<PurchaseDetails> purchaseDetailsList,
  ) async {
    for (var purchaseDetails in purchaseDetailsList) {
      await _handlePurchase(purchaseDetails);
    }
    notifyListeners();
  }

  Future<void> _handlePurchase(PurchaseDetails purchaseDetails) async {
    if (purchaseDetails.status == PurchaseStatus.purchased) {
      // Send to server
      var validPurchase = await _verifyPurchase(purchaseDetails);

      if (validPurchase) {
        // Apply changes locally
        switch (purchaseDetails.productID) {
          case storeKeySubscription:
            counter.applyPaidMultiplier();
          case storeKeyConsumable:
            counter.addBoughtDashes(2000);
          case storeKeyUpgrade:
            _beautifiedDashUpgrade = true;
        }
      }
    }

    if (purchaseDetails.pendingCompletePurchase) {
      await iapConnection.completePurchase(purchaseDetails);
    }
  }

अब ऐप्लिकेशन में, खरीदारी की पुष्टि करने के लिए सभी ज़रूरी चीज़ें उपलब्ध हैं.

बैकएंड सेवा सेट अप करना

इसके बाद, बैकएंड पर खरीदारी की पुष्टि करने के लिए, बैकएंड सेट अप करें.

खरीदारी के हैंडलर बनाना

दोनों स्टोर के लिए पुष्टि करने का तरीका लगभग एक जैसा है. इसलिए, एक ऐब्स्ट्रैक्ट PurchaseHandler क्लास सेट अप करें. इसमें हर स्टोर के लिए अलग-अलग तरीके से लागू करने की सुविधा हो.

be50c207c5a2a519.png

शुरुआत में, lib/ फ़ोल्डर में purchase_handler.dart फ़ाइल जोड़ें. इसमें, आपको एक ऐब्स्ट्रैक्ट PurchaseHandler क्लास तय करनी होगी. इसमें दो ऐब्स्ट्रैक्ट तरीके होंगे. इनका इस्तेमाल दो तरह की खरीदारी की पुष्टि करने के लिए किया जाएगा: सदस्यताएं और बिना सदस्यता वाली खरीदारी.

lib/purchase_handler.dart

import 'products.dart';

/// Generic purchase handler,
/// must be implemented for Google Play and Apple Store
abstract class PurchaseHandler {
  /// Verify if non-subscription purchase (aka consumable) is valid
  /// and update the database
  Future<bool> handleNonSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  });

  /// Verify if subscription purchase (aka non-consumable) is valid
  /// and update the database
  Future<bool> handleSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  });
}

जैसा कि आप देख सकते हैं, हर तरीके के लिए तीन पैरामीटर ज़रूरी हैं:

  • userId: लॉग इन किए हुए उपयोगकर्ता का आईडी, ताकि खरीदारी को उपयोगकर्ता से जोड़ा जा सके.
  • productData: प्रॉडक्ट के बारे में डेटा. आपको इसे एक मिनट में तय करना है.
  • token: यह टोकन, स्टोर ने उपयोगकर्ता को दिया है.

इसके अलावा, खरीदारी के इन हैंडलर को इस्तेमाल करने में आसानी हो, इसके लिए एक verifyPurchase() तरीका जोड़ें. इसका इस्तेमाल सदस्यता और बिना सदस्यता वाली खरीदारी, दोनों के लिए किया जा सकता है:

lib/purchase_handler.dart

  /// Verify if purchase is valid and update the database
  Future<bool> verifyPurchase({
    required String userId,
    required ProductData productData,
    required String token,
  }) async {
    switch (productData.type) {
      case ProductType.subscription:
        return handleSubscription(
          userId: userId,
          productData: productData,
          token: token,
        );
      case ProductType.nonSubscription:
        return handleNonSubscription(
          userId: userId,
          productData: productData,
          token: token,
        );
    }
  }

अब दोनों मामलों के लिए, सिर्फ़ verifyPurchase को कॉल किया जा सकता है. हालांकि, अब भी अलग-अलग तरीके से लागू किया जा सकता है!

ProductData क्लास में, खरीदने के लिए उपलब्ध अलग-अलग प्रॉडक्ट के बारे में बुनियादी जानकारी होती है. इसमें प्रॉडक्ट आईडी (कभी-कभी इसे एसकेयू भी कहा जाता है) और ProductType शामिल होता है.

lib/products.dart

class ProductData {
  final String productId;
  final ProductType type;

  const ProductData(this.productId, this.type);
}

ProductType, सदस्यता या बिना सदस्यता के इस्तेमाल किया जा सकता है.

lib/products.dart

enum ProductType { subscription, nonSubscription }

आखिर में, प्रॉडक्ट की सूची को उसी फ़ाइल में मैप के तौर पर तय किया जाता है.

lib/products.dart

const productDataMap = {
  'dash_consumable_2k': ProductData(
    'dash_consumable_2k',
    ProductType.nonSubscription,
  ),
  'dash_upgrade_3d': ProductData(
    'dash_upgrade_3d',
    ProductType.nonSubscription,
  ),
  'dash_subscription_doubler': ProductData(
    'dash_subscription_doubler',
    ProductType.subscription,
  ),
};

इसके बाद, Google Play Store और Apple App Store के लिए कुछ प्लेसहोल्डर लागू करें. Google Play से शुरुआत करें:

lib/google_play_purchase_handler.dart बनाएं और एक ऐसी क्लास जोड़ें जो अभी-अभी लिखे गए PurchaseHandler को बढ़ाती है:

lib/google_play_purchase_handler.dart

import 'dart:async';

import 'package:googleapis/androidpublisher/v3.dart' as ap;

import 'constants.dart';
import 'iap_repository.dart';
import 'products.dart';
import 'purchase_handler.dart';

class GooglePlayPurchaseHandler extends PurchaseHandler {
  final ap.AndroidPublisherApi androidPublisher;
  final IapRepository iapRepository;

  GooglePlayPurchaseHandler(this.androidPublisher, this.iapRepository);

  @override
  Future<bool> handleNonSubscription({
    required String? userId,
    required ProductData productData,
    required String token,
  }) async {
    return true;
  }

  @override
  Future<bool> handleSubscription({
    required String? userId,
    required ProductData productData,
    required String token,
  }) async {
    return true;
  }
}

फ़िलहाल, यह हैंडलर के तरीकों के लिए true दिखाता है. आपको इनके बारे में बाद में पता चलेगा.

जैसा कि आपने देखा होगा, कंस्ट्रक्टर IapRepository का एक इंस्टेंस लेता है. खरीदारी हैंडलर इस इंस्टेंस का इस्तेमाल, बाद में Firestore में खरीदारी के बारे में जानकारी सेव करने के लिए करता है. Google Play से कम्यूनिकेट करने के लिए, आपको AndroidPublisherApi का इस्तेमाल करना होगा.

इसके बाद, ऐप्लिकेशन स्टोर हैंडलर के लिए भी ऐसा ही करें. lib/app_store_purchase_handler.dart बनाएं और PurchaseHandler को फिर से बढ़ाने वाली क्लास जोड़ें:

lib/app_store_purchase_handler.dart

import 'dart:async';

import 'package:app_store_server_sdk/app_store_server_sdk.dart';

import 'constants.dart';
import 'iap_repository.dart';
import 'products.dart';
import 'purchase_handler.dart';

class AppStorePurchaseHandler extends PurchaseHandler {
  final IapRepository iapRepository;

  AppStorePurchaseHandler(this.iapRepository);

  @override
  Future<bool> handleNonSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  }) async {
    return true;
  }

  @override
  Future<bool> handleSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  }) async {
    return true;
  }
}

बढ़िया! अब आपके पास खरीदारी के दो हैंडलर हैं. इसके बाद, खरीदारी की पुष्टि करने वाले एपीआई एंडपॉइंट को बनाएं.

परचेज़ हैंडलर का इस्तेमाल करना

bin/server.dart खोलें और shelf_route का इस्तेमाल करके एपीआई एंडपॉइंट बनाएं:

bin/server.dart

import 'dart:convert';

import 'package:firebase_backend_dart/helpers.dart';
import 'package:firebase_backend_dart/products.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';

Future<void> main() async {
  final router = Router();

  final purchaseHandlers = await _createPurchaseHandlers();

  router.post('/verifypurchase', (Request request) async {
    final dynamic payload = json.decode(await request.readAsString());

    final (:userId, :source, :productData, :token) = getPurchaseData(payload);

    final result = await purchaseHandlers[source]!.verifyPurchase(
      userId: userId,
      productData: productData,
      token: token,
    );

    if (result) {
      return Response.ok('all good!');
    } else {
      return Response.internalServerError();
    }
  });

  await serveHandler(router.call);
}

({String userId, String source, ProductData productData, String token})
getPurchaseData(dynamic payload) {
  if (payload case {
    'userId': String userId,
    'source': String source,
    'productId': String productId,
    'verificationData': String token,
  }) {
    return (
      userId: userId,
      source: source,
      productData: productDataMap[productId]!,
      token: token,
    );
  } else {
    throw const FormatException('Unexpected JSON');
  }
}

यह कोड ये काम कर रहा है:

  1. एक POST एंडपॉइंट तय करें, जिसे पहले बनाए गए ऐप्लिकेशन से कॉल किया जाएगा.
  2. JSON पेलोड को डिकोड करें और यह जानकारी निकालें:
    1. userId: लॉग इन किए गए उपयोगकर्ता का आईडी
    2. source: इस्तेमाल किया गया स्टोर, app_store या google_play.
    3. productData: यह जानकारी, पहले से बनाए गए productDataMap से ली जाती है.
    4. token: इसमें पुष्टि करने से जुड़ा डेटा होता है, जिसे स्टोर को भेजा जाता है.
  3. सोर्स के आधार पर, GooglePlayPurchaseHandler या AppStorePurchaseHandler के लिए verifyPurchase वाले तरीके को कॉल करें.
  4. अगर पुष्टि हो जाती है, तो यह तरीका क्लाइंट को Response.ok दिखाता है.
  5. अगर पुष्टि नहीं हो पाती है, तो यह तरीका क्लाइंट को Response.internalServerError दिखाता है.

एपीआई एंडपॉइंट बनाने के बाद, आपको खरीदारी के दो हैंडलर कॉन्फ़िगर करने होंगे. इसके लिए, आपको पिछले चरण में मिली सेवा खाते की कुंजियां लोड करनी होंगी. साथ ही, Android Publisher API और Firebase Firestore API जैसी अलग-अलग सेवाओं के लिए ऐक्सेस कॉन्फ़िगर करना होगा. इसके बाद, अलग-अलग डिपेंडेंसी के साथ दो परचेज़ हैंडलर बनाएं:

bin/server.dart

import 'dart:convert';
import 'dart:io'; // new

import 'package:firebase_backend_dart/app_store_purchase_handler.dart'; // new
import 'package:firebase_backend_dart/google_play_purchase_handler.dart'; // new
import 'package:firebase_backend_dart/helpers.dart';
import 'package:firebase_backend_dart/iap_repository.dart'; // new
import 'package:firebase_backend_dart/products.dart';
import 'package:firebase_backend_dart/purchase_handler.dart'; // new
import 'package:googleapis/androidpublisher/v3.dart' as ap; // new
import 'package:googleapis/firestore/v1.dart' as fs; // new
import 'package:googleapis_auth/auth_io.dart' as auth; // new
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';

Future<Map<String, PurchaseHandler>> _createPurchaseHandlers() async {
  // Configure Android Publisher API access
  final serviceAccountGooglePlay =
      File('assets/service-account-google-play.json').readAsStringSync();
  final clientCredentialsGooglePlay =
      auth.ServiceAccountCredentials.fromJson(serviceAccountGooglePlay);
  final clientGooglePlay =
      await auth.clientViaServiceAccount(clientCredentialsGooglePlay, [
    ap.AndroidPublisherApi.androidpublisherScope,
  ]);
  final androidPublisher = ap.AndroidPublisherApi(clientGooglePlay);

  // Configure Firestore API access
  final serviceAccountFirebase =
      File('assets/service-account-firebase.json').readAsStringSync();
  final clientCredentialsFirebase =
      auth.ServiceAccountCredentials.fromJson(serviceAccountFirebase);
  final clientFirebase =
      await auth.clientViaServiceAccount(clientCredentialsFirebase, [
    fs.FirestoreApi.cloudPlatformScope,
  ]);
  final firestoreApi = fs.FirestoreApi(clientFirebase);
  final dynamic json = jsonDecode(serviceAccountFirebase);
  final projectId = json['project_id'] as String;
  final iapRepository = IapRepository(firestoreApi, projectId);

  return {
    'google_play': GooglePlayPurchaseHandler(
      androidPublisher,
      iapRepository,
    ),
    'app_store': AppStorePurchaseHandler(
      iapRepository,
    ),
  };
}

Android पर की गई खरीदारी की पुष्टि करना: खरीदारी के हैंडलर को लागू करना

इसके बाद, Google Play पर खरीदारी करने वाले हैंडलर को लागू करना जारी रखें.

Google पहले से ही Dart पैकेज उपलब्ध कराता है. इनकी मदद से, उन एपीआई के साथ इंटरैक्ट किया जा सकता है जिनसे खरीदारी की पुष्टि करनी होती है. आपने उन्हें server.dart फ़ाइल में शुरू किया है और अब उनका इस्तेमाल GooglePlayPurchaseHandler क्लास में किया जा रहा है.

बिना सदस्यता वाली खरीदारी के लिए हैंडलर लागू करें:

lib/google_play_purchase_handler.dart

  /// Handle non-subscription purchases (one time purchases).
  ///
  /// Retrieves the purchase status from Google Play and updates
  /// the Firestore Database accordingly.
  @override
  Future<bool> handleNonSubscription({
    required String? userId,
    required ProductData productData,
    required String token,
  }) async {
    print(
      'GooglePlayPurchaseHandler.handleNonSubscription'
      '($userId, ${productData.productId}, ${token.substring(0, 5)}...)',
    );

    try {
      // Verify purchase with Google
      final response = await androidPublisher.purchases.products.get(
        androidPackageId,
        productData.productId,
        token,
      );

      print('Purchases response: ${response.toJson()}');

      // Make sure an order ID exists
      if (response.orderId == null) {
        print('Could not handle purchase without order id');
        return false;
      }
      final orderId = response.orderId!;

      final purchaseData = NonSubscriptionPurchase(
        purchaseDate: DateTime.fromMillisecondsSinceEpoch(
          int.parse(response.purchaseTimeMillis ?? '0'),
        ),
        orderId: orderId,
        productId: productData.productId,
        status: _nonSubscriptionStatusFrom(response.purchaseState),
        userId: userId,
        iapSource: IAPSource.googleplay,
      );

      // Update the database
      if (userId != null) {
        // If we know the userId,
        // update the existing purchase or create it if it does not exist.
        await iapRepository.createOrUpdatePurchase(purchaseData);
      } else {
        // If we don't know the user ID, a previous entry must already
        // exist, and thus we'll only update it.
        await iapRepository.updatePurchase(purchaseData);
      }
      return true;
    } on ap.DetailedApiRequestError catch (e) {
      print(
        'Error on handle NonSubscription: $e\n'
        'JSON: ${e.jsonResponse}',
      );
    } catch (e) {
      print('Error on handle NonSubscription: $e\n');
    }
    return false;
  }

सदस्यता खरीदने के हैंडलर को इसी तरह अपडेट किया जा सकता है:

lib/google_play_purchase_handler.dart

  /// Handle subscription purchases.
  ///
  /// Retrieves the purchase status from Google Play and updates
  /// the Firestore Database accordingly.
  @override
  Future<bool> handleSubscription({
    required String? userId,
    required ProductData productData,
    required String token,
  }) async {
    print(
      'GooglePlayPurchaseHandler.handleSubscription'
      '($userId, ${productData.productId}, ${token.substring(0, 5)}...)',
    );

    try {
      // Verify purchase with Google
      final response = await androidPublisher.purchases.subscriptions.get(
        androidPackageId,
        productData.productId,
        token,
      );

      print('Subscription response: ${response.toJson()}');

      // Make sure an order ID exists
      if (response.orderId == null) {
        print('Could not handle purchase without order id');
        return false;
      }
      final orderId = extractOrderId(response.orderId!);

      final purchaseData = SubscriptionPurchase(
        purchaseDate: DateTime.fromMillisecondsSinceEpoch(
          int.parse(response.startTimeMillis ?? '0'),
        ),
        orderId: orderId,
        productId: productData.productId,
        status: _subscriptionStatusFrom(response.paymentState),
        userId: userId,
        iapSource: IAPSource.googleplay,
        expiryDate: DateTime.fromMillisecondsSinceEpoch(
          int.parse(response.expiryTimeMillis ?? '0'),
        ),
      );

      // Update the database
      if (userId != null) {
        // If we know the userId,
        // update the existing purchase or create it if it does not exist.
        await iapRepository.createOrUpdatePurchase(purchaseData);
      } else {
        // If we don't know the user ID, a previous entry must already
        // exist, and thus we'll only update it.
        await iapRepository.updatePurchase(purchaseData);
      }
      return true;
    } on ap.DetailedApiRequestError catch (e) {
      print(
        'Error on handle Subscription: $e\n'
        'JSON: ${e.jsonResponse}',
      );
    } catch (e) {
      print('Error on handle Subscription: $e\n');
    }
    return false;
  }
}

ऑर्डर आईडी को पार्स करने के लिए, यहां दिया गया तरीका जोड़ें. साथ ही, खरीदारी की स्थिति को पार्स करने के लिए दो तरीके जोड़ें.

lib/google_play_purchase_handler.dart

NonSubscriptionStatus _nonSubscriptionStatusFrom(int? state) {
  return switch (state) {
    0 => NonSubscriptionStatus.completed,
    2 => NonSubscriptionStatus.pending,
    _ => NonSubscriptionStatus.cancelled,
  };
}

SubscriptionStatus _subscriptionStatusFrom(int? state) {
  return switch (state) {
    // Payment pending
    0 => SubscriptionStatus.pending,
    // Payment received
    1 => SubscriptionStatus.active,
    // Free trial
    2 => SubscriptionStatus.active,
    // Pending deferred upgrade/downgrade
    3 => SubscriptionStatus.pending,
    // Expired or cancelled
    _ => SubscriptionStatus.expired,
  };
}

/// If a subscription suffix is present (..#) extract the orderId.
String extractOrderId(String orderId) {
  final orderIdSplit = orderId.split('..');
  if (orderIdSplit.isNotEmpty) {
    orderId = orderIdSplit[0];
  }
  return orderId;
}

अब Google Play पर की गई आपकी खरीदारी की पुष्टि हो गई होगी और उसे डेटाबेस में सेव कर लिया गया होगा.

इसके बाद, iOS के लिए App Store से की गई खरीदारी पर जाएं.

iOS पर की गई खरीदारी की पुष्टि करना: खरीदारी हैंडलर लागू करना

App Store से की गई खरीदारी की पुष्टि करने के लिए, तीसरे पक्ष का एक Dart पैकेज उपलब्ध है. इसका नाम app_store_server_sdk है. इससे पुष्टि करने की प्रोसेस आसान हो जाती है.

ITunesApi इंस्टेंस बनाकर शुरू करें. सैंडबॉक्स कॉन्फ़िगरेशन का इस्तेमाल करें. साथ ही, गड़बड़ी को डीबग करने के लिए लॉगिंग की सुविधा चालू करें.

lib/app_store_purchase_handler.dart

  final _iTunesAPI = ITunesApi(
    ITunesHttpClient(ITunesEnvironment.sandbox(), loggingEnabled: true),
  );

अब Google Play API के उलट, App Store सदस्यता और बिना सदस्यता वाले दोनों ऐप्लिकेशन के लिए एक ही एपीआई एंडपॉइंट का इस्तेमाल करता है. इसका मतलब है कि दोनों हैंडलर के लिए एक ही लॉजिक का इस्तेमाल किया जा सकता है. इन्हें एक साथ मर्ज करें, ताकि ये एक ही तरीके से लागू हों:

lib/app_store_purchase_handler.dart

  @override
  Future<bool> handleNonSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  }) {
    return handleValidation(userId: userId, token: token);
  }

  @override
  Future<bool> handleSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  }) {
    return handleValidation(userId: userId, token: token);
  }

  /// Handle purchase validation.
  Future<bool> handleValidation({
    required String userId,
    required String token,
  }) async {

    // See next step
  }

अब, handleValidation लागू करें:

lib/app_store_purchase_handler.dart

  /// Handle purchase validation.
  Future<bool> handleValidation({
    required String userId,
    required String token,
  }) async {
    print('AppStorePurchaseHandler.handleValidation');
    final response = await _iTunesAPI.verifyReceipt(
      password: appStoreSharedSecret,
      receiptData: token,
    );
    print('response: $response');
    if (response.status == 0) {
      print('Successfully verified purchase');
      final receipts = response.latestReceiptInfo ?? [];
      for (final receipt in receipts) {
        final product = productDataMap[receipt.productId];
        if (product == null) {
          print('Error: Unknown product: ${receipt.productId}');
          continue;
        }
        switch (product.type) {
          case ProductType.nonSubscription:
            await iapRepository.createOrUpdatePurchase(
              NonSubscriptionPurchase(
                userId: userId,
                productId: receipt.productId ?? '',
                iapSource: IAPSource.appstore,
                orderId: receipt.originalTransactionId ?? '',
                purchaseDate: DateTime.fromMillisecondsSinceEpoch(
                  int.parse(receipt.originalPurchaseDateMs ?? '0'),
                ),
                type: product.type,
                status: NonSubscriptionStatus.completed,
              ),
            );
            break;
          case ProductType.subscription:
            await iapRepository.createOrUpdatePurchase(
              SubscriptionPurchase(
                userId: userId,
                productId: receipt.productId ?? '',
                iapSource: IAPSource.appstore,
                orderId: receipt.originalTransactionId ?? '',
                purchaseDate: DateTime.fromMillisecondsSinceEpoch(
                  int.parse(receipt.originalPurchaseDateMs ?? '0'),
                ),
                type: product.type,
                expiryDate: DateTime.fromMillisecondsSinceEpoch(
                  int.parse(receipt.expiresDateMs ?? '0'),
                ),
                status: SubscriptionStatus.active,
              ),
            );
            break;
        }
      }
      return true;
    } else {
      print('Error: Status: ${response.status}');
      return false;
    }
  }

अब App Store से की गई आपकी खरीदारी की पुष्टि हो गई होगी और इसे डेटाबेस में सेव कर लिया गया होगा!

बैकएंड चलाना

इस चरण में, /verifypurchase एंडपॉइंट को दिखाने के लिए, dart bin/server.dart को चलाया जा सकता है.

$ dart bin/server.dart
Serving at http://0.0.0.0:8080

11. खरीदारी का हिसाब रखना

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

सबसे पहले, Dart बैकएंड की मदद से बैकएंड पर स्टोर इवेंट को प्रोसेस करने की सुविधा सेट अप करें.

बैकएंड पर स्टोर इवेंट प्रोसेस करना

स्टोर, आपके बैकएंड को बिलिंग से जुड़े किसी भी इवेंट के बारे में सूचना दे सकते हैं. जैसे, सदस्यता के रिन्यू होने पर. इन इवेंट को अपने बैकएंड में प्रोसेस किया जा सकता है, ताकि आपके डेटाबेस में खरीदारी की जानकारी अप-टू-डेट रहे. इस सेक्शन में, Google Play Store और Apple App Store, दोनों के लिए इसे सेट अप करें.

Google Play के बिलिंग इवेंट प्रोसेस करना

Google Play, बिलिंग इवेंट की जानकारी देता है. इसके लिए, वह Cloud Pub/Sub टॉपिक का इस्तेमाल करता है. ये मैसेज कतारें होती हैं. इन पर मैसेज पब्लिश किए जा सकते हैं. साथ ही, इनसे मैसेज इस्तेमाल किए जा सकते हैं.

यह सुविधा सिर्फ़ Google Play पर उपलब्ध है. इसलिए, आपको इसे GooglePlayPurchaseHandler में शामिल करना होगा.

शुरू करने के लिए, lib/google_play_purchase_handler.dart खोलें और PubsubApi इंपोर्ट करें:

lib/google_play_purchase_handler.dart

import 'package:googleapis/pubsub/v1.dart' as pubsub;

इसके बाद, PubsubApi को GooglePlayPurchaseHandler पर पास करें. साथ ही, क्लास कंस्ट्रक्टर में बदलाव करके Timer बनाएं. इसके लिए, यह तरीका अपनाएं:

lib/google_play_purchase_handler.dart

class GooglePlayPurchaseHandler extends PurchaseHandler {
  final ap.AndroidPublisherApi androidPublisher;
  final IapRepository iapRepository;
  final pubsub.PubsubApi pubsubApi; // new

  GooglePlayPurchaseHandler(
    this.androidPublisher,
    this.iapRepository,
    this.pubsubApi, // new
  ) {
    // Poll messages from Pub/Sub every 10 seconds
    Timer.periodic(Duration(seconds: 10), (_) {
      _pullMessageFromPubSub();
    });
  }

Timer को हर दस सेकंड में _pullMessageFromPubSub तरीके को कॉल करने के लिए कॉन्फ़िगर किया गया है. अपनी पसंद के मुताबिक, अवधि में बदलाव किया जा सकता है.

इसके बाद, _pullMessageFromPubSub

lib/google_play_purchase_handler.dart

  /// Process messages from Google Play
  /// Called every 10 seconds
  Future<void> _pullMessageFromPubSub() async {
    print('Polling Google Play messages');
    final request = pubsub.PullRequest(maxMessages: 1000);
    final topicName =
        'projects/$googleCloudProjectId/subscriptions/$googlePlayPubsubBillingTopic-sub';
    final pullResponse = await pubsubApi.projects.subscriptions.pull(
      request,
      topicName,
    );
    final messages = pullResponse.receivedMessages ?? [];
    for (final message in messages) {
      final data64 = message.message?.data;
      if (data64 != null) {
        await _processMessage(data64, message.ackId);
      }
    }
  }

  Future<void> _processMessage(String data64, String? ackId) async {
    final dataRaw = utf8.decode(base64Decode(data64));
    print('Received data: $dataRaw');
    final dynamic data = jsonDecode(dataRaw);
    if (data['testNotification'] != null) {
      print('Skip test messages');
      if (ackId != null) {
        await _ackMessage(ackId);
      }
      return;
    }
    final dynamic subscriptionNotification = data['subscriptionNotification'];
    final dynamic oneTimeProductNotification =
        data['oneTimeProductNotification'];
    if (subscriptionNotification != null) {
      print('Processing Subscription');
      final subscriptionId =
          subscriptionNotification['subscriptionId'] as String;
      final purchaseToken = subscriptionNotification['purchaseToken'] as String;
      final productData = productDataMap[subscriptionId]!;
      final result = await handleSubscription(
        userId: null,
        productData: productData,
        token: purchaseToken,
      );
      if (result && ackId != null) {
        await _ackMessage(ackId);
      }
    } else if (oneTimeProductNotification != null) {
      print('Processing NonSubscription');
      final sku = oneTimeProductNotification['sku'] as String;
      final purchaseToken =
          oneTimeProductNotification['purchaseToken'] as String;
      final productData = productDataMap[sku]!;
      final result = await handleNonSubscription(
        userId: null,
        productData: productData,
        token: purchaseToken,
      );
      if (result && ackId != null) {
        await _ackMessage(ackId);
      }
    } else {
      print('invalid data');
    }
  }

  /// ACK Messages from Pub/Sub
  Future<void> _ackMessage(String id) async {
    print('ACK Message');
    final request = pubsub.AcknowledgeRequest(ackIds: [id]);
    final subscriptionName =
        'projects/$googleCloudProjectId/subscriptions/$googlePlayPubsubBillingTopic-sub';
    await pubsubApi.projects.subscriptions.acknowledge(
      request,
      subscriptionName,
    );
  }

आपने अभी जो कोड जोड़ा है वह हर दस सेकंड में, Google Cloud के Pub/Sub विषय से कम्यूनिकेट करता है और नए मैसेज के लिए अनुरोध करता है. इसके बाद, _processMessage तरीके से हर मैसेज को प्रोसेस करता है.

यह तरीका, आने वाले मैसेज को डिकोड करता है. साथ ही, हर खरीदारी के बारे में अपडेट की गई जानकारी हासिल करता है. इसमें सदस्यताएं और बिना सदस्यता वाली खरीदारी, दोनों शामिल हैं. अगर ज़रूरी हो, तो यह मौजूदा handleSubscription या handleNonSubscription को कॉल करता है.

हर मैसेज की पुष्टि _askMessage तरीके से की जानी चाहिए.

इसके बाद, server.dart फ़ाइल में ज़रूरी डिपेंडेंसी जोड़ें. क्रेडेंशियल कॉन्फ़िगरेशन में PubsubApi.cloudPlatformScope जोड़ें:

bin/server.dart

import 'package:googleapis/pubsub/v1.dart' as pubsub;      // Add this import

  final clientGooglePlay = await auth
      .clientViaServiceAccount(clientCredentialsGooglePlay, [
        ap.AndroidPublisherApi.androidpublisherScope,
        pubsub.PubsubApi.cloudPlatformScope,               // Add this line
      ]);

इसके बाद, PubsubApi इंस्टेंस बनाएं:

bin/server.dart

  final pubsubApi = pubsub.PubsubApi(clientGooglePlay);

आखिर में, इसे GooglePlayPurchaseHandler कंस्ट्रक्टर को पास करें:

bin/server.dart

  return {
    'google_play': GooglePlayPurchaseHandler(
      androidPublisher,
      iapRepository,
      pubsubApi,                                           // Add this line
    ),
    'app_store': AppStorePurchaseHandler(
      iapRepository,
    ),
  };

Google Play का सेटअप

आपने pub/sub विषय से बिलिंग इवेंट इस्तेमाल करने के लिए कोड लिखा है, लेकिन आपने pub/sub विषय नहीं बनाया है. साथ ही, न ही कोई बिलिंग इवेंट पब्लिश किया जा रहा है. इसे सेट अप करने का समय आ गया है.

सबसे पहले, Pub/Sub विषय बनाएं:

  1. constants.dart में मौजूद googleCloudProjectId की वैल्यू को अपने Google Cloud प्रोजेक्ट के आईडी पर सेट करें.
  2. Google Cloud Console पर Cloud Pub/Sub पेज पर जाएं.
  3. पक्का करें कि आप अपने Firebase प्रोजेक्ट पर हों. इसके बाद, + विषय बनाएं पर क्लिक करें. d5ebf6897a0a8bf5.png
  4. नए विषय को वही नाम दें जो constants.dart में googlePlayPubsubBillingTopic के लिए सेट किया गया है. इस मामले में, इसे play_billing नाम दें. अगर आपने कोई दूसरा विकल्प चुना है, तो constants.dart को अपडेट करना न भूलें. विषय बनाएं. 20d690fc543c4212.png
  5. Pub/Sub विषयों की सूची में, उस विषय के लिए तीन वर्टिकल बिंदुओं पर क्लिक करें जिसे आपने अभी बनाया है. इसके बाद, अनुमतियां देखें पर क्लिक करें. ea03308190609fb.png
  6. दाईं ओर मौजूद साइडबार में, प्रिंसिपल जोड़ें को चुनें.
  7. यहां google-play-developer-notifications@system.gserviceaccount.com को जोड़ें और उसे Pub/Sub पब्लिशर की भूमिका दें. 55631ec0549215bc.png
  8. अनुमति में किए गए बदलावों को सेव करें.
  9. आपने अभी-अभी जो विषय बनाया है उसका विषय का नाम कॉपी करें.
  10. Play Console को फिर से खोलें और सभी ऐप्लिकेशन सूची से अपना ऐप्लिकेशन चुनें.
  11. नीचे की ओर स्क्रोल करें और कमाई करें > कमाई करने के लिए सेटअप पर जाएं.
  12. पूरा विषय भरें और अपने बदलाव सेव करें. 7e5e875dc6ce5d54.png

Google Play की बिलिंग से जुड़े सभी इवेंट अब इस विषय पर पब्लिश किए जाएंगे.

App Store के बिलिंग इवेंट प्रोसेस करना

इसके बाद, App Store के बिलिंग इवेंट के लिए भी ऐसा ही करें. App Store पर खरीदारी के अपडेट को लागू करने के दो असरदार तरीके हैं. पहला तरीका यह है कि Apple को एक वेबहुक उपलब्ध कराया जाए. Apple इसका इस्तेमाल आपके सर्वर से कम्यूनिकेट करने के लिए करता है. दूसरा तरीका यह है कि App Store Server API से कनेक्ट करके, सदस्यता की जानकारी मैन्युअल तरीके से हासिल की जाए. इस कोडलैब में आपको यही तरीका मिलेगा.

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

प्रोडक्शन एनवायरमेंट में, आपको इन दोनों को इस्तेमाल करना चाहिए. App Store से इवेंट पाने के लिए वेबहुक और अगर आपसे कोई इवेंट छूट गया है या आपको सदस्यता की स्थिति की दोबारा जांच करनी है, तो सर्वर एपीआई का इस्तेमाल करें.

lib/app_store_purchase_handler.dart खोलें और AppStoreServerAPI डिपेंडेंसी जोड़ें:

lib/app_store_purchase_handler.dart

  final AppStoreServerAPI appStoreServerAPI;                 // Add this member

  AppStorePurchaseHandler(
    this.iapRepository,
    this.appStoreServerAPI,                                  // And this parameter
  );

कंस्ट्रक्टर में बदलाव करके, एक टाइमर जोड़ें. यह टाइमर, _pullStatus तरीके को कॉल करेगा. यह टाइमर, हर 10 सेकंड में _pullStatus तरीके को कॉल करेगा. इस टाइमर की अवधि को अपनी ज़रूरत के हिसाब से बदला जा सकता है.

lib/app_store_purchase_handler.dart

  AppStorePurchaseHandler(this.iapRepository, this.appStoreServerAPI) {
    // Poll Subscription status every 10 seconds.
    Timer.periodic(Duration(seconds: 10), (_) {
      _pullStatus();
    });
  }

इसके बाद, _pullStatus तरीके को इस तरह बनाएं:

lib/app_store_purchase_handler.dart

  /// Request the App Store for the latest subscription status.
  /// Updates all App Store subscriptions in the database.
  /// NOTE: This code only handles when a subscription expires as example.
  Future<void> _pullStatus() async {
    print('Polling App Store');
    final purchases = await iapRepository.getPurchases();
    // filter for App Store subscriptions
    final appStoreSubscriptions = purchases.where(
      (element) =>
          element.type == ProductType.subscription &&
          element.iapSource == IAPSource.appstore,
    );
    for (final purchase in appStoreSubscriptions) {
      final status = await appStoreServerAPI.getAllSubscriptionStatuses(
        purchase.orderId,
      );
      // Obtain all subscriptions for the order ID.
      for (final subscription in status.data) {
        // Last transaction contains the subscription status.
        for (final transaction in subscription.lastTransactions) {
          final expirationDate = DateTime.fromMillisecondsSinceEpoch(
            transaction.transactionInfo.expiresDate ?? 0,
          );
          // Check if subscription has expired.
          final isExpired = expirationDate.isBefore(DateTime.now());
          print('Expiration Date: $expirationDate - isExpired: $isExpired');
          // Update the subscription status with the new expiration date and status.
          await iapRepository.updatePurchase(
            SubscriptionPurchase(
              userId: null,
              productId: transaction.transactionInfo.productId,
              iapSource: IAPSource.appstore,
              orderId: transaction.originalTransactionId,
              purchaseDate: DateTime.fromMillisecondsSinceEpoch(
                transaction.transactionInfo.originalPurchaseDate,
              ),
              type: ProductType.subscription,
              expiryDate: expirationDate,
              status: isExpired
                  ? SubscriptionStatus.expired
                  : SubscriptionStatus.active,
            ),
          );
        }
      }
    }
  }

यह तरीका इस तरह काम करता है:

  1. यह IapRepository का इस्तेमाल करके, Firestore से चालू सदस्यताओं की सूची हासिल करता है.
  2. यह हर ऑर्डर के लिए, App Store Server API से सदस्यता की स्थिति का अनुरोध करता है.
  3. यह कुकी, सदस्यता खरीदने के लिए किए गए आखिरी लेन-देन की जानकारी इकट्ठा करती है.
  4. यह कुकी, समयसीमा खत्म होने की तारीख की जांच करती है.
  5. यह कुकी, Firestore पर सदस्यता की स्थिति को अपडेट करती है. अगर सदस्यता खत्म हो गई है, तो उसे 'खत्म हो गई' के तौर पर मार्क कर दिया जाएगा.

आखिर में, App Store Server API के ऐक्सेस को कॉन्फ़िगर करने के लिए, सभी ज़रूरी कोड जोड़ें:

bin/server.dart

import 'package:app_store_server_sdk/app_store_server_sdk.dart';  // Add this import
import 'package:firebase_backend_dart/constants.dart';            // And this one.


  // add from here
  final subscriptionKeyAppStore = File(
    'assets/SubscriptionKey.p8',
  ).readAsStringSync();

  // Configure Apple Store API access
  var appStoreEnvironment = AppStoreEnvironment.sandbox(
    bundleId: bundleId,
    issuerId: appStoreIssuerId,
    keyId: appStoreKeyId,
    privateKey: subscriptionKeyAppStore,
  );

  // Stored token for Apple Store API access, if available
  final file = File('assets/appstore.token');
  String? appStoreToken;
  if (file.existsSync() && file.lengthSync() > 0) {
    appStoreToken = file.readAsStringSync();
  }

  final appStoreServerAPI = AppStoreServerAPI(
    AppStoreServerHttpClient(
      appStoreEnvironment,
      jwt: appStoreToken,
      jwtTokenUpdatedCallback: (token) {
        file.writeAsStringSync(token);
      },
    ),
  );
  // to here

  return {
    'google_play': GooglePlayPurchaseHandler(
      androidPublisher,
      iapRepository,
      pubsubApi,
    ),
    'app_store': AppStorePurchaseHandler(
      iapRepository,
      appStoreServerAPI,                                     // Add this argument
    ),
  };

App Store का सेटअप

इसके बाद, App Store सेट अप करें:

  1. App Store Connect में लॉगिन करें और उपयोगकर्ता और ऐक्सेस चुनें.
  2. इंटिग्रेशन > कुंजियां > ऐप्लिकेशन में खरीदारी पर जाएं.
  3. नया फ़ीड जोड़ने के लिए, "प्लस" आइकॉन पर टैप करें.
  4. इसे कोई नाम दें, जैसे कि "कोड लैब की".
  5. कुंजी वाली p8 फ़ाइल डाउनलोड करें.
  6. इसे ऐसेट फ़ोल्डर में SubscriptionKey.p8 नाम से कॉपी करें.
  7. नई बनाई गई कुंजी से कुंजी आईडी कॉपी करें और उसे lib/constants.dart फ़ाइल में appStoreKeyId कॉन्स्टेंट पर सेट करें.
  8. कुंजियों की सूची में सबसे ऊपर मौजूद, जारी करने वाले का आईडी कॉपी करें. इसके बाद, इसे lib/constants.dart फ़ाइल में appStoreIssuerId कॉन्स्टेंट पर सेट करें.

9540ea9ada3da151.png

डिवाइस पर की गई खरीदारी को ट्रैक करना

सर्वर साइड पर अपनी खरीदारी को ट्रैक करना सबसे सुरक्षित तरीका है, क्योंकि क्लाइंट को सुरक्षित करना मुश्किल होता है. हालांकि, आपको क्लाइंट को जानकारी वापस भेजने का कोई तरीका अपनाना होगा, ताकि ऐप्लिकेशन सदस्यता की स्थिति की जानकारी के आधार पर कार्रवाई कर सके. Firestore में खरीदारी की जानकारी सेव करके, डेटा को क्लाइंट के साथ सिंक किया जा सकता है. साथ ही, इसे अपने-आप अपडेट किया जा सकता है.

आपने ऐप्लिकेशन में पहले से ही IAPRepo को शामिल किया है. यह Firestore रिपॉज़िटरी है. इसमें List<PastPurchase> purchases में उपयोगकर्ता की खरीदारी का पूरा डेटा शामिल होता है. इस रिपॉज़िटरी में hasActiveSubscription, भी शामिल है. यह तब सही होता है, जब productId storeKeySubscription की खरीदारी की गई हो और उसकी स्थिति 'समयसीमा खत्म नहीं हुई' हो. जब उपयोगकर्ता लॉग इन नहीं होता है, तो सूची खाली होती है.

lib/repo/iap_repo.dart

  void updatePurchases() {
    _purchaseSubscription?.cancel();
    var user = _user;
    if (user == null) {
      purchases = [];
      hasActiveSubscription = false;
      hasUpgrade = false;
      return;
    }
    var purchaseStream = _firestore
        .collection('purchases')
        .where('userId', isEqualTo: user.uid)
        .snapshots();
    _purchaseSubscription = purchaseStream.listen((snapshot) {
      purchases = snapshot.docs.map((document) {
        var data = document.data();
        return PastPurchase.fromJson(data);
      }).toList();

      hasActiveSubscription = purchases.any(
        (element) =>
            element.productId == storeKeySubscription &&
            element.status != Status.expired,
      );

      hasUpgrade = purchases.any(
        (element) => element.productId == storeKeyUpgrade,
      );

      notifyListeners();
    });
  }

खरीदारी से जुड़ा सारा लॉजिक, DashPurchases क्लास में होता है. यहीं से सदस्यताएं लागू या हटाई जानी चाहिए. इसलिए, क्लास में iapRepo को प्रॉपर्टी के तौर पर जोड़ें और कंस्ट्रक्टर में iapRepo असाइन करें. इसके बाद, कंस्ट्रक्टर में सीधे तौर पर लिसनर जोड़ें और dispose() तरीके में लिसनर हटाएं. शुरुआत में, लिसनर सिर्फ़ एक खाली फ़ंक्शन हो सकता है. IAPRepo एक ChangeNotifier है. साथ ही, Firestore में खरीदारी से जुड़े डेटा में बदलाव होने पर, notifyListeners() को कॉल किया जाता है. इसलिए, खरीदे गए प्रॉडक्ट में बदलाव होने पर, purchasesUpdate() तरीके को हमेशा कॉल किया जाता है.

lib/logic/dash_purchases.dart

import '../repo/iap_repo.dart';                              // Add this import

class DashPurchases extends ChangeNotifier {
  DashCounter counter;
  FirebaseNotifier firebaseNotifier;
  StoreState storeState = StoreState.loading;
  late StreamSubscription<List<PurchaseDetails>> _subscription;
  List<PurchasableProduct> products = [];
  IAPRepo iapRepo;                                           // Add this line

  bool get beautifiedDash => _beautifiedDashUpgrade;
  bool _beautifiedDashUpgrade = false;
  final iapConnection = IAPConnection.instance;

  // Add this.iapRepo as a parameter
  DashPurchases(this.counter, this.firebaseNotifier, this.iapRepo) {
    final purchaseUpdated = iapConnection.purchaseStream;
    _subscription = purchaseUpdated.listen(
      _onPurchaseUpdate,
      onDone: _updateStreamOnDone,
      onError: _updateStreamOnError,
    );
    iapRepo.addListener(purchasesUpdate);
    loadPurchases();
  }

  Future<void> loadPurchases() async {
    // Elided.
  }

  @override
  void dispose() {
    _subscription.cancel();
    iapRepo.removeListener(purchasesUpdate);                 // Add this line
    super.dispose();
  }

  void purchasesUpdate() {
    //TODO manage updates
  }

इसके बाद, main.dart. में कंस्ट्रक्टर को IAPRepo दें. context.read का इस्तेमाल करके, रिपॉज़िटरी पाई जा सकती है, क्योंकि यह पहले से ही Provider में बनाई गई है.

lib/main.dart

        ChangeNotifierProvider<DashPurchases>(
          create: (context) => DashPurchases(
            context.read<DashCounter>(),
            context.read<FirebaseNotifier>(),
            context.read<IAPRepo>(),                         // Add this line
          ),
          lazy: false,
        ),

इसके बाद, purchaseUpdate() फ़ंक्शन के लिए कोड लिखें. dash_counter.dart, में, applyPaidMultiplier और removePaidMultiplier तरीकों से मल्टीप्लायर को क्रमशः 10 या 1 पर सेट किया जाता है. इसलिए, आपको यह देखने की ज़रूरत नहीं है कि सदस्यता पहले से लागू है या नहीं. सदस्यता की स्थिति बदलने पर, खरीदने के लिए उपलब्ध प्रॉडक्ट की स्थिति को भी अपडेट करें. इससे, खरीदारी वाले पेज पर यह दिखाया जा सकेगा कि सदस्यता पहले से ही चालू है. अपग्रेड खरीदा गया है या नहीं, इसके आधार पर _beautifiedDashUpgrade प्रॉपर्टी सेट करें.

lib/logic/dash_purchases.dart

  void purchasesUpdate() {
    var subscriptions = <PurchasableProduct>[];
    var upgrades = <PurchasableProduct>[];
    // Get a list of purchasable products for the subscription and upgrade.
    // This should be 1 per type.
    if (products.isNotEmpty) {
      subscriptions = products
          .where((element) => element.productDetails.id == storeKeySubscription)
          .toList();
      upgrades = products
          .where((element) => element.productDetails.id == storeKeyUpgrade)
          .toList();
    }

    // Set the subscription in the counter logic and show/hide purchased on the
    // purchases page.
    if (iapRepo.hasActiveSubscription) {
      counter.applyPaidMultiplier();
      for (var element in subscriptions) {
        _updateStatus(element, ProductStatus.purchased);
      }
    } else {
      counter.removePaidMultiplier();
      for (var element in subscriptions) {
        _updateStatus(element, ProductStatus.purchasable);
      }
    }

    // Set the Dash beautifier and show/hide purchased on
    // the purchases page.
    if (iapRepo.hasUpgrade != _beautifiedDashUpgrade) {
      _beautifiedDashUpgrade = iapRepo.hasUpgrade;
      for (var element in upgrades) {
        _updateStatus(
          element,
          _beautifiedDashUpgrade
              ? ProductStatus.purchased
              : ProductStatus.purchasable,
        );
      }
      notifyListeners();
    }
  }

  void _updateStatus(PurchasableProduct product, ProductStatus status) {
    if (product.status != ProductStatus.purchased) {
      product.status = ProductStatus.purchased;
      notifyListeners();
    }
  }

अब आपने यह पक्का कर लिया है कि बैकएंड सेवा में सदस्यता और अपग्रेड का स्टेटस हमेशा अपडेट रहे और ऐप्लिकेशन के साथ सिंक हो. ऐप्लिकेशन इसके मुताबिक काम करता है और आपके डैश क्लिकर गेम पर सदस्यता और अपग्रेड की सुविधाएं लागू करता है.

12. सब हो गया!

बधाई हो!!! आपने यह कोडलैब पूरा कर लिया है. इस कोडलैब का पूरा कोड, android_studio_folder.png complete फ़ोल्डर में देखा जा सकता है.

ज़्यादा जानने के लिए, अन्य Flutter कोडलैब आज़माएं.