افزودن خریدهای درون برنامه ای به برنامه Flutter خود

1. مقدمه

آخرین به روز رسانی: 2023-07-11

افزودن خریدهای درون‌برنامه‌ای به برنامه Flutter مستلزم راه‌اندازی صحیح فروشگاه‌های App و Play، تأیید خرید و اعطای مجوزهای لازم، مانند امتیازات اشتراک است.

در این کد لبه شما سه نوع خرید درون‌برنامه‌ای را به یک برنامه اضافه می‌کنید (برای شما ارائه شده است)، و این خریدها را با استفاده از یک Dart Backend با Firebase تأیید می‌کنید. برنامه ارائه شده، Dash Clicker، حاوی یک بازی است که از طلسم Dash به عنوان ارز استفاده می کند. گزینه های خرید زیر را اضافه خواهید کرد:

  1. یک گزینه خرید قابل تکرار برای 2000 داش در یک بار.
  2. یک خرید ارتقاء یکباره برای تبدیل Dash سبک قدیمی به Dash سبک مدرن.
  3. اشتراکی که کلیک های ایجاد شده به صورت خودکار را دو برابر می کند.

اولین گزینه خرید به کاربر سود مستقیم 2000 داش می دهد. اینها مستقیماً در دسترس کاربر هستند و می توانند بارها خریداری شوند. این ماده مصرفی نامیده می شود زیرا مستقیماً مصرف می شود و می تواند چندین بار مصرف شود.

گزینه دوم Dash را به Dash زیباتر ارتقا می دهد. این فقط یک بار باید خریداری شود و برای همیشه در دسترس است. چنین خریدی غیر قابل مصرف نامیده می شود زیرا نمی تواند توسط برنامه مصرف شود اما برای همیشه معتبر است.

سومین و آخرین گزینه خرید اشتراک است. زمانی که اشتراک فعال است، کاربر Dashes را سریع‌تر دریافت می‌کند، اما وقتی پرداخت هزینه اشتراک را متوقف کرد، مزایای آن نیز از بین می‌رود.

سرویس Backend (همچنین برای شما ارائه شده است) به عنوان یک برنامه Dart اجرا می شود، تأیید می کند که خریدها انجام شده است و آنها را با استفاده از Firestore ذخیره می کند. Firestore برای تسهیل فرآیند استفاده می شود، اما در برنامه تولیدی خود، می توانید از هر نوع سرویس پشتیبان استفاده کنید.

300123416ebc8dc1.png7145d0fffe6ea741.png646317a79be08214.png

چیزی که خواهی ساخت

  • شما یک برنامه را برای پشتیبانی از خریدهای مصرفی و اشتراک گسترش خواهید داد.
  • همچنین برای تأیید و ذخیره اقلام خریداری شده، یک برنامه Backend Dart را گسترش خواهید داد.

چیزی که یاد خواهید گرفت

  • نحوه پیکربندی App Store و Play Store با محصولات قابل خرید.
  • نحوه ارتباط با فروشگاه ها برای تأیید خرید و ذخیره آنها در Firestore.
  • نحوه مدیریت خرید در اپلیکیشن

آنچه شما نیاز دارید

  • اندروید استودیو 4.1 یا بالاتر
  • Xcode 12 یا جدیدتر (برای توسعه iOS)
  • فلوتر SDK

2. محیط توسعه را تنظیم کنید

برای راه اندازی این کد، کد را دانلود کنید و شناسه بسته نرم افزاری iOS و نام بسته را برای اندروید تغییر دهید.

کد را دانلود کنید

برای کلون کردن مخزن GitHub از خط فرمان، از دستور زیر استفاده کنید:

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

یا اگر ابزار cli GitHub را نصب کرده اید، از دستور زیر استفاده کنید:

gh repo clone flutter/codelabs flutter-codelabs

کد نمونه در دایرکتوری flutter-codelabs که حاوی کد مجموعه ای از codelabs است، کلون می شود. کد این کد لبه در flutter-codelabs/in_app_purchases است.

ساختار دایرکتوری تحت flutter-codelabs/in_app_purchases شامل مجموعه‌ای از عکس‌های فوری از جایی است که باید در انتهای هر مرحله نام‌گذاری شده باشید. کد شروع در مرحله 0 است، بنابراین مکان یابی فایل های منطبق به آسانی:

cd flutter-codelabs/in_app_purchases/step_00

اگر می خواهید به جلو پرش کنید یا ببینید چیزی بعد از یک مرحله چگونه باید باشد، به دایرکتوری که نام آن مرحله مورد علاقه شما است نگاه کنید. کد آخرین مرحله در زیر پوشه complete است.

پروژه شروع را راه اندازی کنید

پروژه شروع را از step_00 در IDE مورد علاقه خود باز کنید. ما از اندروید استودیو برای اسکرین شات ها استفاده کردیم، اما Visual Studio Code نیز یک گزینه عالی است. با هر ویرایشگر، مطمئن شوید که آخرین پلاگین های Dart و Flutter نصب شده اند.

برنامه هایی که می خواهید بسازید باید با اپ استور و پلی استور ارتباط برقرار کنند تا بدانند کدام محصولات و با چه قیمتی در دسترس هستند. هر برنامه با یک شناسه منحصر به فرد شناسایی می شود. برای فروشگاه برنامه iOS این شناسه بسته نرم افزاری و برای فروشگاه Play Android این شناسه برنامه نامیده می شود. این شناسه ها معمولاً با استفاده از نماد نام دامنه معکوس ساخته می شوند. به عنوان مثال هنگام ساخت یک برنامه خرید درون برنامه برای flutter.dev از dev.flutter.inapppurchase استفاده می کنیم. به یک شناسه برای برنامه خود فکر کنید، اکنون می خواهید آن را در تنظیمات پروژه تنظیم کنید.

ابتدا شناسه بسته نرم افزاری را برای iOS تنظیم کنید.

با باز بودن پروژه در Android Studio، روی پوشه iOS کلیک راست کرده، Flutter را کلیک کنید و ماژول را در برنامه Xcode باز کنید.

942772eb9a73bfaa.png

در ساختار پوشه Xcode، پروژه Runner در بالا قرار دارد و اهداف Flutter ، Runner و Products در زیر پروژه Runner قرار دارند. برای ویرایش تنظیمات پروژه، روی Runner دوبار کلیک کنید و روی Signing & Capabilities کلیک کنید. شناسه بسته ای را که به تازگی انتخاب کرده اید در قسمت Team وارد کنید تا تیم خود را تنظیم کنید.

812f919d965c649a.jpeg

اکنون می‌توانید Xcode را ببندید و به Android Studio برگردید تا پیکربندی اندروید را به پایان برسانید. برای انجام این کار، فایل build.gradle در زیر android/app, و applicationId خود را (در خط 37 در تصویر زیر) به شناسه برنامه، همان شناسه بسته iOS تغییر دهید. توجه داشته باشید که شناسه‌های فروشگاه‌های iOS و Android نباید یکسان باشند، اما یکسان نگه داشتن آنها کمتر مستعد خطا است و بنابراین در این کد لبه از شناسه‌های یکسان نیز استفاده خواهیم کرد.

5c4733ac560ae8c2.png

3. افزونه را نصب کنید

در این قسمت از Codelab افزونه in_app_purchase را نصب خواهید کرد.

