1. مقدمه
آخرین به روز رسانی: 2023-07-11
افزودن خریدهای درونبرنامهای به برنامه Flutter مستلزم راهاندازی صحیح فروشگاههای App و Play، تأیید خرید و اعطای مجوزهای لازم، مانند امتیازات اشتراک است.
در این کد لبه شما سه نوع خرید درونبرنامهای را به یک برنامه اضافه میکنید (برای شما ارائه شده است)، و این خریدها را با استفاده از یک Dart Backend با Firebase تأیید میکنید. برنامه ارائه شده، Dash Clicker، حاوی یک بازی است که از طلسم Dash به عنوان ارز استفاده می کند. گزینه های خرید زیر را اضافه خواهید کرد:
- یک گزینه خرید قابل تکرار برای 2000 داش در یک بار.
- یک خرید ارتقاء یکباره برای تبدیل Dash سبک قدیمی به Dash سبک مدرن.
- اشتراکی که کلیک های ایجاد شده به صورت خودکار را دو برابر می کند.
اولین گزینه خرید به کاربر سود مستقیم 2000 داش می دهد. اینها مستقیماً در دسترس کاربر هستند و می توانند بارها خریداری شوند. این ماده مصرفی نامیده می شود زیرا مستقیماً مصرف می شود و می تواند چندین بار مصرف شود.
گزینه دوم Dash را به Dash زیباتر ارتقا می دهد. این فقط یک بار باید خریداری شود و برای همیشه در دسترس است. چنین خریدی غیر قابل مصرف نامیده می شود زیرا نمی تواند توسط برنامه مصرف شود اما برای همیشه معتبر است.
سومین و آخرین گزینه خرید اشتراک است. زمانی که اشتراک فعال است، کاربر Dashes را سریعتر دریافت میکند، اما وقتی پرداخت هزینه اشتراک را متوقف کرد، مزایای آن نیز از بین میرود.
سرویس Backend (همچنین برای شما ارائه شده است) به عنوان یک برنامه Dart اجرا می شود، تأیید می کند که خریدها انجام شده است و آنها را با استفاده از Firestore ذخیره می کند. Firestore برای تسهیل فرآیند استفاده می شود، اما در برنامه تولیدی خود، می توانید از هر نوع سرویس پشتیبان استفاده کنید.
چیزی که خواهی ساخت
- شما یک برنامه را برای پشتیبانی از خریدهای مصرفی و اشتراک گسترش خواهید داد.
- همچنین برای تأیید و ذخیره اقلام خریداری شده، یک برنامه 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 باز کنید.
در ساختار پوشه Xcode، پروژه Runner در بالا قرار دارد و اهداف Flutter ، Runner و Products در زیر پروژه Runner قرار دارند. برای ویرایش تنظیمات پروژه، روی Runner دوبار کلیک کنید و روی Signing & Capabilities کلیک کنید. شناسه بسته ای را که به تازگی انتخاب کرده اید در قسمت Team وارد کنید تا تیم خود را تنظیم کنید.
اکنون میتوانید Xcode را ببندید و به Android Studio برگردید تا پیکربندی اندروید را به پایان برسانید. برای انجام این کار، فایل build.gradle
در زیر android/app,
و applicationId
خود را (در خط 37 در تصویر زیر) به شناسه برنامه، همان شناسه بسته iOS تغییر دهید. توجه داشته باشید که شناسههای فروشگاههای iOS و Android نباید یکسان باشند، اما یکسان نگه داشتن آنها کمتر مستعد خطا است و بنابراین در این کد لبه از شناسههای یکسان نیز استفاده خواهیم کرد.
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/ بروید و روی توافقنامه ها، مالیات و بانکداری کلیک کنید.
در اینجا قراردادهایی را برای برنامه های رایگان و پولی مشاهده خواهید کرد. وضعیت برنامه های رایگان باید فعال باشد و وضعیت برنامه های پولی جدید است. اطمینان حاصل کنید که شرایط را مشاهده می کنید، آنها را می پذیرید و تمام اطلاعات مورد نیاز را وارد می کنید.
وقتی همه چیز به درستی تنظیم شود، وضعیت برنامه های پولی فعال خواهد بود. این بسیار مهم است زیرا نمیتوانید خریدهای درونبرنامهای را بدون توافقنامه فعال امتحان کنید.
شناسه برنامه را ثبت کنید
یک شناسه جدید در پورتال توسعه دهندگان اپل ایجاد کنید.
شناسه های برنامه را انتخاب کنید
برنامه را انتخاب کنید
توضیحاتی ارائه دهید و شناسه بسته را طوری تنظیم کنید که با همان مقداری که قبلاً در XCode تنظیم شده بود مطابقت داشته باشد.
برای راهنمایی بیشتر درباره نحوه ایجاد شناسه برنامه جدید، به راهنمای حساب برنامهنویس مراجعه کنید.
ایجاد یک برنامه جدید
با شناسه بسته منحصر به فرد خود یک برنامه جدید در App Store ایجاد کنید.
برای راهنمایی بیشتر درباره نحوه ایجاد یک برنامه جدید و مدیریت قراردادها، به راهنمای App Store Connect مراجعه کنید.
برای تست خریدهای درونبرنامهای، به یک کاربر تست جعبه ایمنی نیاز دارید. این کاربر آزمایشی نباید به iTunes متصل باشد - فقط برای آزمایش خریدهای درون برنامه ای استفاده می شود. شما نمی توانید از آدرس ایمیلی که قبلاً برای حساب اپل استفاده شده است استفاده کنید. در Users and Access ، به Testers در Sandbox بروید تا یک حساب sandbox جدید ایجاد کنید یا IDهای Apple sandbox موجود را مدیریت کنید.
اکنون می توانید با رفتن به Settings > App Store > Sandbox-account، کاربر sandbox خود را در آیفون خود تنظیم کنید.
پیکربندی خریدهای درون برنامه ای
اکنون سه مورد قابل خرید را پیکربندی خواهید کرد:
-
dash_consumable_2k
: خریدی مصرفی که می توان چندین برابر آن را خریداری کرد که به ازای هر خرید 2000 داش (ارز درون برنامه ای) به کاربر اعطا می کند. -
dash_upgrade_3d
: یک خرید «ارتقای» غیرمصرفی است که فقط یک بار میتوان آن را خریداری کرد و به کاربر داش متفاوتی برای کلیک کردن میدهد. -
dash_subscription_doubler
: اشتراکی که به کاربر دو برابر بیشتر Dash در هر کلیک برای مدت زمان اشتراک می دهد.
به خریدهای درونبرنامه > مدیریت بروید.
خریدهای درون برنامه ای خود را با شناسه های مشخص شده ایجاد کنید:
-
dash_consumable_2k
به عنوان یک Consumable تنظیم کنید.
از dash_consumable_2k
به عنوان شناسه محصول استفاده کنید. نام مرجع فقط در اتصال به فروشگاه برنامه استفاده می شود، فقط آن را dash consumable 2k
تنظیم کنید و محلی سازی های خود را برای خرید اضافه کنید. تماس بگیرید خرید Spring is in the air
با 2000 dashes fly out
.
-
dash_upgrade_3d
به عنوان غیر مصرفی تنظیم کنید.
از dash_upgrade_3d
به عنوان شناسه محصول استفاده کنید. نام مرجع را روی dash upgrade 3d
تنظیم کنید و محلی سازی های خود را برای خرید اضافه کنید. خرید 3D Dash
with Brings your dash back to the future
به عنوان توضیحات.
-
dash_subscription_doubler
را به عنوان یک اشتراک تمدید خودکار تنظیم کنید.
جریان برای اشتراک ها کمی متفاوت است. ابتدا باید نام مرجع و شناسه محصول را تنظیم کنید:
بعد، شما باید یک گروه اشتراک ایجاد کنید. هنگامی که چندین اشتراک بخشی از یک گروه هستند، یک کاربر میتواند همزمان تنها در یکی از این اشتراکها مشترک شود، اما میتواند به راحتی بین این اشتراکها ارتقا یا کاهش دهد. فقط با این subscriptions
گروه تماس بگیرید.
در مرحله بعد، مدت زمان اشتراک و محلی سازی ها را وارد کنید. نام این اشتراک را Jet Engine
با توضیح Doubles your clicks
. روی ذخیره کلیک کنید.
بعد از اینکه روی دکمه ذخیره کلیک کردید، قیمت اشتراک را اضافه کنید. هر قیمتی را که می خواهید انتخاب کنید
اکنون باید سه خرید را در لیست خریدها مشاهده کنید:
5. Play Store را راه اندازی کنید
همانند اپ استور، برای Play Store نیز به یک حساب توسعه دهنده نیاز دارید. اگر هنوز ندارید، یک حساب ثبت کنید .
یک برنامه جدید ایجاد کنید
یک برنامه جدید در کنسول Google Play ایجاد کنید:
- کنسول Play را باز کنید.
- همه برنامه ها > ایجاد برنامه را انتخاب کنید.
- یک زبان پیش فرض را انتخاب کنید و عنوانی برای برنامه خود اضافه کنید. نام برنامه خود را همانطور که می خواهید در Google Play نمایش داده شود تایپ کنید. بعدا می توانید نام را تغییر دهید.
- مشخص کنید که برنامه شما یک بازی است. می توانید بعداً این را تغییر دهید.
- مشخص کنید که برنامه شما رایگان است یا پولی.
- یک آدرس ایمیل اضافه کنید که کاربران Play Store بتوانند از آن برای تماس با شما در مورد این برنامه استفاده کنند.
- دستورالعملهای محتوا و اعلامیههای قوانین صادرات ایالات متحده را تکمیل کنید.
- ایجاد برنامه را انتخاب کنید.
پس از ایجاد برنامه، به داشبورد بروید و تمام وظایف را در بخش Set up your app انجام دهید. در اینجا، اطلاعاتی در مورد برنامه خود ارائه می دهید، مانند رتبه بندی محتوا و اسکرین شات ها.
برنامه را امضا کنید
برای اینکه بتوانید خریدهای درونبرنامهای را آزمایش کنید، به حداقل یک نسخه آپلود شده در 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 را فشار دهید.
سپس، بسته برنامه app-release.aab
را که توسط دستور build ایجاد شده است، آپلود کنید.
روی ذخیره و سپس بررسی انتشار کلیک کنید.
در نهایت، روی Start rollout to Internal testing کلیک کنید تا نسخه آزمایشی داخلی فعال شود.
کاربران آزمایشی را تنظیم کنید
برای اینکه بتوانید خریدهای درونبرنامهای را آزمایش کنید، حسابهای Google آزمایشکنندگان شما باید در کنسول Google Play در دو مکان اضافه شوند:
- به مسیر آزمایشی خاص (تست داخلی)
- به عنوان آزمایش کننده مجوز
ابتدا، با افزودن تستر به مسیر تست داخلی شروع کنید. به Release > Testing > Internal testing برگردید و روی تب Testers کلیک کنید.
با کلیک روی ایجاد لیست ایمیل، یک لیست ایمیل جدید ایجاد کنید. به فهرست یک نام بدهید و آدرسهای ایمیل حسابهای Google را که نیاز به دسترسی به آزمایش خریدهای درونبرنامه دارند، اضافه کنید.
بعد، کادر انتخاب لیست را انتخاب کنید و روی ذخیره تغییرات کلیک کنید.
سپس، آزمایش کنندگان مجوز را اضافه کنید:
- به نمای همه برنامهها در کنسول Google Play برگردید.
- به تنظیمات > آزمایش مجوز بروید.
- همان آدرس ایمیل آزمایشکنندگانی را اضافه کنید که باید بتوانند خریدهای درونبرنامهای را آزمایش کنند.
- پاسخ مجوز را روی
RESPOND_NORMALLY
تنظیم کنید. - روی ذخیره تغییرات کلیک کنید.
پیکربندی خریدهای درون برنامه ای
اکنون مواردی را که در برنامه قابل خرید هستند پیکربندی خواهید کرد.
درست مانند اپ استور، شما باید سه خرید مختلف را تعریف کنید:
-
dash_consumable_2k
: خریدی مصرفی که می توان چندین برابر آن را خریداری کرد که به ازای هر خرید 2000 داش (ارز درون برنامه ای) به کاربر اعطا می کند. -
dash_upgrade_3d
: یک خرید «ارتقای» غیر مصرفی که فقط یک بار قابل خرید است، که به کاربر داش متفاوتی برای کلیک کردن می دهد. -
dash_subscription_doubler
: اشتراکی که به کاربر دو برابر بیشتر Dash در هر کلیک برای مدت زمان اشتراک می دهد.
ابتدا مواد مصرفی و غیر مصرفی را اضافه کنید.
- به کنسول Google Play بروید و برنامه خود را انتخاب کنید.
- به کسب درآمد > محصولات > محصولات درون برنامه ای بروید.
- روی ایجاد محصول کلیک کنید
- تمام اطلاعات مورد نیاز برای محصول خود را وارد کنید. اطمینان حاصل کنید که شناسه محصول دقیقاً با شناسه ای که قصد استفاده از آن را دارید مطابقت دارد.
- روی ذخیره کلیک کنید.
- روی Activate کلیک کنید.
- برای خرید «ارتقای» غیرقابل مصرف، فرآیند را تکرار کنید.
بعد، اشتراک را اضافه کنید:
- به کنسول Google Play بروید و برنامه خود را انتخاب کنید.
- به کسب درآمد > محصولات > اشتراک ها بروید.
- روی ایجاد اشتراک کلیک کنید
- تمام اطلاعات مورد نیاز برای اشتراک خود را وارد کنید. مطمئن شوید شناسه محصول دقیقاً با شناسه ای که قصد استفاده از آن را دارید مطابقت دارد.
- روی ذخیره کلیک کنید
خریدهای شما اکنون باید در Play Console تنظیم شوند.
6. Firebase را راه اندازی کنید
در این کد لبه، شما از یک سرویس پشتیبان برای تأیید و پیگیری خریدهای کاربران استفاده خواهید کرد.
استفاده از سرویس پشتیبان چندین مزیت دارد:
- می توانید به طور ایمن تراکنش ها را تأیید کنید.
- میتوانید به رویدادهای صورتحساب از فروشگاههای برنامه واکنش نشان دهید.
- می توانید خریدها را در پایگاه داده پیگیری کنید.
- کاربران نمی توانند برنامه شما را فریب دهند تا با چرخاندن ساعت سیستم خود، ویژگی های ممتاز را ارائه دهند.
در حالی که راههای زیادی برای راهاندازی یک سرویس پشتیبان وجود دارد، این کار را با استفاده از توابع ابری و Firestore با استفاده از Firebase خود Google انجام خواهید داد.
نوشتن backend خارج از محدوده این نرم افزار Code تلقی می شود، بنابراین کد شروع از قبل شامل یک پروژه Firebase است که خریدهای اولیه را برای شروع شما انجام می دهد.
افزونه های Firebase نیز همراه با برنامه استارت گنجانده شده است.
کاری که باید انجام دهید این است که پروژه Firebase خود را ایجاد کنید، برنامه و Backend را برای Firebase پیکربندی کنید و در نهایت backend را مستقر کنید.
یک پروژه Firebase ایجاد کنید
به کنسول Firebase بروید و یک پروژه Firebase جدید ایجاد کنید. برای این مثال، پروژه Dash Clicker را صدا بزنید.
در برنامه باطن، خریدها را به یک کاربر خاص گره میزنید، بنابراین، نیاز به احراز هویت دارید. برای این کار، از ماژول احراز هویت Firebase با ورود به سیستم Google استفاده کنید.
- از داشبورد Firebase، به Authentication بروید و در صورت نیاز آن را فعال کنید.
- به برگه روش ورود به سیستم بروید و ارائه دهنده ورود به سیستم Google را فعال کنید.
از آنجایی که از پایگاه داده Firebases Firestore نیز استفاده خواهید کرد، این را نیز فعال کنید.
قوانین 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 (اندروید) را انتخاب کنید.
برای اجازه ورود به سیستم 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
هستند.
جفت کلید-مقدار قبلاً اضافه شده است، اما مقادیر آنها باید جایگزین شوند:
- مقدار
REVERSED_CLIENT_ID
را از فایلGoogleService-Info.plist
، بدون عنصر<string>..</string>
آن را دریافت کنید. - مقدار را در فایلهای
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 را ببینید. توجه داشته باشید که ممکن است مدتی طول بکشد تا خریدها در کنسول های مربوطه وارد شوند.
به 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/
به عنوان ریشه کار کنید.
مطمئن شوید که ابزارهای زیر را نصب کرده اید:
- دارت
- Firebase CLI
نمای کلی پروژه پایه
از آنجایی که برخی از قسمت های این پروژه خارج از محدوده این کدلب در نظر گرفته می شوند، در کد شروع قرار می گیرند. ایده خوبی است که قبل از شروع به مرور مواردی که قبلاً در کد استارت وجود دارد، بپردازید تا از نحوه ساختاربندی چیزها مطلع شوید.
این کد پشتیبان می تواند به صورت محلی روی دستگاه شما اجرا شود، برای استفاده از آن نیازی به استقرار آن ندارید. با این حال، شما باید بتوانید از دستگاه توسعه خود (اندروید یا آیفون) به دستگاهی که سرور در آن اجرا می شود وصل شوید. برای آن، آنها باید در یک شبکه باشند و شما باید آدرس 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 را انتخاب کنید.
فایل JSON دانلود شده را در پوشه assets/
کپی کنید و نام آن را به service-account-firebase.json
تغییر دهید.
دسترسی به Google Play را تنظیم کنید
برای دسترسی به فروشگاه Play برای تأیید خریدها، باید یک حساب سرویس با این مجوزها ایجاد کنید و اعتبار JSON را برای آن دانلود کنید.
- به کنسول Google Play بروید و از صفحه همه برنامه ها شروع کنید.
- به تنظیمات > دسترسی به API بروید. در صورتی که کنسول Google Play درخواست میکند که یک پروژه موجود را ایجاد کنید یا به آن پیوند دهید، ابتدا این کار را انجام دهید و سپس به این صفحه بازگردید.
- قسمتی را که میتوانید حسابهای سرویس را تعریف کنید، پیدا کنید و روی ایجاد حساب سرویس جدید کلیک کنید.
- روی پیوند Google Cloud Platform در گفتگوی ظاهر شده کلیک کنید.
- پروژه خود را انتخاب کنید اگر آن را نمیبینید، مطمئن شوید که در فهرست کشویی حساب در بالا سمت راست به حساب Google صحیح وارد شدهاید.
- پس از انتخاب پروژه خود، روی + Create Service Account در نوار منوی بالا کلیک کنید.
- یک نام برای حساب سرویس ارائه دهید، به صورت اختیاری یک توضیح ارائه دهید تا به خاطر بیاورید که برای چه کاری است، و به مرحله بعدی بروید.
- نقش ویرایشگر را به حساب سرویس اختصاص دهید.
- جادوگر را تمام کنید، به صفحه دسترسی API در کنسول توسعه دهنده برگردید و روی Refresh service accounts کلیک کنید. شما باید حساب جدید خود را در لیست ببینید.
- روی اعطای دسترسی برای حساب سرویس جدید خود کلیک کنید.
- صفحه بعدی را به سمت پایین اسکرول کنید تا به بلوک داده های مالی برسید. هم مشاهده دادههای مالی، سفارشها و پاسخهای نظرسنجی لغو و هم مدیریت سفارشها و اشتراکها را انتخاب کنید.
- روی دعوت از کاربر کلیک کنید.
- اکنون که حساب راهاندازی شده است، فقط باید چند اعتبار ایجاد کنید. در کنسول ابری بازگردید، حساب سرویس خود را در لیست حسابهای سرویس پیدا کنید، روی سه نقطه عمودی کلیک کنید و مدیریت کلیدها را انتخاب کنید.
- یک کلید JSON جدید ایجاد کنید و آن را دانلود کنید.
- نام فایل دانلود شده را به
service-account-google-play.json,
تغییر دهید و آن را به فهرستassets/
منتقل کنید.
یک کار دیگر که باید انجام دهیم این است که lib/constants.dart,
را باز کرده و مقدار androidPackageId
را با شناسه بسته ای که برای برنامه اندروید خود انتخاب کرده اید جایگزین کنید.
دسترسی Apple App Store را تنظیم کنید
برای دسترسی به App Store برای تأیید خریدها، باید یک راز مشترک را تنظیم کنید:
- App Store Connect را باز کنید.
- به My Apps بروید و برنامه خود را انتخاب کنید.
- در پیمایش نوار کناری، به خریدهای درون برنامه > مدیریت بروید.
- در سمت راست بالای لیست ، روی برنامه مشترک برنامه خاص کلیک کنید.
- یک راز جدید ایجاد کنید و آن را کپی کنید.
-
lib/constants.dart,
را باز کنید و مقدارappStoreSharedSecret
را با راز مشترکی که تازه تولید کرده اید جایگزین کنید.
پرونده پیکربندی ثابت
قبل از اقدام ، اطمینان حاصل کنید که ثابت های زیر در پرونده 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
را با پیاده سازی های جداگانه برای هر فروشگاه تنظیم کنید.
با اضافه کردن یک پرونده 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');
}
}
کد فوق موارد زیر را انجام می دهد:
- یک نقطه پایانی پست را تعریف کنید که از برنامه ای که قبلاً ایجاد کرده اید فراخوانی خواهد شد.
- بارگیری JSON را رمزگشایی کنید و اطلاعات زیر را استخراج کنید:
-
userId
: در حال حاضر در شناسه کاربر وارد شده است -
source
: فروشگاه مورد استفاده ، یاapp_store
یاgoogle_play
. -
productData
: به دست آمده ازproductDataMap
که قبلاً ایجاد کرده اید. -
token
: شامل داده های تأیید برای ارسال به فروشگاه ها است. - بسته به منبع ، با روش
verifyPurchase
، یا برایGooglePlayPurchaseHandler
یاAppStorePurchaseHandler
تماس بگیرید. - اگر تأیید موفقیت آمیز بود ، روش
Response.ok
به مشتری باز می گرداند. - در صورت عدم موفقیت ، این روش
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 را ایجاد نکرده اید ، و همچنین هیچ رویدادی صورتحساب را منتشر نمی کنید. وقت آن است که این کار را تنظیم کنیم.
اول ، ایجاد یک میخانه/زیر موضوع:
- به صفحه Cloud Pub/Sub در کنسول Google Cloud مراجعه کنید.
- اطمینان حاصل کنید که در پروژه Firebase خود هستید و روی + ایجاد موضوع کلیک کنید.
- به موضوع جدید یک نام ، یکسان با مقدار تعیین شده برای
GOOGLE_PLAY_PUBSUB_BILLING_TOPIC
درconstants.ts
. در این حالت ، آن راplay_billing
نامگذاری کنید. اگر چیز دیگری را انتخاب کردید ، حتماًconstants.ts
را به روز کنید. موضوع را ایجاد کنید. - در لیست مباحث میخانه/فرعی خود ، برای موضوعی که اخیراً ایجاد کرده اید ، روی سه نقطه عمودی کلیک کنید و روی View Permissions کلیک کنید.
- در نوار کناری سمت راست ، Add Principal را انتخاب کنید.
- در اینجا ،
google-play-developer-notifications@system.gserviceaccount.com
را اضافه کرده و به آن نقش میخانه/زیر ناشر را اعطا کنید. - تغییرات اجازه را ذخیره کنید.
- نام موضوع موضوعی را که اخیراً ایجاد کرده اید کپی کنید.
- کنسول Play را دوباره باز کنید و برنامه خود را از لیست All Apps انتخاب کنید.
- به سمت پایین بروید و به کسب درآمد> Setup کسب درآمد بروید.
- موضوع کامل را پر کرده و تغییرات خود را ذخیره کنید.
اکنون همه رویدادهای صورتحساب 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,
));
}
}
}
}
این روش به شرح زیر است:
- لیست اشتراک های فعال را از Firestore با استفاده از iAprepository به دست می آورد.
- برای هر سفارش ، وضعیت اشتراک را به API سرور App Store درخواست می کند.
- آخرین معامله را برای آن خرید اشتراک بدست می آورد.
- تاریخ انقضا را بررسی می کند.
- وضعیت اشتراک در 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 را تنظیم کنید:
- وارد App Store Connect شوید و کاربران و دسترسی را انتخاب کنید.
- به نوع کلید> خرید درون برنامه بروید.
- برای اضافه کردن یک نسخه جدید ، روی نماد "Plus" ضربه بزنید.
- به عنوان مثال "کلید CodeLab" به آن بدهید.
- پرونده P8 حاوی کلید را بارگیری کنید.
- آن را در پوشه دارایی ها ، با نام
SubscriptionKey.p8
کپی کنید. - شناسه کلید را از کلید تازه ایجاد شده کپی کرده و آن را روی
appStoreKeyId
ثابت در پروندهlib/constants.dart
تنظیم کنید. - شناسه صادرکننده را درست در بالای لیست کلیدها کپی کرده و آن را در پرونده
lib/constants.dart
appStoreIssuerId
کنید.
خریدهای موجود در دستگاه را پیگیری کنید
امن ترین راه برای ردیابی خریدهای شما در سمت سرور است زیرا مشتری برای اطمینان از آن دشوار است ، اما شما باید راهی داشته باشید تا اطلاعات را به مشتری برگردانید تا برنامه بتواند بر روی اطلاعات وضعیت اشتراک عمل کند. با ذخیره خریدها در 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 را در آن پیدا کنید پوشه کامل
برای کسب اطلاعات بیشتر ، سایر CodeLabs Flutter را امتحان کنید.