اضافه کردن وابستگی در pubspec

با افزودن in_app_purchase به وابستگی‌های موجود در pubspec خود، in_app_purchase را به pubspec اضافه کنید:

$ cd app
$ flutter pub add in_app_purchase

pubspec.yaml

dependencies:
  ..
  cloud_firestore: ^4.0.3
  firebase_auth: ^4.2.2
  firebase_core: ^2.5.0
  google_sign_in: ^6.0.1
  http: ^0.13.4
  in_app_purchase: ^3.0.1
  intl: ^0.18.0
  provider: ^6.0.2
  ..

برای دانلود بسته روی pub get کلیک کنید یا flutter pub get در خط فرمان اجرا کنید.

4. App Store را راه اندازی کنید

برای راه‌اندازی خریدهای درون‌برنامه‌ای و آزمایش آن‌ها در iOS، باید یک برنامه جدید در App Store ایجاد کنید و محصولات قابل خرید را در آنجا ایجاد کنید. لازم نیست چیزی منتشر کنید یا برنامه را برای بررسی به اپل بفرستید. برای انجام این کار به یک حساب توسعه دهنده نیاز دارید. اگر ندارید، در برنامه توسعه دهنده اپل ثبت نام کنید .

برای استفاده از خریدهای درون‌برنامه‌ای، همچنین باید یک توافقنامه فعال برای برنامه‌های پولی در App Store Connect داشته باشید. به https://appstoreconnect.apple.com/ بروید و روی توافقنامه ها، مالیات و بانکداری کلیک کنید.

6e373780e5e24a6f.png

در اینجا قراردادهایی را برای برنامه های رایگان و پولی مشاهده خواهید کرد. وضعیت برنامه های رایگان باید فعال باشد و وضعیت برنامه های پولی جدید است. اطمینان حاصل کنید که شرایط را مشاهده می کنید، آنها را می پذیرید و تمام اطلاعات مورد نیاز را وارد می کنید.

74c73197472c9aec.png

وقتی همه چیز به درستی تنظیم شود، وضعیت برنامه های پولی فعال خواهد بود. این بسیار مهم است زیرا نمی‌توانید خریدهای درون‌برنامه‌ای را بدون توافقنامه فعال امتحان کنید.

4a100bbb8cafdbbf.jpeg

شناسه برنامه را ثبت کنید

یک شناسه جدید در پورتال توسعه دهندگان اپل ایجاد کنید.

55d7e592d9a3fc7b.png

شناسه های برنامه را انتخاب کنید

13f125598b72ca77.png

برنامه را انتخاب کنید

41ac4c13404e2526.png

توضیحاتی ارائه دهید و شناسه بسته را طوری تنظیم کنید که با همان مقداری که قبلاً در XCode تنظیم شده بود مطابقت داشته باشد.

9d2c940ad80deeef.png

برای راهنمایی بیشتر درباره نحوه ایجاد شناسه برنامه جدید، به راهنمای حساب برنامه‌نویس مراجعه کنید.

ایجاد یک برنامه جدید

با شناسه بسته منحصر به فرد خود یک برنامه جدید در App Store ایجاد کنید.

10509b17fbf031bd.png

5b7c0bb684ef52c7.png

برای راهنمایی بیشتر درباره نحوه ایجاد یک برنامه جدید و مدیریت قراردادها، به راهنمای App Store Connect مراجعه کنید.

برای تست خریدهای درون‌برنامه‌ای، به یک کاربر تست جعبه ایمنی نیاز دارید. این کاربر آزمایشی نباید به iTunes متصل باشد - فقط برای آزمایش خریدهای درون برنامه ای استفاده می شود. شما نمی توانید از آدرس ایمیلی که قبلاً برای حساب اپل استفاده شده است استفاده کنید. در Users and Access ، به Testers در Sandbox بروید تا یک حساب sandbox جدید ایجاد کنید یا IDهای Apple sandbox موجود را مدیریت کنید.

3ca2b26d4e391a4c.jpeg

اکنون می توانید با رفتن به Settings > App Store > Sandbox-account، کاربر sandbox خود را در آیفون خود تنظیم کنید.

c7dadad2c1d448fa.jpeg5363f87efcddaa4.jpeg

پیکربندی خریدهای درون برنامه ای

اکنون سه مورد قابل خرید را پیکربندی خواهید کرد:

  • dash_consumable_2k : خریدی مصرفی که می توان چندین برابر آن را خریداری کرد که به ازای هر خرید 2000 داش (ارز درون برنامه ای) به کاربر اعطا می کند.
  • dash_upgrade_3d : یک خرید «ارتقای» غیرمصرفی است که فقط یک بار می‌توان آن را خریداری کرد و به کاربر داش متفاوتی برای کلیک کردن می‌دهد.
  • dash_subscription_doubler : اشتراکی که به کاربر دو برابر بیشتر Dash در هر کلیک برای مدت زمان اشتراک می دهد.

d156b2f5bac43ca8.png

به خریدهای درون‌برنامه > مدیریت بروید.

خریدهای درون برنامه ای خود را با شناسه های مشخص شده ایجاد کنید:

  1. dash_consumable_2k به عنوان یک Consumable تنظیم کنید.

از dash_consumable_2k به عنوان شناسه محصول استفاده کنید. نام مرجع فقط در اتصال به فروشگاه برنامه استفاده می شود، فقط آن را dash consumable 2k تنظیم کنید و محلی سازی های خود را برای خرید اضافه کنید. تماس بگیرید خرید Spring is in the air با 2000 dashes fly out .

ec1701834fd8527.png

  1. dash_upgrade_3d به عنوان غیر مصرفی تنظیم کنید.

از dash_upgrade_3d به عنوان شناسه محصول استفاده کنید. نام مرجع را روی dash upgrade 3d تنظیم کنید و محلی سازی های خود را برای خرید اضافه کنید. خرید 3D Dash with Brings your dash back to the future به عنوان توضیحات.

6765d4b711764c30.png

  1. dash_subscription_doubler را به عنوان یک اشتراک تمدید خودکار تنظیم کنید.

جریان برای اشتراک ها کمی متفاوت است. ابتدا باید نام مرجع و شناسه محصول را تنظیم کنید:

6d29e08dae26a0c4.png

بعد، شما باید یک گروه اشتراک ایجاد کنید. هنگامی که چندین اشتراک بخشی از یک گروه هستند، یک کاربر می‌تواند همزمان تنها در یکی از این اشتراک‌ها مشترک شود، اما می‌تواند به راحتی بین این اشتراک‌ها ارتقا یا کاهش دهد. فقط با این subscriptions گروه تماس بگیرید.

5bd0da17a85ac076.png

در مرحله بعد، مدت زمان اشتراک و محلی سازی ها را وارد کنید. نام این اشتراک را Jet Engine با توضیح Doubles your clicks . روی ذخیره کلیک کنید.

bd1b1d82eeee4cb3.png

بعد از اینکه روی دکمه ذخیره کلیک کردید، قیمت اشتراک را اضافه کنید. هر قیمتی را که می خواهید انتخاب کنید

d0bf39680ef0aa2e.png

اکنون باید سه خرید را در لیست خریدها مشاهده کنید:

99d5c4b446e8fecf.png

5. Play Store را راه اندازی کنید

همانند اپ استور، برای Play Store نیز به یک حساب توسعه دهنده نیاز دارید. اگر هنوز ندارید، یک حساب ثبت کنید .

یک برنامه جدید ایجاد کنید

یک برنامه جدید در کنسول Google Play ایجاد کنید:

  1. کنسول Play را باز کنید.
  2. همه برنامه ها > ایجاد برنامه را انتخاب کنید.
  3. یک زبان پیش فرض را انتخاب کنید و عنوانی برای برنامه خود اضافه کنید. نام برنامه خود را همانطور که می خواهید در Google Play نمایش داده شود تایپ کنید. بعدا می توانید نام را تغییر دهید.
  4. مشخص کنید که برنامه شما یک بازی است. می توانید بعداً این را تغییر دهید.
  5. مشخص کنید که برنامه شما رایگان است یا پولی.
  6. یک آدرس ایمیل اضافه کنید که کاربران Play Store بتوانند از آن برای تماس با شما در مورد این برنامه استفاده کنند.
  7. دستورالعمل‌های محتوا و اعلامیه‌های قوانین صادرات ایالات متحده را تکمیل کنید.
  8. ایجاد برنامه را انتخاب کنید.

پس از ایجاد برنامه، به داشبورد بروید و تمام وظایف را در بخش Set up your app انجام دهید. در اینجا، اطلاعاتی در مورد برنامه خود ارائه می دهید، مانند رتبه بندی محتوا و اسکرین شات ها. 13845badcf9bc1db.png

برنامه را امضا کنید

برای اینکه بتوانید خریدهای درون‌برنامه‌ای را آزمایش کنید، به حداقل یک نسخه آپلود شده در Google Play نیاز دارید.

برای این کار، باید نسخه انتشار خود را با چیزی غیر از کلیدهای اشکال زدایی امضا کنید.

یک فروشگاه کلید ایجاد کنید

اگر یک فروشگاه کلید موجود دارید، به مرحله بعدی بروید. اگر نه، با اجرای دستور زیر در خط فرمان یکی ایجاد کنید.

در مک/لینوکس از دستور زیر استفاده کنید:

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

در ویندوز از دستور زیر استفاده کنید:

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 ایجاد کنید که حاوی ارجاع به keystore شما باشد:

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 ، امضای برنامه خود را پیکربندی کنید.

قبل از بلوک android ، اطلاعات keystore را از فایل خواص خود اضافه کنید:

   def keystoreProperties = new Properties()
   def keystorePropertiesFile = rootProject.file('key.properties')
   if (keystorePropertiesFile.exists()) {
       keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
   }

   android {
         // omitted
   }

فایل key.properties را در شی keystoreProperties بارگیری کنید.

قبل از بلوک buildTypes کد زیر را اضافه کنید:

   buildTypes {
       release {
           // TODO: Add your own signing config for the release build.
           // Signing with the debug keys for now,
           // so `flutter run --release` works.
           signingConfig signingConfigs.debug
       }
   }

بلوک signingConfigs را در فایل build.gradle ماژول خود با اطلاعات پیکربندی امضا پیکربندی کنید:

   signingConfigs {
       release {
           keyAlias keystoreProperties['keyAlias']
           keyPassword keystoreProperties['keyPassword']
           storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
           storePassword keystoreProperties['storePassword']
       }
   }
   buildTypes {
       release {
           signingConfig signingConfigs.release
       }
   }

نسخه‌های انتشار برنامه شما اکنون به صورت خودکار امضا می‌شوند.

برای اطلاعات بیشتر درباره امضای برنامه خود، به امضای برنامه خود در developer.android.com مراجعه کنید.

اولین ساخت خود را آپلود کنید

پس از پیکربندی برنامه شما برای امضا، باید بتوانید برنامه خود را با اجرای زیر بسازید:

flutter build appbundle

این دستور به طور پیش‌فرض یک نسخه نسخه تولید می‌کند و خروجی را می‌توانید در <your app dir>/build/app/outputs/bundle/release/ پیدا کنید.

از داشبورد در کنسول Google Play، به Release > Testing > Closed testing بروید و یک نسخه آزمایشی بسته جدید ایجاد کنید.

برای این کد لبه، باید به امضای Google برنامه پایبند باشید، بنابراین ادامه دهید و برای شرکت در Play App Signing را فشار دهید.

ba98446d9c5c40e0.png

سپس، بسته برنامه app-release.aab را که توسط دستور build ایجاد شده است، آپلود کنید.

روی ذخیره و سپس بررسی انتشار کلیک کنید.

در نهایت، روی Start rollout to Internal testing کلیک کنید تا نسخه آزمایشی داخلی فعال شود.

کاربران آزمایشی را تنظیم کنید

برای اینکه بتوانید خریدهای درون‌برنامه‌ای را آزمایش کنید، حساب‌های Google آزمایش‌کنندگان شما باید در کنسول Google Play در دو مکان اضافه شوند:

  1. به مسیر آزمایشی خاص (تست داخلی)
  2. به عنوان آزمایش کننده مجوز

ابتدا، با افزودن تستر به مسیر تست داخلی شروع کنید. به Release > Testing > Internal testing برگردید و روی تب Testers کلیک کنید.

a0d0394e85128f84.png

با کلیک روی ایجاد لیست ایمیل، یک لیست ایمیل جدید ایجاد کنید. به فهرست یک نام بدهید و آدرس‌های ایمیل حساب‌های Google را که نیاز به دسترسی به آزمایش خریدهای درون‌برنامه دارند، اضافه کنید.

بعد، کادر انتخاب لیست را انتخاب کنید و روی ذخیره تغییرات کلیک کنید.

سپس، آزمایش کنندگان مجوز را اضافه کنید:

  1. به نمای همه برنامه‌ها در کنسول Google Play برگردید.
  2. به تنظیمات > آزمایش مجوز بروید.
  3. همان آدرس ایمیل آزمایش‌کنندگانی را اضافه کنید که باید بتوانند خریدهای درون‌برنامه‌ای را آزمایش کنند.
  4. پاسخ مجوز را روی RESPOND_NORMALLY تنظیم کنید.
  5. روی ذخیره تغییرات کلیک کنید.

a1a0f9d3e55ea8da.png

پیکربندی خریدهای درون برنامه ای

اکنون مواردی را که در برنامه قابل خرید هستند پیکربندی خواهید کرد.

درست مانند اپ استور، شما باید سه خرید مختلف را تعریف کنید:

  • dash_consumable_2k : خریدی مصرفی که می توان چندین برابر آن را خریداری کرد که به ازای هر خرید 2000 داش (ارز درون برنامه ای) به کاربر اعطا می کند.
  • dash_upgrade_3d : یک خرید «ارتقای» غیر مصرفی که فقط یک بار قابل خرید است، که به کاربر داش متفاوتی برای کلیک کردن می دهد.
  • dash_subscription_doubler : اشتراکی که به کاربر دو برابر بیشتر Dash در هر کلیک برای مدت زمان اشتراک می دهد.

ابتدا مواد مصرفی و غیر مصرفی را اضافه کنید.

  1. به کنسول Google Play بروید و برنامه خود را انتخاب کنید.
  2. به کسب درآمد > محصولات > محصولات درون برنامه ای بروید.
  3. روی ایجاد محصول کلیک کنید c8d66e32f57dee21.png
  4. تمام اطلاعات مورد نیاز برای محصول خود را وارد کنید. اطمینان حاصل کنید که شناسه محصول دقیقاً با شناسه ای که قصد استفاده از آن را دارید مطابقت دارد.
  5. روی ذخیره کلیک کنید.
  6. روی Activate کلیک کنید.
  7. برای خرید «ارتقای» غیرقابل مصرف، فرآیند را تکرار کنید.

بعد، اشتراک را اضافه کنید:

  1. به کنسول Google Play بروید و برنامه خود را انتخاب کنید.
  2. به کسب درآمد > محصولات > اشتراک ها بروید.
  3. روی ایجاد اشتراک کلیک کنید 32a6a9eefdb71dd0.png
  4. تمام اطلاعات مورد نیاز برای اشتراک خود را وارد کنید. مطمئن شوید شناسه محصول دقیقاً با شناسه ای که قصد استفاده از آن را دارید مطابقت دارد.
  5. روی ذخیره کلیک کنید

خریدهای شما اکنون باید در Play Console تنظیم شوند.

6. Firebase را راه اندازی کنید

در این کد لبه، شما از یک سرویس پشتیبان برای تأیید و پیگیری خریدهای کاربران استفاده خواهید کرد.

استفاده از سرویس پشتیبان چندین مزیت دارد:

  • می توانید به طور ایمن تراکنش ها را تأیید کنید.
  • می‌توانید به رویدادهای صورت‌حساب از فروشگاه‌های برنامه واکنش نشان دهید.
  • می توانید خریدها را در پایگاه داده پیگیری کنید.
  • کاربران نمی توانند برنامه شما را فریب دهند تا با چرخاندن ساعت سیستم خود، ویژگی های ممتاز را ارائه دهند.

در حالی که راه‌های زیادی برای راه‌اندازی یک سرویس پشتیبان وجود دارد، این کار را با استفاده از توابع ابری و Firestore با استفاده از Firebase خود Google انجام خواهید داد.

نوشتن backend خارج از محدوده این نرم افزار Code تلقی می شود، بنابراین کد شروع از قبل شامل یک پروژه Firebase است که خریدهای اولیه را برای شروع شما انجام می دهد.

افزونه های Firebase نیز همراه با برنامه استارت گنجانده شده است.

کاری که باید انجام دهید این است که پروژه Firebase خود را ایجاد کنید، برنامه و Backend را برای Firebase پیکربندی کنید و در نهایت backend را مستقر کنید.

یک پروژه Firebase ایجاد کنید

به کنسول Firebase بروید و یک پروژه Firebase جدید ایجاد کنید. برای این مثال، پروژه Dash Clicker را صدا بزنید.

در برنامه باطن، خریدها را به یک کاربر خاص گره می‌زنید، بنابراین، نیاز به احراز هویت دارید. برای این کار، از ماژول احراز هویت Firebase با ورود به سیستم Google استفاده کنید.

  1. از داشبورد Firebase، به Authentication بروید و در صورت نیاز آن را فعال کنید.
  2. به برگه روش ورود به سیستم بروید و ارائه دهنده ورود به سیستم Google را فعال کنید.

7babb48832fbef29.png

از آنجایی که از پایگاه داده Firebases Firestore نیز استفاده خواهید کرد، این را نیز فعال کنید.

e20553e0de5ac331.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
    }
  }
}

Firebase را برای Flutter تنظیم کنید

روش توصیه شده برای نصب Firebase در برنامه Flutter استفاده از FlutterFire CLI است. دستورالعمل ها را همانطور که در صفحه راه اندازی توضیح داده شده است دنبال کنید.

هنگام اجرای پیکربندی flutterfire، پروژه ای را که در مرحله قبل ایجاد کردید انتخاب کنید.

$ 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 را لغو کنید، yes را انتخاب کنید.

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

راه اندازی Firebase برای Android: مراحل بعدی

از داشبورد Firebase، به Project Overview بروید، تنظیمات را انتخاب کنید و برگه General را انتخاب کنید.

به پایین بروید و به برنامه های شما بروید و برنامه dashclicker (اندروید) را انتخاب کنید.

b22d46a759c0c834.png

برای اجازه ورود به سیستم Google در حالت اشکال‌زدایی، باید اثر انگشت هش SHA-1 گواهی اشکال‌زدایی خود را ارائه دهید.

هش گواهی امضای اشکال زدایی خود را دریافت کنید

در ریشه پروژه برنامه Flutter خود، دایرکتوری را به پوشه android/ تغییر دهید و سپس یک گزارش امضا ایجاد کنید.

cd android
./gradlew :app:signingReport

لیست بزرگی از کلیدهای امضا به شما نمایش داده می شود. از آنجایی که به دنبال هش گواهی اشکال زدایی هستید، به دنبال گواهی با ویژگی های Variant و Config بگردید که روی debug تنظیم شده است. به احتمال زیاد keystore در پوشه اصلی شما تحت .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 را کپی کنید و آخرین فیلد را در گفتگوی مدال ارسال برنامه پر کنید.

راه اندازی Firebase برای iOS: مراحل بعدی

ios/Runnder.xcworkspace را با Xcode باز کنید. یا با IDE انتخابی شما.

در VSCode روی پوشه ios/ کلیک راست کرده و open in xcode .

در اندروید استودیو روی پوشه ios/ کلیک راست کرده سپس بر روی flutter و سپس open iOS module in Xcode کلیک کنید.

برای اجازه ورود به سیستم Google در iOS، گزینه پیکربندی CFBundleURLTypes را به فایل‌های build plist خود اضافه کنید. (برای اطلاعات بیشتر، اسناد بسته google_sign_in را بررسی کنید.) در این مورد، فایل ها ios/Runner/Info-Debug.plist و ios/Runner/Info-Release.plist هستند.

جفت کلید-مقدار قبلاً اضافه شده است، اما مقادیر آنها باید جایگزین شوند:

  1. مقدار REVERSED_CLIENT_ID را از فایل GoogleService-Info.plist ، بدون عنصر <string>..</string> آن را دریافت کنید.
  2. مقدار را در فایل‌های ios/Runner/Info-Debug.plist و ios/Runner/Info-Release.plist زیر کلید CFBundleURLTypes جایگزین کنید.
<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 حاوی دو صفحه است. این صفحه همچنین سه Provider برای DashCounter ، DashUpgrades, و DashPurchases ایجاد می کند. DashCounter تعداد فعلی داش ها را ردیابی می کند و به صورت خودکار آنها را افزایش می دهد. DashUpgrades ارتقاهایی را که می توانید با Dashes خریداری کنید مدیریت می کند. این کد لبه روی DashPurchases تمرکز دارد.

به طور پیش فرض، شی یک ارائه دهنده زمانی تعریف می شود که آن شی برای اولین بار درخواست شود. این شیء مستقیماً هنگام شروع برنامه به خرید به‌روزرسانی‌ها گوش می‌دهد، بنابراین بارگذاری تنبل را در این شیء با lazy: false غیرفعال کنید:

lib/main.dart

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

شما همچنین به یک نمونه از 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!;
  }
}

اگر می خواهید تست به کار خود ادامه دهد، باید کمی آزمون را به روز کنید. برای دریافت کد کامل TestIAPConnection widget_test.dart را در GitHub بررسی کنید.

test/widget_test.dart

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

در lib/logic/dash_purchases.dart ، به کد DashPurchases ChangeNotifier بروید. در حال حاضر فقط یک DashCounter وجود دارد که می توانید آن را به داش های خریداری شده خود اضافه کنید.

یک ویژگی اشتراک جریان، _subscription (از نوع StreamSubscription<List<PurchaseDetails>> _subscription;IAPConnection.instance, و importها اضافه کنید. کد به دست آمده باید به شکل زیر باشد:

lib/logic/dash_purchases.dart

import 'package:in_app_purchase/in_app_purchase.dart';

class DashPurchases extends ChangeNotifier {
  late StreamSubscription<List<PurchaseDetails>> _subscription;
  final iapConnection = IAPConnection.instance;

  DashPurchases(this.counter);
}

کلمه کلیدی late به _subscription اضافه می شود زیرا _subscription در سازنده مقداردهی اولیه می شود. این پروژه به‌طور پیش‌فرض به‌گونه‌ای تنظیم شده است که غیر تهی باشد (NNBD)، به این معنی که ویژگی‌هایی که nullable اعلام نمی‌شوند باید دارای یک مقدار غیر تهی باشند. late شرایط به شما امکان می دهد تا تعیین این مقدار را به تاخیر بیندازید.

در سازنده، purchaseUpdatedStream را دریافت کنید و شروع به گوش دادن به جریان کنید. در روش dispose() اشتراک جریان را لغو کنید.

lib/logic/dash_purchases.dart

class DashPurchases extends ChangeNotifier {
  DashCounter counter;
  late StreamSubscription<List<PurchaseDetails>> _subscription;
  final iapConnection = IAPConnection.instance;

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

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

  Future<void> buy(PurchasableProduct product) async {
    // omitted
  }

  void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
    // Handle purchases here
  }

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

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

اکنون، برنامه به‌روزرسانی‌های خرید را دریافت می‌کند، بنابراین، در بخش بعدی، خرید انجام می‌دهید!

قبل از ادامه، تست ها را با " flutter test" اجرا کنید تا مطمئن شوید همه چیز به درستی تنظیم شده است.

$ flutter test

00:01 +1: All tests passed!                                                                                   

8. خرید کنید

در این قسمت از نرم افزار کد، محصولات ساختگی موجود در حال حاضر را با محصولات واقعی قابل خرید جایگزین خواهید کرد. این محصولات از فروشگاه ها بارگیری می شوند، در یک لیست نشان داده می شوند و هنگام ضربه زدن روی محصول خریداری می شوند.

محصول قابل خرید را تطبیق دهید

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;
    }
  }

وقتی فروشگاه در دسترس است، خریدهای موجود را بارگیری کنید. با توجه به تنظیم قبلی Firebase، انتظار داشته باشید 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);
    for (var element in response.notFoundIDs) {
      debugPrint('Purchase $element not found');
    }
    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();
  }

در نهایت، مقدار فیلد 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(),
    );
  }
}

اگر به درستی پیکربندی شده باشند، باید بتوانید محصولات موجود در فروشگاه های اندروید و 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);
        break;
      case storeKeySubscription:
      case storeKeyUpgrade:
        await iapConnection.buyNonConsumable(purchaseParam: purchaseParam);
        break;
      default:
        throw ArgumentError.value(
            product.productDetails, '${product.id} is not a known product');
    }
  }

قبل از ادامه، متغیر _beautifiedDashUpgrade را ایجاد کنید و beautifiedDash getter را برای ارجاع به آن به روز کنید.

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();
          break;
        case storeKeyConsumable:
          counter.addBoughtDashes(2000);
          break;
        case storeKeyUpgrade:
          _beautifiedDashUpgrade = true;
          break;
      }
    }

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

9. Backend را راه اندازی کنید

قبل از اینکه به دنبال ردیابی و تأیید خرید باشید، یک Dart Backend راه‌اندازی کنید تا از انجام این کار پشتیبانی کند.

در این بخش، از پوشه dart-backend/ به عنوان ریشه کار کنید.

مطمئن شوید که ابزارهای زیر را نصب کرده اید:

نمای کلی پروژه پایه

از آنجایی که برخی از قسمت های این پروژه خارج از محدوده این کدلب در نظر گرفته می شوند، در کد شروع قرار می گیرند. ایده خوبی است که قبل از شروع به مرور مواردی که قبلاً در کد استارت وجود دارد، بپردازید تا از نحوه ساختاربندی چیزها مطلع شوید.

این کد پشتیبان می تواند به صورت محلی روی دستگاه شما اجرا شود، برای استفاده از آن نیازی به استقرار آن ندارید. با این حال، شما باید بتوانید از دستگاه توسعه خود (اندروید یا آیفون) به دستگاهی که سرور در آن اجرا می شود وصل شوید. برای آن، آنها باید در یک شبکه باشند و شما باید آدرس IP دستگاه خود را بدانید.

سعی کنید با استفاده از دستور زیر سرور را اجرا کنید:

$ dart ./bin/server.dart

Serving at http://0.0.0.0:8080

Backend Dart از shelf و shelf_router برای ارائه نقاط پایانی API استفاده می کند. به طور پیش فرض، سرور هیچ مسیری را ارائه نمی دهد. بعداً یک مسیر برای رسیدگی به فرآیند تأیید خرید ایجاد خواهید کرد.

یکی از قسمت هایی که قبلاً در کد شروع گنجانده شده است IapRepository در lib/iap_repository.dart است. از آنجایی که یادگیری نحوه تعامل با Firestore یا به طور کلی پایگاه‌های داده مرتبط با این کد تلقی نمی‌شود، کد شروع شامل توابعی برای ایجاد یا به‌روزرسانی خریدها در Firestore و همچنین تمام کلاس‌های آن خریدها است.

دسترسی Firebase را تنظیم کنید

برای دسترسی به Firebase Firestore، به یک کلید دسترسی به حساب سرویس نیاز دارید. یکی را ایجاد کنید که تنظیمات پروژه Firebase را باز می کند و به بخش حساب های سرویس بروید، سپس Generate new private key را انتخاب کنید.

27590fc77ae94ad4.png

فایل JSON دانلود شده را در پوشه assets/ کپی کنید و نام آن را به service-account-firebase.json تغییر دهید.

دسترسی به Google Play را تنظیم کنید

برای دسترسی به فروشگاه Play برای تأیید خریدها، باید یک حساب سرویس با این مجوزها ایجاد کنید و اعتبار JSON را برای آن دانلود کنید.

  1. به کنسول Google Play بروید و از صفحه همه برنامه ها شروع کنید.
  2. به تنظیمات > دسترسی به API بروید. 317fdfb54921f50e.png در صورتی که کنسول Google Play درخواست می‌کند که یک پروژه موجود را ایجاد کنید یا به آن پیوند دهید، ابتدا این کار را انجام دهید و سپس به این صفحه بازگردید.
  3. قسمتی را که می‌توانید حساب‌های سرویس را تعریف کنید، پیدا کنید و روی ایجاد حساب سرویس جدید کلیک کنید. 1e70d3f8d794bebb.png
  4. روی پیوند Google Cloud Platform در گفتگوی ظاهر شده کلیک کنید. 7c9536336dd9e9b4.png
  5. پروژه خود را انتخاب کنید اگر آن را نمی‌بینید، مطمئن شوید که در فهرست کشویی حساب در بالا سمت راست به حساب Google صحیح وارد شده‌اید. 3fb3a25bad803063.png
  6. پس از انتخاب پروژه خود، روی + Create Service Account در نوار منوی بالا کلیک کنید. 62fe4c3f8644acd8.png
  7. یک نام برای حساب سرویس ارائه دهید، به صورت اختیاری یک توضیح ارائه دهید تا به خاطر بیاورید که برای چه کاری است، و به مرحله بعدی بروید. 8a92d5d6a3dff48c.png
  8. نقش ویرایشگر را به حساب سرویس اختصاص دهید. 6052b7753667ed1a.png
  9. جادوگر را تمام کنید، به صفحه دسترسی API در کنسول توسعه دهنده برگردید و روی Refresh service accounts کلیک کنید. شما باید حساب جدید خود را در لیست ببینید. 5895a7db8b4c7659.png
  10. روی اعطای دسترسی برای حساب سرویس جدید خود کلیک کنید.
  11. صفحه بعدی را به سمت پایین اسکرول کنید تا به بلوک داده های مالی برسید. هم مشاهده داده‌های مالی، سفارش‌ها و پاسخ‌های نظرسنجی لغو و هم مدیریت سفارش‌ها و اشتراک‌ها را انتخاب کنید. 75b22d0201cf67e.png
  12. روی دعوت از کاربر کلیک کنید. 70ea0b1288c62a59.png
  13. اکنون که حساب راه‌اندازی شده است، فقط باید چند اعتبار ایجاد کنید. در کنسول ابری بازگردید، حساب سرویس خود را در لیست حساب‌های سرویس پیدا کنید، روی سه نقطه عمودی کلیک کنید و مدیریت کلیدها را انتخاب کنید. 853ee186b0e9954e.png
  14. یک کلید JSON جدید ایجاد کنید و آن را دانلود کنید. 2a33a55803f5299c.pngcb4bf48ebac0364e.png
  15. نام فایل دانلود شده را به service-account-google-play.json, تغییر دهید و آن را به فهرست assets/ منتقل کنید.

یک کار دیگر که باید انجام دهیم این است که lib/constants.dart, را باز کرده و مقدار androidPackageId را با شناسه بسته ای که برای برنامه اندروید خود انتخاب کرده اید جایگزین کنید.

دسترسی Apple App Store را تنظیم کنید

برای دسترسی به App Store برای تأیید خریدها، باید یک راز مشترک را تنظیم کنید:

  1. App Store Connect را باز کنید.
  2. به My Apps بروید و برنامه خود را انتخاب کنید.
  3. در پیمایش نوار کناری، به خریدهای درون برنامه > مدیریت بروید.
  4. در سمت راست بالای لیست ، روی برنامه مشترک برنامه خاص کلیک کنید.
  5. یک راز جدید ایجاد کنید و آن را کپی کنید.
  6. lib/constants.dart, را باز کنید و مقدار appStoreSharedSecret را با راز مشترکی که تازه تولید کرده اید جایگزین کنید.

d8b8042470aeeff.png

B72F4565750E2F40.PNG

پرونده پیکربندی ثابت

قبل از اقدام ، اطمینان حاصل کنید که ثابت های زیر در پرونده lib/constants.dart پیکربندی شده است:

  • androidPackageId : شناسه بسته استفاده شده در Android. به عنوان مثال com.example.dashclicker
  • appStoreSharedSecret : راز مشترک برای دسترسی به App Store Connect برای انجام تأیید خرید.
  • bundleId : شناسه بسته نرم افزاری که در iOS استفاده می شود. به عنوان مثال com.example.dashclicker

فعلاً می توانید بقیه ثابت ها را نادیده بگیرید.

10. خریدها را تأیید کنید

جریان کلی برای تأیید خریدها برای iOS و Android مشابه است.

برای هر دو فروشگاه ، برنامه شما هنگام خرید ، نشانه ای را دریافت می کند.

این نشانه توسط برنامه به سرویس باطن شما ارسال می شود ، که به نوبه خود ، خرید را با سرورهای فروشگاه مربوطه با استفاده از نشانه ارائه شده تأیید می کند.

سرویس پس زمینه می تواند خرید را انتخاب کند و به برنامه پاسخ دهد که آیا خرید معتبر بوده یا خیر.

با داشتن سرویس باطن ، اعتبار سنجی را با فروشگاه ها به جای برنامه کاربردی که در دستگاه کاربر شما اجرا می شود ، می توانید از دسترسی کاربر به ویژگی های حق بیمه جلوگیری کنید ، به عنوان مثال ، بازگرداندن ساعت سیستم خود.

طرف فلاتر را تنظیم کنید

احراز هویت را تنظیم کنید

همانطور که می خواهید خریدها را به سرویس باطن خود ارسال کنید ، می خواهید هنگام خرید ، کاربر تأیید شود. بیشتر منطق احراز هویت در حال حاضر در پروژه استارت برای شما اضافه شده است ، شما فقط باید مطمئن شوید که PurchasePage وقتی کاربر هنوز وارد نشده است ، دکمه ورود را نشان می دهد. کد زیر را به ابتدای روش ساخت PurchasePage اضافه کنید:

lib/pages/buy_page.dart

import '../logic/firebase_notifier.dart';
import '../model/firebase_state.dart';
import 'login_page.dart';

class PurchasePage extends StatelessWidget {  
  const PurchasePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    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();
    }
    // omitted

نقطه پایانی تأیید تماس از برنامه

در برنامه ، عملکرد _verifyPurchase(PurchaseDetails purchaseDetails) را ایجاد کنید که با استفاده از تماس پست HTTP ، نقطه پایانی /verifypurchase را در پس زمینه دارت خود فراخوانی می کند.

فروشگاه انتخاب شده ( google_play برای فروشگاه Play یا app_store برای فروشگاه App) ، serverVerificationData و productID ارسال کنید. سرور کد وضعیت را نشان می دهد که نشان می دهد خرید تأیید شده است یا خیر.

در ثابت برنامه ، IP سرور را به آدرس IP دستگاه محلی خود پیکربندی کنید.

lib/logic/dash_purchases.dart

  FirebaseNotifier firebaseNotifier;

  DashPurchases(this.counter, this.firebaseNotifier) {
    // omitted
  }

firebaseNotifier با ایجاد DashPurchases در main.dart:

lib/main.dart

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

یک گیرنده را برای کاربر در Firebasenotifier اضافه کنید ، بنابراین می توانید شناسه کاربر را به عملکرد خرید تأیید کنید.

lib/logic/firebase_notifier.dart

  User? get user => FirebaseAuth.instance.currentUser;

عملکرد _verifyPurchase به کلاس DashPurchases اضافه کنید. این عملکرد 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) {
      print('Successfully verified purchase');
      return true;
    } else {
      print('failed request: ${response.statusCode} - ${response.body}');
      return false;
    }
  }

درست قبل از انجام خرید ، با عملکرد _verifyPurchase در _handlePurchase تماس بگیرید. شما فقط باید خرید را در هنگام تأیید اعمال کنید. در یک برنامه تولید ، می توانید این مورد را بیشتر مشخص کنید ، به عنوان مثال ، هنگامی که فروشگاه به طور موقت در دسترس نیست ، اشتراک آزمایشی را اعمال کنید. با این حال ، برای این مثال ، آن را ساده نگه دارید و فقط هنگام تأیید خرید با موفقیت ، خرید را انجام دهید.

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();
            break;
          case storeKeyConsumable:
            counter.addBoughtDashes(1000);
            break;
        }
      }
    }

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

در برنامه اکنون همه چیز آماده اعتبار خرید است.

سرویس پس زمینه را تنظیم کنید

در مرحله بعد ، عملکرد ابر را برای تأیید خریدها در پس زمینه تنظیم کنید.

ساخت دستگیره های خرید

از آنجا که جریان تأیید برای هر دو فروشگاه نزدیک به یکسان است ، یک کلاس چکیده PurchaseHandler را با پیاده سازی های جداگانه برای هر فروشگاه تنظیم کنید.

BE50C207C5A2A519.PNG

با اضافه کردن یک پرونده purchase_handler.dart به lib/ Folder ، جایی که یک کلاس انتزاعی PurchaseHandler را با دو روش انتزاعی برای تأیید دو نوع خرید مختلف تعریف می کنید: اشتراک و اشتراک و اشتراک.

lib/buy_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/buy_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 حاوی اطلاعات اساسی در مورد محصولات مختلف قابل خرید است ، که شامل شناسه محصول (که گاهی اوقات به آن SKU نیز گفته می شود) و 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 و فروشگاه App App تعریف کنید. با 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 ارائه شده استفاده می کنید.

بعد ، همین کار را برای کنترل کننده فروشگاه App انجام دهید. 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,
  }) {
    return true;
  }

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

عالیه اکنون شما دو گیرنده خرید دارید. در مرحله بعد ، بیایید نقطه انتهایی API تأیید خرید را ایجاد کنیم.

از دستگیره های خرید استفاده کنید

bin/server.dart را باز کنید. dart و یک نقطه پایانی API با استفاده از shelf_route ایجاد کنید:

bin/server.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);
}

({
  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. یک نقطه پایانی پست را تعریف کنید که از برنامه ای که قبلاً ایجاد کرده اید فراخوانی خواهد شد.
  2. بارگیری JSON را رمزگشایی کنید و اطلاعات زیر را استخراج کنید:
  3. userId : در حال حاضر در شناسه کاربر وارد شده است
  4. source : فروشگاه مورد استفاده ، یا app_store یا google_play .
  5. productData : به دست آمده از productDataMap که قبلاً ایجاد کرده اید.
  6. token : شامل داده های تأیید برای ارسال به فروشگاه ها است.
  7. بسته به منبع ، با روش verifyPurchase ، یا برای GooglePlayPurchaseHandler یا AppStorePurchaseHandler تماس بگیرید.
  8. اگر تأیید موفقیت آمیز بود ، روش Response.ok به مشتری باز می گرداند.
  9. در صورت عدم موفقیت ، این روش Response.internalServerError برمی گرداند. internalServerError به مشتری.

پس از ایجاد نقطه پایان API ، باید دو گیرنده خرید را پیکربندی کنید. این امر شما را ملزم می کند که کلیدهای حساب کاربری را که در مرحله قبل به دست آورده اید بارگیری کنید و دسترسی به خدمات مختلف از جمله ناشر Android API و Firebase Firestore API را پیکربندی کنید. سپس ، دو گیرنده خرید را با وابستگی های مختلف ایجاد کنید:

bin/server.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 را تأیید کنید: HANDER خرید را پیاده سازی کنید

در مرحله بعد ، اجرای کنترل کننده خرید Google Play را ادامه دهید.

Google در حال حاضر بسته های DART را برای تعامل با API های مورد نیاز برای تأیید خریدها ارائه می دهد. شما آنها را در پرونده server.dart تنظیم کرده اید و اکنون از آنها در کلاس GooglePlayPurchaseHandler استفاده می کنید.

برای خریدهای نوع غیر زیرنویس ، کنترل کننده را پیاده سازی کنید:

lib/google_play_purchase_handler.dart

  @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 do not 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 do not 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

/// 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;
}

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,
  };
}

خرید Google Play شما اکنون باید در پایگاه داده تأیید و ذخیره شود.

در مرحله بعد ، به خریدهای فروشگاه App برای iOS بروید.

خریدهای iOS را تأیید کنید: کنترل کننده خرید را پیاده سازی کنید

برای تأیید خریدها با فروشگاه App ، یک بسته دارت شخص ثالث به نام app_store_server_sdk وجود دارد که این روند را آسان تر می کند.

با ایجاد نمونه ITunesApi شروع کنید. برای تسهیل اشکال زدایی خطا از پیکربندی Sandbox و همچنین فعال کردن ورود به سیستم استفاده کنید.

lib/app_store_purchase_handler.dart

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

اکنون بر خلاف API های Google Play ، فروشگاه App از همان نقاط پایانی API برای اشتراک ها و غیر اشتراک ها استفاده می کند. این بدان معنی است که شما می توانید از همان منطق برای هر دو گیرنده استفاده کنید. آنها را با هم ادغام کنید تا آنها همان اجرای را فراخوانی کنند:

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 {
   //..
  }

اکنون ، 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;
    }
  }

خرید فروشگاه برنامه شما اکنون باید در پایگاه داده تأیید و ذخیره شود!

پس زمینه را اجرا کنید

در این مرحله ، می توانید dart bin/server.dart را اجرا کنید تا به نقطه پایانی /verifypurchase خدمت کنید.

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

11. خریدها را پیگیری کنید

روش پیشنهادی برای ردیابی خریدهای کاربران شما در سرویس پس زمینه است. این امر به این دلیل است که پس زمینه شما می تواند به وقایع موجود در فروشگاه پاسخ دهد و بنابراین کمتر مستعد ابتلا به اطلاعات منسوخ به دلیل ذخیره سازی و همچنین مستعد ابتلا به دستکاری کمتر است.

ابتدا پردازش رویدادهای فروشگاه را روی پس زمینه با پشتیبان دارت که در حال ساخت هستید تنظیم کنید.

رویدادهای فروشگاه را در پس زمینه پردازش کنید

فروشگاه ها این توانایی را دارند که از هرگونه رویداد صدور صورتحساب که اتفاق می افتد ، از پس زمینه خود مطلع شود ، مانند زمان تمدید اشتراک. شما می توانید این رویدادها را در پس زمینه خود پردازش کنید تا خریدها در پایگاه داده خود ادامه دهید. در این بخش ، این کار را برای فروشگاه Google Play و فروشگاه App App تنظیم کنید.

رویدادهای صورتحساب 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 پیکربندی شده است تا هر ده ثانیه با روش _pullMessageFromSubSub تماس بگیرد. می توانید مدت زمان را به ترجیح خود تنظیم کنید.

سپس _pullMessageFromSubSub ایجاد کنید

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/$googlePlayProjectName/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/$googlePlayProjectName/subscriptions/$googlePlayPubsubBillingTopic-sub';
    await pubsubApi.projects.subscriptions.acknowledge(
      request,
      subscriptionName,
    );
  }

کدی که شما به تازگی اضافه کرده اید هر ده ثانیه با موضوع Pub/Sub از Google Cloud ارتباط برقرار می کند و پیام های جدیدی را درخواست می کند. سپس ، هر پیام را به روش _processMessage پردازش می کند.

این روش پیام های دریافتی را رمزگشایی می کند و اطلاعات به روز شده در مورد هر خرید ، هم اشتراک و هم غیر اشتراک را به دست می آورد و در صورت لزوم به handleSubscription موجود یا handleNonSubscription فرا می خواند.

هر پیام باید با روش _askMessage اذعان شود.

در مرحله بعد ، وابستگی های مورد نیاز را به پرونده server.dart اضافه کنید. pubsubapi.cloudplatformscope را به پیکربندی اعتبار اضافه کنید:

bin/server.dart

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

سپس ، نمونه pubsubapi را ایجاد کنید:

bin/server.dart

  final pubsubApi = pubsub.PubsubApi(clientGooglePlay);

و در آخر ، آن را به سازنده GooglePlayPurchaseHandler منتقل کنید:

bin/server.dart

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

Google Play Setup

شما کد را برای مصرف رویدادهای صورتحساب از موضوع Pub/Sub نوشتید ، اما شما موضوع Pub/Sub را ایجاد نکرده اید ، و همچنین هیچ رویدادی صورتحساب را منتشر نمی کنید. وقت آن است که این کار را تنظیم کنیم.

اول ، ایجاد یک میخانه/زیر موضوع:

  1. به صفحه Cloud Pub/Sub در کنسول Google Cloud مراجعه کنید.
  2. اطمینان حاصل کنید که در پروژه Firebase خود هستید و روی + ایجاد موضوع کلیک کنید. d5ebf6897a0a8bf5.png
  3. به موضوع جدید یک نام ، یکسان با مقدار تعیین شده برای GOOGLE_PLAY_PUBSUB_BILLING_TOPIC در constants.ts . در این حالت ، آن را play_billing نامگذاری کنید. اگر چیز دیگری را انتخاب کردید ، حتماً constants.ts را به روز کنید. موضوع را ایجاد کنید. 20D690FC543C4212.png
  4. در لیست مباحث میخانه/فرعی خود ، برای موضوعی که اخیراً ایجاد کرده اید ، روی سه نقطه عمودی کلیک کنید و روی View Permissions کلیک کنید. EA03308190609FB.PNG
  5. در نوار کناری سمت راست ، Add Principal را انتخاب کنید.
  6. در اینجا ، google-play-developer-notifications@system.gserviceaccount.com را اضافه کرده و به آن نقش میخانه/زیر ناشر را اعطا کنید. 55631EC0549215BC.PNG
  7. تغییرات اجازه را ذخیره کنید.
  8. نام موضوع موضوعی را که اخیراً ایجاد کرده اید کپی کنید.
  9. کنسول Play را دوباره باز کنید و برنامه خود را از لیست All Apps انتخاب کنید.
  10. به سمت پایین بروید و به کسب درآمد> Setup کسب درآمد بروید.
  11. موضوع کامل را پر کرده و تغییرات خود را ذخیره کنید. 7E5E875DC6ce5D54.png

اکنون همه رویدادهای صورتحساب Google Play در مورد این موضوع منتشر می شوند.

رویدادهای صورتحساب فروشگاه برنامه را پردازش کنید

در مرحله بعد ، همین کار را برای رویدادهای صورتحساب فروشگاه برنامه انجام دهید. دو روش مؤثر برای اجرای به روزرسانی های حمل و نقل در خریدهای فروشگاه App وجود دارد. یکی اجرای یک وب وب است که شما به اپل ارائه می دهید و آنها برای برقراری ارتباط با سرور خود استفاده می کنند. راه دوم ، که روشی است که در این CodeLab پیدا خواهید کرد ، اتصال به API سرور App Store و به دست آوردن اطلاعات اشتراک به صورت دستی است.

دلیل تمرکز این CodeLab بر روی راه حل دوم این است که شما باید سرور خود را در معرض اینترنت قرار دهید تا بتوانید وب را پیاده سازی کنید.

در یک محیط تولید ، در حالت ایده آل دوست دارید هر دو را داشته باشید. Webhook برای به دست آوردن رویدادها از فروشگاه App و API سرور در صورت از دست دادن یک رویداد یا نیاز به بررسی دو برابر وضعیت اشتراک.

با باز کردن lib/app_store_purchase_handler.dart شروع کنید و اضافه کردن وابستگی AppStoreserverapi:

lib/app_store_purchase_handler.dart

final AppStoreServerAPI appStoreServerAPI;

AppStorePurchaseHandler(
  this.iapRepository,
  this.appStoreServerAPI, // new
)

سازنده را اصلاح کنید تا یک تایمر اضافه کنید که به روش _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

  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. لیست اشتراک های فعال را از Firestore با استفاده از iAprepository به دست می آورد.
  2. برای هر سفارش ، وضعیت اشتراک را به API سرور App Store درخواست می کند.
  3. آخرین معامله را برای آن خرید اشتراک بدست می آورد.
  4. تاریخ انقضا را بررسی می کند.
  5. وضعیت اشتراک در Firestore را به روز می کند ، در صورت منقضی شدن ، به همین ترتیب مشخص می شود.

در آخر ، تمام کد لازم را برای پیکربندی API APP SERVER API APP اضافه کنید:

bin/server.dart

  // 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, // new
    ),
  };

تنظیم فروشگاه برنامه

بعد ، فروشگاه App را تنظیم کنید:

  1. وارد App Store Connect شوید و کاربران و دسترسی را انتخاب کنید.
  2. به نوع کلید> خرید درون برنامه بروید.
  3. برای اضافه کردن یک نسخه جدید ، روی نماد "Plus" ضربه بزنید.
  4. به عنوان مثال "کلید CodeLab" به آن بدهید.
  5. پرونده P8 حاوی کلید را بارگیری کنید.
  6. آن را در پوشه دارایی ها ، با نام SubscriptionKey.p8 کپی کنید.
  7. شناسه کلید را از کلید تازه ایجاد شده کپی کرده و آن را روی appStoreKeyId ثابت در پرونده lib/constants.dart تنظیم کنید.
  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((DocumentSnapshot 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

  IAPRepo iapRepo;

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

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

  void purchasesUpdate() {
    //TODO manage updates
  }

در مرحله بعد ، IAPRepo به سازنده در main.dart. می توانید با استفاده از context.read ، مخزن را دریافت کنید زیرا قبلاً در یک Provider ایجاد شده است.

lib/main.dart

        ChangeNotifierProvider<DashPurchases>(
          create: (context) => DashPurchases(
            context.read<DashCounter>(),
            context.read<FirebaseNotifier>(),
            context.read<IAPRepo>(),
          ),
          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();
    }
  }

اکنون اطمینان حاصل کرده اید که وضعیت اشتراک و به روزرسانی همیشه در سرویس پس زمینه جریان دارد و با برنامه هماهنگ شده است. این برنامه مطابق با آن عمل می کند و ویژگی های اشتراک و به روزرسانی را در بازی Clicker Dash شما اعمال می کند.

12. همه انجام شده!

تبریک میگم!!! شما CodeLab را تکمیل کرده اید. می توانید کد تکمیل شده برای این CodeLab را در آن پیدا کنید android_studio_folder.png پوشه کامل

برای کسب اطلاعات بیشتر ، سایر CodeLabs Flutter را امتحان کنید.