1. Wprowadzenie
Ostatnia aktualizacja: 11.07.2023 r.
Dodanie zakupów w aplikacji do aplikacji Flutter wymaga poprawnego skonfigurowania Sklepu App i Sklepu Play, zweryfikowania zakupu oraz przyznania niezbędnych uprawnień, np. bonusów z subskrypcji.
W ramach tego ćwiczenia w Codelabs dodasz 3 typy zakupów w aplikacji do aplikacji (udostępnione przez Ciebie) i potwierdzisz te zakupy za pomocą backendu Dart z Firebase. Udostępniona aplikacja, Dash Clicker, zawiera grę, w której walutą jest maskotka Dash. Dodasz te opcje zakupu:
- Opcja wielokrotnego zakupu za 2000 kresek.
- Jednorazowy zakup ulepszenia, który pozwoli Ci zamienić starą wersję Dash w nowoczesną wersję.
- Subskrypcja, która podwaja liczbę kliknięć generowanych automatycznie.
Pierwsza opcja zakupu zapewnia użytkownikowi bezpośrednie korzyści w postaci 2000 kresek. Są one dostępne bezpośrednio dla użytkowników i można je kupić wiele razy. To właśnie produkty konsumpcyjne, ponieważ są spożywane bezpośrednio i można je wielokrotnie spożywać.
Druga opcja sprawi, że Dash będzie piękniejszy. Trzeba ją kupić tylko raz. Jest ona dostępna bezterminowo. Taki zakup określa się jako niemożliwy do zużycia, ponieważ aplikacja nie może go wykorzystać, ale jest ważny bezterminowo.
Trzecią i ostatnią opcją zakupu jest subskrypcja. Gdy subskrypcja jest aktywna, użytkownik szybciej uzyskuje dostęp do Dashe, ale gdy przestanie płacić za subskrypcję, korzyści też utracą.
Usługa backendu (dostępna również dla Ciebie) działa jako aplikacja Dart, weryfikuje, czy zakupy są dokonywane, i przechowuje je w Firestore. Firestore ułatwia proces, ale w aplikacji produkcyjnej możesz korzystać z dowolnego typu usługi backendu.
Co utworzysz
- Przedłużysz aplikację o obsługę zakupów produktów konsumpcyjnych i subskrypcji.
- Rozszerzysz też aplikację backendu DART do weryfikacji i przechowywania zakupionych elementów.
Czego się nauczysz
- Jak skonfigurować produkty do kupienia w App Store i Sklepie Play.
- Jak komunikować się ze sklepami w celu weryfikowania zakupów i przechowywania ich w Firestore.
- Jak zarządzać zakupami w aplikacji
Czego potrzebujesz
- Android Studio 4.1 lub nowszy
- Xcode 12 lub nowsza wersja (na potrzeby programowania na iOS)
- Pakiet SDK Flutter
2. Konfigurowanie środowiska programistycznego
Aby rozpocząć to ćwiczenie w Codelabs, pobierz kod i zmień identyfikator pakietu w przypadku iOS oraz nazwę pakietu na Androida.
Pobieranie kodu
Aby skopiować repozytorium GitHub z wiersza poleceń, użyj tego polecenia:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Jeśli masz zainstalowane narzędzie cli GitHub, użyj tego polecenia:
gh repo clone flutter/codelabs flutter-codelabs
Przykładowy kod zostanie skopiowany do katalogu flutter-codelabs
, który zawiera kod kolekcji ćwiczeń z programowania. Kod do tego ćwiczenia w Codelabs jest dostępny w języku flutter-codelabs/in_app_purchases
.
Struktura katalogów w flutter-codelabs/in_app_purchases
zawiera serię zrzutów ekranu, w których powinno znajdować się miejsce na końcu każdego nazwanego kroku. Kod startowy znajduje się w kroku 0, więc zlokalizowanie pasujących plików jest dziecinnie proste:
cd flutter-codelabs/in_app_purchases/step_00
Jeśli chcesz przejść do następnego kroku lub zobaczyć, jak powinien wyglądać dany krok, zajrzyj do katalogu o nazwie odpowiadającej interesującemu Cię kroku. Kod z ostatniego kroku znajduje się w folderze complete
.
Konfigurowanie projektu startowego
Otwórz projekt początkowy z step_00
w swoim ulubionym IDE. Do tworzenia zrzutów ekranu użyliśmy Androida Studio, ale świetnym rozwiązaniem jest też Visual Studio Code. W obu edytorze sprawdź, czy masz zainstalowane najnowsze wtyczki Dart i Flutter.
Aplikacje, które planujesz skonfigurować, będą musiały komunikować się z App Store i Sklepem Play, aby dowiedzieć się, które produkty są dostępne i w jakiej cenie. Każda aplikacja ma niepowtarzalny identyfikator. W sklepie App Store jest to identyfikator pakietu, a w Sklepie Play – identyfikator aplikacji. Identyfikatory te są zwykle tworzone w odwrotnym zapisie nazwy domeny. Na przykład do tworzenia aplikacji do zakupu w aplikacji dla flutter.dev użyjemy dev.flutter.inapppurchase
. Pomyśl o identyfikatorze swojej aplikacji i ustaw go teraz w ustawieniach projektu.
Najpierw skonfiguruj identyfikator pakietu dla iOS.
Po otwarciu projektu w Android Studio kliknij prawym przyciskiem myszy folder iOS, kliknij Flutter i otwórz moduł w aplikacji Xcode.
W strukturze folderów Xcode u góry znajduje się Projekt Runner, a cele Flutter, Runner i Products poniżej projektu Runner. Kliknij dwukrotnie Runner, aby edytować ustawienia projektu, a następnie kliknij Podpisanie Możliwości. Aby określić zespół, wpisz wybrany przed chwilą identyfikator pakietu w polu Zespół.
Możesz teraz zamknąć Xcode i wrócić do Android Studio, by dokończyć konfigurację dla Androida. Aby to zrobić, otwórz plik build.gradle
w katalogu android/app,
i zmień applicationId
(w wierszu 37 na zrzucie ekranu poniżej) na identyfikator aplikacji, taki jak identyfikator pakietu na iOS. Pamiętaj, że identyfikatory dla sklepów z iOS i Androidem nie muszą być identyczne, ale pozostawienie ich identycznych jest mniej podatne na błędy i dlatego w tym ćwiczeniu w programowaniu używamy również identycznych identyfikatorów.
3. Zainstaluj wtyczkę
W tej części ćwiczenia z programowania zainstalujesz wtyczkę in_app_purchase.
Dodaj zależność w specyfikacji pubspec
Dodaj in_app_purchase
do specyfikacji wydawcy, dodając in_app_purchase
do zależności w specyfikacji publik:
$ 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
..
Kliknij pub get, aby pobrać pakiet, lub uruchom polecenie flutter pub get
w wierszu poleceń.
4. Skonfiguruj App Store
Aby skonfigurować zakupy w aplikacji i przetestować je w systemie iOS, musisz utworzyć nową aplikację w App Store, a w niej utworzyć produkty, które można kupić. Nie musisz niczego publikować ani przesyłać aplikacji do sprawdzenia Apple. Aby to zrobić, musisz mieć konto dewelopera. Jeśli go nie masz, zarejestruj się w programie Apple dla deweloperów.
Umowy na płatne aplikacje
Aby korzystać z zakupów w aplikacji, musisz też mieć aktywną umowę na płatne aplikacje w App Store Connect. Wejdź na https://appstoreconnect.apple.com/ i kliknij Umowy, podatki i bankowość.
Tutaj znajdziesz umowy dotyczące bezpłatnych i płatnych aplikacji. Stan bezpłatnych aplikacji powinien być aktywny, a stan płatnych aplikacji powinien być nowy. Zapoznaj się z warunkami, zaakceptuj je i wpisz wszystkie wymagane informacje.
Po ustawieniu wszystkich opcji płatne aplikacje będą miały stan aktywny. To bardzo ważne, ponieważ bez aktywnej umowy nie możesz wypróbować zakupów w aplikacji.
Zarejestruj identyfikator aplikacji
Utwórz nowy identyfikator w portalu Apple dla deweloperów.
Wybierz identyfikatory aplikacji
Wybierz aplikację
Podaj opis i ustaw identyfikator pakietu tak, aby identyfikator pakietu miał taką samą wartość jak wcześniej ustawiona w XCode.
Więcej wskazówek o tworzeniu nowego identyfikatora aplikacji znajdziesz w Centrum pomocy konta dewelopera .
Tworzenie nowej aplikacji
Utwórz nową aplikację w App Store Connect, używając unikalnego identyfikatora pakietu.
Więcej wskazówek na temat tworzenia nowych aplikacji i zarządzania umowami znajdziesz w pomocy do App Store Connect.
Aby testować zakupy w aplikacji, potrzebujesz testowego użytkownika piaskownicy. Ten użytkownik testowy nie powinien być połączony z iTunes – służy tylko do testowania zakupów w aplikacji. Nie możesz użyć adresu e-mail, który jest już używany na koncie Apple. W sekcji Użytkownicy i dostęp kliknij Testerzy w sekcji Piaskownica, aby utworzyć nowe konto w trybie piaskownicy lub zarządzać dotychczasowymi identyfikatorami Apple ID piaskownicy.
Teraz możesz skonfigurować na iPhonie użytkownika sandbox. W tym celu wybierz kolejno Settings > (Ustawienia >) Sklep z aplikacjami > Konto w trybie piaskownicy.
Konfigurowanie zakupów w aplikacji
Następnie skonfigurujesz te 3 elementy, które można kupić:
dash_consumable_2k
: produkt konsumpcyjny, który można kupić wielokrotnie, a użytkownik otrzymuje 2000 kresek (waluty w aplikacji) na każdy zakup.dash_upgrade_3d
: nieużywana „aktualizacja” zakup, który można kupić tylko raz.dash_subscription_doubler
: subskrypcja, w ramach której użytkownik przyznaje użytkownikowi 2 razy więcej kresek na kliknięcie.
Wybierz Zakupy w aplikacji >”. Zarządzaj.
Twórz zakupy w aplikacji o określonych identyfikatorach:
- Skonfiguruj
dash_consumable_2k
jako Towar konsumpcyjny.
Użyj dash_consumable_2k
jako identyfikatora produktu. Nazwa referencyjna jest używana tylko w przypadku połączenia ze sklepem z aplikacjami. Wystarczy ustawić ją na dash consumable 2k
i dodać lokalizacje zakupu. Wywołaj zakup Spring is in the air
z opisem 2000 dashes fly out
.
- Skonfiguruj urządzenie
dash_upgrade_3d
jako nieużywane.
Użyj dash_upgrade_3d
jako identyfikatora produktu. Jako nazwę referencyjną ustaw dash upgrade 3d
i dodaj lokalizacje zakupu. Wywołaj zakup 3D Dash
z opisem Brings your dash back to the future
.
- Skonfiguruj
dash_subscription_doubler
jako automatycznie odnawianą subskrypcję.
Proces subskrypcji wygląda nieco inaczej. Najpierw ustaw nazwę referencyjną i identyfikator produktu:
Musisz teraz utworzyć grupę subskrypcji. Gdy w grupie jest wiele subskrypcji, użytkownik może w tym samym czasie subskrybować tylko jeden z nich, ale może łatwo przejść na wyższą lub niższą wersję. Po prostu zadzwoń do tej grupy: subscriptions
.
Następnie wpisz czas trwania subskrypcji i lokalizacje. Nazwij ją Jet Engine
i nadaj jej opisowi Doubles your clicks
. Kliknij Zapisz.
Po kliknięciu przycisku Zapisz dodaj cenę subskrypcji. Możesz wybrać dowolną cenę.
Na liście zakupów powinny być widoczne trzy zakupy:
5. Konfigurowanie Sklepu Play
Podobnie jak w przypadku App Store, musisz mieć też konto dewelopera w Sklepie Play. Jeśli go nie masz, zarejestruj się.
Tworzenie nowej aplikacji
Utwórz nową aplikację w Konsoli Google Play:
- Otwórz Konsolę Play.
- Wybierz Wszystkie aplikacje > Utwórz aplikację.
- Wybierz język domyślny i nazwij aplikację. Wpisz nazwę aplikacji, która ma wyświetlać się w Google Play. Możesz ją później zmienić.
- Określ, że aplikacja jest grą. Możesz to później zmienić.
- Określ, czy aplikacja ma być bezpłatna czy płatna.
- Dodaj adres e-mail, pod którym użytkownicy Sklepu Play mogą kontaktować się z Tobą w sprawie tej aplikacji.
- Wypełnij wytyczne dotyczące treści i deklaracje dotyczące przepisów eksportowych Stanów Zjednoczonych.
- Wybierz Utwórz aplikację.
Po utworzeniu aplikacji otwórz panel i wykonaj wszystkie zadania w sekcji Konfigurowanie aplikacji. W tym miejscu znajdziesz informacje o aplikacji, takie jak oceny treści czy zrzuty ekranu.
Podpisywanie zgłoszenia
Aby testować zakupy w aplikacji, musisz przesłać do Google Play co najmniej 1 kompilację.
W tym celu kompilacja wersji musi być podpisana czymś innym niż klucze debugowania.
Utwórz magazyn kluczy
Jeśli masz już magazyn kluczy, przejdź do następnego kroku. Jeśli tak nie jest, utwórz je, uruchamiając w wierszu poleceń podane niżej polecenie.
W systemie Mac lub Linux użyj tego polecenia:
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
W systemie Windows użyj tego polecenia:
keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias key
To polecenie zapisuje plik key.jks
w katalogu głównym. Jeśli chcesz zapisać plik w innym miejscu, zmień argument przekazywany do parametru -keystore
. Zachowaj
keystore
file private; nie sprawdzaj ich w kontroli źródeł publicznych.
Dodaj odwołanie do magazynu kluczy z aplikacji
Utwórz plik o nazwie <your app dir>/android/key.properties
, który zawiera odwołanie do Twojego magazynu kluczy:
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>
Konfigurowanie logowania Gradle
Skonfiguruj podpisywanie aplikacji, edytując plik <your app dir>/android/app/build.gradle
.
Dodaj informacje o magazynie kluczy z pliku właściwości przed blokiem android
:
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
// omitted
}
Wczytaj plik key.properties
do obiektu keystoreProperties
.
Dodaj ten kod przed blokiem 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
}
}
Skonfiguruj blok signingConfigs
w pliku build.gradle
modułu, podając informacje o konfiguracji podpisywania:
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
Kompilacje wersji Twojej aplikacji będą teraz podpisywane automatycznie.
Więcej informacji o podpisywaniu aplikacji znajdziesz w artykule Podpisywanie aplikacji na stronie developer.android.com.
Prześlij swoją pierwszą kompilację
Gdy skonfigurujesz aplikację do podpisywania, możesz ją skompilować, uruchamiając polecenie:
flutter build appbundle
To polecenie domyślnie generuje kompilację wersji, a dane wyjściowe znajdziesz w <your app dir>/build/app/outputs/bundle/release/
W panelu w Konsoli Google Play kliknij Wersja > Testowanie > test zamknięty i utwórz nową wersję do testów zamkniętych.
W przypadku tych ćwiczeń w programie musisz pozostać przy podpisywaniu aplikacji przez Google, więc kliknij Dalej w sekcji Podpisywanie aplikacji przez Google Play, aby wyrazić zgodę.
Następnie prześlij pakiet aplikacji app-release.aab
wygenerowany za pomocą polecenia Build.
Kliknij kolejno Zapisz i Sprawdzanie wersji.
Na koniec kliknij Rozpocznij wdrażanie do testów wewnętrznych, aby aktywować wersję do testów wewnętrznych.
Konfigurowanie użytkowników testowych
Aby móc testować zakupy w aplikacji, musisz dodać w Konsoli Google Play konta Google testerów w 2 lokalizacjach:
- Na konkretną ścieżkę testu (test wewnętrzny)
- Jako tester licencji
Zacznij od dodania testera do ścieżki testu wewnętrznego. Wróć do Wersja > Testowanie > testów wewnętrznych i kliknij kartę Testerzy.
Utwórz nową listę e-mailową, klikając Utwórz listę e-mailową. Nazwij listę i dodaj adresy e-mail kont Google, które mają mieć dostęp do testowania zakupów w aplikacji.
Następnie zaznacz pole wyboru listy i kliknij Zapisz zmiany.
Następnie dodaj testerów licencji:
- Wróć do widoku Wszystkie aplikacje w Konsoli Google Play.
- Wybierz kolejno Ustawienia > Testowanie licencji.
- Dodaj te same adresy e-mail testerów, którzy mają mieć możliwość testowania zakupów w aplikacji.
- Ustaw Odpowiedź dotyczącą licencji na
RESPOND_NORMALLY
. - Kliknij Zapisz zmiany.
Konfigurowanie zakupów w aplikacji
Teraz skonfigurujesz elementy, które można kupić w aplikacji.
Podobnie jak w przypadku sklepu z aplikacjami, musisz zdefiniować trzy różne zakupy:
dash_consumable_2k
: produkt konsumpcyjny, który można kupić wielokrotnie, a użytkownik otrzymuje 2000 kresek (waluty w aplikacji) na każdy zakup.dash_upgrade_3d
: nieużywana „aktualizacja” zakup, który można kupić tylko raz.dash_subscription_doubler
: subskrypcja, w ramach której użytkownik przyznaje użytkownikowi 2 razy więcej kresek na kliknięcie.
Najpierw dodaj produkty zużywalne i nieużywane.
- Otwórz Konsolę Google Play i wybierz aplikację.
- Przejdź do sekcji Zarabianie >”. Produkty > Produkty w aplikacji.
- Kliknij Utwórz produkt.
- Wpisz wszystkie wymagane informacje o produkcie. Upewnij się, że identyfikator produktu jest dokładnie taki sam jak identyfikator, którego zamierzasz użyć.
- Kliknij Zapisz.
- Kliknij Aktywuj.
- Powtórz ten proces w przypadku „aktualizacji”, która nie jest zużywana. zakup.
Następnie dodaj subskrypcję:
- Otwórz Konsolę Google Play i wybierz aplikację.
- Przejdź do sekcji Zarabianie >”. Produkty > Subskrypcje.
- Kliknij Utwórz subskrypcję.
- Wpisz wszystkie wymagane informacje o subskrypcji. Upewnij się, że identyfikator produktu jest dokładnie taki sam jak identyfikator, którego chcesz użyć.
- Kliknij Zapisz.
Twoje zakupy powinny być teraz skonfigurowane w Konsoli Play.
6. Skonfiguruj Firebase
W ramach tego ćwiczenia w Codelabs wykorzystasz usługę backendu do weryfikowania i śledzenia zakupów.
Korzystanie z usługi backendu ma kilka zalet:
- Transakcje możesz weryfikować w bezpieczny sposób.
- Możesz reagować na zdarzenia związane z płatnościami ze sklepów z aplikacjami.
- Zakupy możesz śledzić w bazie danych.
- Użytkownicy nie będą mogli nakłonić Twojej aplikacji do udostępnienia funkcji premium, przewijając do tyłu zegar systemowy.
Usługę backendu można skonfigurować na wiele sposobów, ale możesz to zrobić za pomocą funkcji w Cloud Functions i Firestore, korzystając z opracowanej przez Google Firebase.
Uznaje się, że pisanie backendu nie obejmuje tego ćwiczenia z programowania, dlatego na początek kod startowy zawiera już projekt Firebase, który obsługuje podstawowe zakupy.
Wtyczki Firebase są też dołączone do aplikacji startowej.
Musisz jeszcze tylko utworzyć własny projekt Firebase, skonfigurować aplikację i backend dla Firebase, a na koniec wdrożyć backend.
Tworzenie projektu Firebase
Otwórz konsolę Firebase i utwórz nowy projekt Firebase. W tym przykładzie użyj nazwy projektu Dash Clicker.
W aplikacji backendu musisz powiązać zakupy z konkretnym użytkownikiem, dlatego potrzebujesz uwierzytelnienia. Wykorzystaj do tego moduł uwierzytelniania Firebase z logowaniem przez Google.
- W panelu Firebase otwórz Uwierzytelnianie i włącz tę opcję w razie potrzeby.
- Otwórz kartę Metoda logowania i włącz dostawcę logowania Google.
Ponieważ będziesz używać bazy danych Firestore Firebase, także ją włącz.
Ustaw reguły Cloud Firestore w następujący sposób:
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
}
}
}
Konfigurowanie Firebase na potrzeby Flutter
Zalecanym sposobem zainstalowania Firebase w aplikacji Flutter jest użycie interfejsu wiersza poleceń FlutterFire. Postępuj zgodnie z instrukcjami na stronie konfiguracji.
Po uruchomieniu Flutterfire Configure wybierz projekt utworzony w poprzednim kroku.
$ 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>
Następnie włącz iOS i Androida, wybierając te platformy.
? Which platforms should your configuration support (use arrow keys & space to select)? ›
✔ android
✔ ios
macos
web
Gdy pojawi się prośba o zastąpienie pliku firebase_options.dart, wybierz Tak.
? Generated FirebaseOptions file lib/firebase_options.dart already exists, do you want to override it? (y/n) › yes
Konfigurowanie Firebase na Androida: dalsze kroki
W panelu Firebase kliknij Przegląd projektu, kliknij Ustawienia i wybierz kartę Ogólne.
Przewiń w dół do sekcji Twoje aplikacje i wybierz aplikację dashclicker (android).
Aby zezwolić na logowanie się przez Google w trybie debugowania, musisz podać odcisk cyfrowy SHA-1 swojego certyfikatu debugowania.
Pobierz hasz certyfikatu podpisywania na potrzeby debugowania
W katalogu głównym projektu aplikacji Flutter zmień katalog na folder android/
, a następnie wygeneruj raport podpisywania.
cd android ./gradlew :app:signingReport
Pojawi się obszerna lista kluczy podpisywania. Szukasz skrótu certyfikatu debugowania, więc szukaj certyfikatu z właściwościami Variant
i Config
ustawionymi na wartość debug
. Magazyn kluczy prawdopodobnie znajduje się w folderze głównym pod adresem .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
Skopiuj hasz SHA-1 i wypełnij ostatnie pole w oknie modalnym przesyłania aplikacji.
Konfigurowanie Firebase na iOS: dalsze kroki
Otwórz: ios/Runnder.xcworkspace
za pomocą aplikacji Xcode
. Możesz też użyć wybranego środowiska IDE.
W VSCode kliknij prawym przyciskiem myszy folder ios/
, a potem kliknij open in xcode
.
W Android Studio kliknij prawym przyciskiem myszy folder ios/
, a następnie kliknij flutter
i opcję open iOS module in Xcode
.
Aby umożliwić logowanie przez Google na urządzeniach z iOS, dodaj opcję konfiguracji CFBundleURLTypes
do plików kompilacji plist
. (Więcej informacji znajdziesz w dokumentacji pakietu google_sign_in
). W tym przypadku pliki to ios/Runner/Info-Debug.plist
i ios/Runner/Info-Release.plist
.
Para klucz-wartość została już dodana, ale należy zastąpić ich wartości:
- Pobierz wartość pola
REVERSED_CLIENT_ID
z plikuGoogleService-Info.plist
bez otaczającego go elementu<string>..</string>
. - Zastąp wartość w plikach
ios/Runner/Info-Debug.plist
iios/Runner/Info-Release.plist
w kluczuCFBundleURLTypes
.
<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>
To już koniec konfiguracji Firebase.
7. Odsłuchaj najnowsze informacje o zakupach
W tej części ćwiczenia w Codelabs przygotujesz aplikację do kupowania produktów. Ten proces obejmuje nasłuchiwanie aktualizacji zakupów i błędów po uruchomieniu aplikacji.
Słuchaj aktualności dotyczących zakupów
W narzędziu main.dart,
znajdź widżet MyHomePage
, który zawiera element Scaffold
z wartością BottomNavigationBar
obejmującą 2 strony. Na tej stronie są też tworzone 3 elementy Provider
dla DashCounter
, DashUpgrades,
i DashPurchases
. DashCounter
śledzi bieżącą liczbę kresek i automatycznie je zwiększa. Uaktualnieniami, które możesz kupić za pomocą Dashes, zarządza DashUpgrades
. To ćwiczenie w programie koncentruje się na tych zagadnieniach: DashPurchases
.
Obiekt dostawcy jest domyślnie definiowany przy pierwszym żądaniu tego obiektu. Ten obiekt nasłuchuje aktualizacji zakupów bezpośrednio po uruchomieniu aplikacji, więc wyłącz leniwe ładowanie w tym obiekcie za pomocą polecenia lazy: false
:
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
),
lazy: false,
),
Potrzebujesz też instancji InAppPurchaseConnection
. Aby jednak utrzymać możliwość testowania aplikacji, musisz mieć jakiś sposób na imitowanie połączenia. W tym celu utwórz metodę instancji, którą można zastąpić w teście, i dodaj ją do 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!;
}
}
Jeśli chcesz, aby nadal działał, musisz wprowadzić niewielkie zmiany w teście. Pełny kod pakietu TestIAPConnection
znajdziesz na stronie widget_test.dart na GitHubie.
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);
});
}
W aplikacji lib/logic/dash_purchases.dart
znajdź kod dla: DashPurchases ChangeNotifier
. Obecnie do zakupionych kropek możesz dodać tylko DashCounter
.
Dodaj usługę subskrypcji strumienia _subscription
(typu StreamSubscription<List<PurchaseDetails>> _subscription;
), IAPConnection.instance,
i importy. Powstały kod powinien wyglądać tak:
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);
}
Słowo kluczowe late
zostało dodane do elementu _subscription
, ponieważ w konstruktorze zainicjowano element _subscription
. Ten projekt jest skonfigurowany tak, aby domyślnie nie dopuszczać do wartości null (NNBD), co oznacza, że właściwości, które nie są zadeklarowane do wartości null, muszą mieć wartość niepustą. Kwalifikator late
pozwala opóźnić zdefiniowanie tej wartości.
W konstruktorze pobierz obiekt purchaseUpdatedStream
i zacznij słuchać strumienia. W metodzie dispose()
anuluj subskrypcję transmisji strumieniowej.
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
}
}
Teraz aplikacja otrzymuje aktualizacje dotyczące zakupu, więc w następnej sekcji dokonasz zakupu.
Zanim przejdziesz dalej, przeprowadź testy na urządzeniu „flutter test"
”, aby sprawdzić, czy wszystko jest prawidłowo skonfigurowane.
$ flutter test
00:01 +1: All tests passed!
8. dokonywać zakupów;
W tej części tego ćwiczenia w Codelabs zastąpisz obecne modele produktów prawdziwymi produktami, które będzie można kupić. Te produkty są wczytywane ze sklepów, wyświetlane na liście i kupowane po kliknięciu produktu.
Dostosowanie PurchasableProduct
PurchasableProduct
wyświetla symulowaną usługę. Zaktualizuj ją, tak aby zawierała rzeczywistą treść, zastępując klasę PurchasableProduct
w purchasable_product.dart
tym kodem:
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;
}
W aplikacji dash_purchases.dart,
usuń fikcyjne zakupy i zastąp je pustą listą (List<PurchasableProduct> products = [];
)
Wczytaj dostępne zakupy
Aby umożliwić użytkownikowi dokonanie zakupu, wczytaj informacje o zakupach ze sklepu. Najpierw sprawdź, czy sklep jest dostępny. Jeśli sklep jest niedostępny, ustawienie storeState
na notAvailable
powoduje wyświetlenie użytkownikowi komunikatu o błędzie.
lib/logic/dash_purchases.dart
Future<void> loadPurchases() async {
final available = await iapConnection.isAvailable();
if (!available) {
storeState = StoreState.notAvailable;
notifyListeners();
return;
}
}
Gdy sklep będzie dostępny, wczytaj dostępne zakupy. Przy poprzedniej konfiguracji Firebase powinny pojawić się storeKeyConsumable
, storeKeySubscription,
i storeKeyUpgrade
. Jeśli oczekiwany zakup jest niedostępny, wydrukuj te informacje w konsoli. możesz też wysłać te informacje do usługi backendu.
Metoda await iapConnection.queryProductDetails(ids)
zwraca zarówno identyfikatory, których nie znaleziono, jak i znalezione produkty, które można kupić. Aby zaktualizować interfejs, użyj pola productDetails
w odpowiedzi, a w polu StoreState
ustaw wartość 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();
}
Wywołaj w konstruktorze funkcję loadPurchases()
:
lib/logic/dash_purchases.dart
DashPurchases(this.counter) {
final purchaseUpdated = iapConnection.purchaseStream;
_subscription = purchaseUpdated.listen(
_onPurchaseUpdate,
onDone: _updateStreamOnDone,
onError: _updateStreamOnError,
);
loadPurchases();
}
Na koniec zmień wartość pola storeState
z StoreState.available
na StoreState.loading:
lib/logic/dash_purchases.dart
StoreState storeState = StoreState.loading;
Wyświetlanie produktów, które można kupić
Weźmy na przykład plik purchase_page.dart
. Widżet PurchasePage
pokazuje wartości _PurchasesLoading
, _PurchaseList,
lub _PurchasesNotAvailable,
w zależności od wartości StoreState
. Widżet pokazuje też wcześniejsze zakupy użytkownika, które zostaną użyte w następnym kroku.
Widżet _PurchaseList
pokazuje listę produktów, które można kupić, i wysyła żądanie zakupu do obiektu 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(),
);
}
}
Jeśli dostępne produkty są prawidłowo skonfigurowane, powinny być widoczne w sklepach na Androida i iOS. Pamiętaj, że gdy informacje o zakupach zostaną wprowadzone w odpowiednich konsolach, może minąć trochę czasu.
Wróć do dash_purchases.dart
i zaimplementuj funkcję kupowania produktu. Wystarczy oddzielić materiały eksploatacyjne od tych, które się nie zużywają. Przejście na wyższą wersję i usługi objęte subskrypcją nie są produktami zużywanymi.
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');
}
}
Zanim przejdziesz dalej, utwórz zmienną _beautifiedDashUpgrade
i zaktualizuj metodę pobierania beautifiedDash
, aby się do niej odwoływał.
lib/logic/dash_purchases.dart
bool get beautifiedDash => _beautifiedDashUpgrade;
bool _beautifiedDashUpgrade = false;
Metoda _onPurchaseUpdate
odbiera aktualizacje dotyczące zakupu, aktualizuje stan produktu wyświetlany na stronie zakupu i stosuje zakup do logiki licznika. Po dokonaniu zakupu musisz zadzwonić do sklepu completePurchase
, aby poinformować sprzedawcę, że transakcja przebiega prawidłowo.
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. Konfigurowanie backendu
Zanim przejdziesz do śledzenia i weryfikowania zakupów, skonfiguruj backend Dart, aby to ułatwić.
W tej sekcji wykonaj czynności z poziomu folderu dart-backend/
jako katalogu głównego.
Musisz mieć zainstalowane te narzędzia:
Omówienie projektu podstawowego
Ponieważ niektóre części tego projektu nie są objęte tymi ćwiczeniami z programowania, są one uwzględnione w kodzie startowym. Zanim zaczniesz, przejrzyj zawartość kodu startowego, aby zorientować się, jak będziesz budować elementy.
Ten kod backendu może działać lokalnie na Twoim komputerze. Nie musisz go wdrażać, aby z niego korzystać. Musisz jednak mieć możliwość połączenia się z urządzenia z Androidem lub iPhone'a z komputera, na którym będzie działać serwer. W tym celu muszą być w tej samej sieci, a Ty musisz znać adres IP komputera.
Spróbuj uruchomić serwer za pomocą tego polecenia:
$ dart ./bin/server.dart
Serving at http://0.0.0.0:8080
Backend Dart używa shelf
i shelf_router
do obsługi punktów końcowych interfejsu API. Domyślnie serwer nie udostępnia żadnych tras. Później utworzysz trasę do weryfikacji zakupów.
Jedna z części, która jest już uwzględniona w kodzie startowym, to IapRepository
w wierszu lib/iap_repository.dart
. Ponieważ nauka korzystania z Firestore lub baz danych w ogóle nie jest istotna w przypadku tego ćwiczenia z programowania, kod startowy zawiera funkcje tworzenia lub aktualizowania zakupów w Firestore, a także wszystkie klasy dla tych zakupów.
Konfigurowanie dostępu do Firebase
Aby uzyskać dostęp do Firebase Firestore, potrzebujesz klucza dostępu do konta usługi. Wygeneruj klucz, otwierając ustawienia projektu Firebase, przechodząc do sekcji Konta usługi i wybierając Wygeneruj nowy klucz prywatny.
Skopiuj pobrany plik JSON do folderu assets/
i zmień jego nazwę na service-account-firebase.json
.
Konfigurowanie dostępu do Google Play
Aby uzyskać dostęp do Sklepu Play na potrzeby weryfikacji zakupów, musisz wygenerować konto usługi z tymi uprawnieniami i pobrać powiązane z nim dane logowania w formacie JSON.
- Otwórz Konsolę Google Play i od strony Wszystkie aplikacje.
- Kliknij Konfiguracja > Interfejs API. Jeśli Konsola Google Play poprosi o utworzenie istniejącego projektu lub o połączenie z nim, najpierw zrób to, a potem wróć na tę stronę.
- Znajdź sekcję, w której możesz zdefiniować konta usługi, i kliknij Utwórz nowe konto usługi.
- W wyświetlonym oknie kliknij link Google Cloud Platform.
- Wybierz projekt. Jeśli go nie widzisz, sprawdź, czy korzystasz z właściwego konta Google z listy Konto w prawym górnym rogu.
- Po wybraniu projektu kliknij + Utwórz konto usługi na pasku menu u góry.
- Podaj nazwę konta usługi (opcjonalnie możesz podać opis, dzięki któremu zapamiętasz, do czego służy), i przejdź do następnego kroku.
- Przypisz do konta usługi rolę Edytujący.
- Zakończ pracę w kreatorze, wróć na stronę Dostęp do interfejsu API w konsoli programisty i kliknij Odśwież konta usługi. Nowo utworzone konto powinno pojawić się na liście.
- Kliknij Przyznaj dostęp obok nowego konta usługi.
- Przewiń następną stronę w dół do bloku Dane finansowe. Wybierz Wyświetlanie danych finansowych, zamówień i odpowiedzi z ankiety na temat anulowania oraz Zarządzaj zamówieniami i subskrypcjami.
- Kliknij Zaproś użytkownika.
- Teraz, gdy konto jest już skonfigurowane, musisz tylko wygenerować kilka danych logowania. Wróć do konsoli Cloud, znajdź swoje konto usługi na liście kont usługi, kliknij 3 pionowe kropki i wybierz Zarządzaj kluczami.
- Utwórz nowy klucz JSON i pobierz go.
- Zmień nazwę pobranego pliku na
service-account-google-play.json,
i przenieś go do kataloguassets/
.
Musimy jeszcze otworzyć plik lib/constants.dart,
i zastąpić wartość androidPackageId
identyfikatorem pakietu wybranym dla aplikacji na Androida.
Konfigurowanie dostępu do Apple App Store
Aby uzyskać dostęp do App Store na potrzeby weryfikacji zakupów, musisz skonfigurować wspólny tajny klucz:
- Otwórz App Store Connect.
- Otwórz Moje aplikacje i wybierz swoją aplikację.
- Na pasku bocznym wybierz Zakupy w aplikacji > Zarządzaj.
- W prawym górnym rogu listy kliknij Wspólny tajny klucz – specyficzny dla aplikacji.
- Wygeneruj nowy obiekt tajny i skopiuj go.
- Otwórz
lib/constants.dart,
i zastąp wartośćappStoreSharedSecret
wybranym przed chwilą udostępnionym tajnym kluczem.
Plik konfiguracji stałej
Zanim przejdziesz dalej, upewnij się, że w pliku lib/constants.dart
są skonfigurowane te stałe:
androidPackageId
: identyfikator pakietu używany na Androidzie. np.com.example.dashclicker
appStoreSharedSecret
: udostępniony tajny klucz umożliwiający dostęp do App Store Connect w celu weryfikacji zakupów.bundleId
: identyfikator pakietu używany w iOS. np.com.example.dashclicker
Na razie możesz zignorować pozostałe stałe.
10. Weryfikowanie zakupów
Ogólny proces weryfikacji zakupów jest podobny w iOS i Androidzie.
W przypadku obu sklepów aplikacja otrzymuje token po dokonaniu zakupu.
Aplikacja wysyła token do usługi backendu, która z kolei weryfikuje zakup na serwerach odpowiednich sklepów przy użyciu podanego tokena.
Usługa backendu może następnie zapisać zakup i odpowiedzieć aplikacji, czy zakup był prawidłowy.
Jeśli usługa backendu przeprowadza weryfikację w magazynach, a nie w aplikacji uruchomionej na urządzeniu użytkownika, możesz uniemożliwić użytkownikowi uzyskanie dostępu do funkcji premium, na przykład przez cofnięcie zegara systemowego.
Konfiguracja Flutter
Konfigurowanie uwierzytelniania
Wysyłając informacje o zakupach do usługi backendu, musisz upewnić się, że użytkownik jest uwierzytelniany podczas dokonywania zakupu. Większość zasad uwierzytelniania została już dodana do projektu startowego, musisz tylko upewnić się, że PurchasePage
pokazuje przycisk logowania, gdy użytkownik nie jest jeszcze zalogowany. Dodaj ten kod na początku metody kompilacji PurchasePage
:
lib/pages/purchase_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
Punkt końcowy weryfikacji wywołania z aplikacji
W aplikacji utwórz funkcję _verifyPurchase(PurchaseDetails purchaseDetails)
, która wywołuje punkt końcowy /verifypurchase
w backendzie Dart za pomocą wywołania HTTP post.
Wyślij wybrany sklep (google_play
w przypadku Sklepu Play lub app_store
w przypadku App Store), serverVerificationData
oraz productID
. Serwer zwraca kod stanu wskazujący, czy zakup został zweryfikowany.
W wartościach stałych aplikacji skonfiguruj adres IP serwera na adres IP komputera lokalnego.
lib/logic/dash_purchases.dart
FirebaseNotifier firebaseNotifier;
DashPurchases(this.counter, this.firebaseNotifier) {
// omitted
}
Dodaj element firebaseNotifier
z utworzeniem elementu DashPurchases
w: main.dart:
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
context.read<FirebaseNotifier>(),
),
lazy: false,
),
Dodaj w FirebaseNotifier kod pobierający dla użytkownika, aby móc przekazać identyfikator użytkownika do funkcji weryfikacji zakupów.
lib/logic/firebase_notifier.dart
User? get user => FirebaseAuth.instance.currentUser;
Dodaj funkcję _verifyPurchase
do klasy DashPurchases
. Ta funkcja async
zwraca wartość logiczną wskazującą, czy zakup został zweryfikowany.
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;
}
}
Wywołaj funkcję _verifyPurchase
w _handlePurchase
tuż przed dokonaniem zakupu. Zakup należy zastosować tylko wtedy, gdy zostanie zweryfikowany. W aplikacji produkcyjnej możesz to określić dalej, aby na przykład zastosować subskrypcję próbną, gdy sklep jest tymczasowo niedostępny. W tym przykładzie należy to zrobić w prosty sposób i zastosować zakup dopiero wtedy, gdy zostanie zweryfikowany.
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);
}
}
W aplikacji są teraz gotowe do weryfikacji zakupów.
Konfigurowanie usługi backendu
Następnie skonfiguruj funkcję w Cloud Functions do weryfikowania zakupów w backendzie.
Tworzenie modułów obsługi zakupów
Ponieważ proces weryfikacji w przypadku obu sklepów jest niemal identyczny, skonfiguruj abstrakcyjną klasę PurchaseHandler
z osobnymi implementacjami dla każdego sklepu.
Zacznij od dodania pliku purchase_handler.dart
do folderu lib/
, w którym definiujesz abstrakcyjną klasę PurchaseHandler
z 2 abstrakcyjnymi metodami weryfikacji 2 różnych rodzajów zakupów: subskrypcji i innych.
lib/purchase_handler.dart
import 'products.dart';
/// Generic purchase handler,
/// must be implemented for Google Play and Apple Store
abstract class PurchaseHandler {
/// Verify if non-subscription purchase (aka consumable) is valid
/// and update the database
Future<bool> handleNonSubscription({
required String userId,
required ProductData productData,
required String token,
});
/// Verify if subscription purchase (aka non-consumable) is valid
/// and update the database
Future<bool> handleSubscription({
required String userId,
required ProductData productData,
required String token,
});
}
Jak widać, każda metoda wymaga trzech parametrów:
userId:
Identyfikator zalogowanego użytkownika. Dzięki temu możesz powiązać z nim zakupy.productData:
Dane o produkcie. Definiujesz to za chwilę.token:
Token przekazany użytkownikowi przez sklep.
Aby ułatwić sobie korzystanie z tych modułów obsługi zakupów, dodaj metodę verifyPurchase()
, która może być używana zarówno w przypadku subskrypcji, jak i pozostałych:
lib/purchase_handler.dart
/// Verify if purchase is valid and update the database
Future<bool> verifyPurchase({
required String userId,
required ProductData productData,
required String token,
}) async {
switch (productData.type) {
case ProductType.subscription:
return handleSubscription(
userId: userId,
productData: productData,
token: token,
);
case ProductType.nonSubscription:
return handleNonSubscription(
userId: userId,
productData: productData,
token: token,
);
}
}
Teraz możesz wywołać funkcję verifyPurchase
w obu przypadkach, ale nadal będziesz mieć osobną implementację.
Klasa ProductData
zawiera podstawowe informacje o różnych produktach do kupienia, w tym ich identyfikator (czasami nazywany też SKU) i ProductType
.
lib/products.dart
class ProductData {
final String productId;
final ProductType type;
const ProductData(this.productId, this.type);
}
ProductType
może być subskrypcją lub nie.
lib/products.dart
enum ProductType {
subscription,
nonSubscription,
}
Lista produktów jest zdefiniowana jako mapa w tym samym pliku.
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,
),
};
Następnie określ implementacje zastępcze dla Sklepu Google Play i Apple App Store. Zacznij korzystać z Google Play:
Utwórz obiekt lib/google_play_purchase_handler.dart
i dodaj klasę, która rozszerza napisane właśnie 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;
}
}
Na razie zwraca true
w przypadku metod modułu obsługi. skontaktujesz się z nim później.
Jak możesz zauważyć, konstruktor używa instancji IapRepository
. Moduł obsługi zakupów używa tej instancji do przechowywania informacji o zakupach w Firestore na później. Do komunikacji z Google Play służy AndroidPublisherApi
.
Następnie wykonaj te same czynności w przypadku modułu obsługi sklepu z aplikacjami. Utwórz lib/app_store_purchase_handler.dart
i dodaj klasę, która ponownie stanowi rozszerzenie elementu 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;
}
}
Świetnie. Masz teraz dwa moduły obsługi zakupów. Teraz utworzymy punkt końcowy interfejsu API weryfikacji zakupów.
Korzystanie z modułów obsługi zakupów
Otwórz bin/server.dart
i utwórz punkt końcowy interfejsu API za pomocą 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');
}
}
Powyższy kod wykonuje następujące czynności:
- Zdefiniuj punkt końcowy POST, który będzie wywoływany z wcześniej utworzonej aplikacji.
- Zdekoduj ładunek JSON i wyodrębnij te informacje:
userId
: identyfikator aktualnie zalogowanego użytkownikasource
: używany sklep,app_store
lubgoogle_play
.productData
: uzyskane z utworzonego wcześniej zasobuproductDataMap
.token
: zawiera dane weryfikacyjne, które należy wysłać do sklepów.- Wywołaj metodę
verifyPurchase
dla metodyGooglePlayPurchaseHandler
lubAppStorePurchaseHandler
, w zależności od źródła. - Jeśli weryfikacja się udała, metoda zwraca klientowi
Response.ok
. - Jeśli weryfikacja się nie powiedzie, metoda zwróci klientowi
Response.internalServerError
.
Po utworzeniu punktu końcowego interfejsu API musisz skonfigurować 2 moduły obsługi zakupu. Wymaga to wczytania kluczy konta usługi uzyskanych w poprzednim kroku i skonfigurowania dostępu do różnych usług, w tym do interfejsów Android Publisher API i Firebase Firestore API. Następnie utwórz dwa moduły obsługi zakupu z różnymi zależnościami:
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,
),
};
}
Weryfikowanie zakupów na Androidzie: wdrażanie narzędzia do obsługi zakupów
Następnie kontynuuj wdrażanie modułu obsługi zakupów w Google Play.
Google udostępnia już pakiety DART do interakcji z interfejsami API, które są potrzebne do weryfikacji zakupów. Zainicjowano je w pliku server.dart
i teraz używasz ich w klasie GooglePlayPurchaseHandler
.
Zaimplementuj moduł obsługi zakupów bez subskrypcji:
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;
}
Moduł obsługi zakupu subskrypcji możesz zaktualizować w podobny sposób:
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;
}
}
Dodaj metodę, która ułatwi Ci analizowanie identyfikatorów zamówień, a także 2 metody analizowania stanu zakupu.
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,
};
}
Twoje zakupy w Google Play powinny być teraz zweryfikowane i zapisane w bazie danych.
Następnie przejdź do zakupów w App Store na iOS.
Weryfikowanie zakupów w systemie iOS: zaimplementuj moduł obsługi zakupów
Do weryfikacji zakupów w App Store dostępny jest pakiet Dart innej firmy o nazwie app_store_server_sdk
, który ułatwia cały proces.
Zacznij od utworzenia instancji ITunesApi
. Użyj konfiguracji piaskownicy i włącz logowanie, aby ułatwić debugowanie błędów.
lib/app_store_purchase_handler.dart
final _iTunesAPI = ITunesApi(
ITunesHttpClient(
ITunesEnvironment.sandbox(),
loggingEnabled: true,
),
);
Teraz, w przeciwieństwie do interfejsów API Google Play, App Store używa tych samych punktów końcowych dla zarówno subskrypcji, jak i pozostałych subskrypcji. Oznacza to, że możesz używać tej samej logiki w przypadku obu modułów obsługi. Scal je, aby nazywały tę samą implementację:
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 {
//..
}
Teraz zaimplementuj 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;
}
}
Twoje zakupy w App Store powinny być teraz zweryfikowane i zapisane w bazie danych.
Uruchamianie backendu
W tym momencie możesz uruchomić dart bin/server.dart
, aby obsługiwać punkt końcowy /verifypurchase
.
$ dart bin/server.dart
Serving at http://0.0.0.0:8080
11. Monitoruj zakupy
Zalecany sposób śledzenia działań użytkowników są dostępne w usłudze backendu. Wynika to z tego, że backend może reagować na zdarzenia z magazynu, przez co jest mniej podatny na natknięcie się na nieaktualne informacje z powodu pamięci podręcznej. Jest też mniej podatny na modyfikację.
Najpierw skonfiguruj przetwarzanie zdarzeń sklepu w backendzie z wygenerowanym przez siebie backendem Dart.
Przetwarzanie zdarzeń sklepu w backendzie
Sklepy mogą informować Twój backend o wszelkich zdarzeniach rozliczeniowych, na przykład o odnowieniu subskrypcji. Możesz przetwarzać te zdarzenia w backendzie, aby zakupy w bazie danych były aktualne. W tej sekcji skonfiguruj je dla Sklepu Google Play i Apple App Store.
Przetwarzanie zdarzeń płatności w Google Play
Google Play udostępnia zdarzenia rozliczeniowe za pomocą tzw. tematu Cloud Pub/Sub. Są to zasadniczo kolejki wiadomości, w których można publikować i odbierać wiadomości.
Ponieważ jest to funkcja dostępna tylko w Google Play, należy ją uwzględnić w GooglePlayPurchaseHandler
.
Otwórz lib/google_play_purchase_handler.dart
i dodaj import PubsubApi:
lib/google_play_purchase_handler.dart
import 'package:googleapis/pubsub/v1.dart' as pubsub;
Następnie przekaż PubsubApi
do GooglePlayPurchaseHandler
i zmodyfikuj konstruktor klas, by utworzyć Timer
w następujący sposób:
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
jest skonfigurowane do wywoływania metody _pullMessageFromSubSub
co 10 sekund. Możesz dostosować Czas trwania.
Następnie utwórz _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,
);
}
Dodany kod komunikuje się z tematem Pub/Sub z Google Cloud co 10 sekund i pyta o nowe wiadomości. Następnie przetwarza wszystkie wiadomości w metodzie _processMessage
.
Ta metoda dekoduje wiadomości przychodzące i uzyskuje zaktualizowane informacje o każdym zakupie (zarówno w przypadku subskrypcji, jak i pozostałych subskrypcji), w razie potrzeby wywołuje metodę handleSubscription
lub handleNonSubscription
.
Każdą wiadomość należy potwierdzić za pomocą metody _askMessage
.
Następnie dodaj wymagane zależności do pliku server.dart
. Dodaj PubsubApi.cloudPlatformScope do konfiguracji danych logowania:
bin/server.dart
final clientGooglePlay =
await auth.clientViaServiceAccount(clientCredentialsGooglePlay, [
ap.AndroidPublisherApi.androidpublisherScope,
pubsub.PubsubApi.cloudPlatformScope, // new
]);
Następnie utwórz instancję PubsubApi:
bin/server.dart
final pubsubApi = pubsub.PubsubApi(clientGooglePlay);
Na koniec przekaż go do konstruktora GooglePlayPurchaseHandler
:
bin/server.dart
return {
'google_play': GooglePlayPurchaseHandler(
androidPublisher,
iapRepository,
pubsubApi, // new
),
'app_store': AppStorePurchaseHandler(
iapRepository,
),
};
Konfiguracja Google Play
Masz już napisany kod do wykorzystywania zdarzeń rozliczeniowych z tematu Pub/Sub, ale nie masz utworzonego tematu Pub/Sub ani nie publikujesz żadnych zdarzeń rozliczeniowych. Czas to skonfigurować.
Najpierw utwórz temat Pub/Sub:
- Otwórz stronę Cloud Pub/Sub w konsoli Google Cloud.
- Upewnij się, że korzystasz z projektu Firebase, i kliknij + Utwórz temat.
- Nadaj nowemu tematowi nazwę identyczną z wartością ustawioną dla pola
GOOGLE_PLAY_PUBSUB_BILLING_TOPIC
w poluconstants.ts
. W tym przypadku nadaj mu nazwęplay_billing
. Jeśli wybierzesz inną opcję, pamiętaj, by zaktualizowaćconstants.ts
. Utwórz temat. - Na liście tematów Pub/Sub kliknij 3 pionowe kropki obok utworzonego tematu, a następnie kliknij Wyświetl uprawnienia.
- Na pasku bocznym po prawej stronie kliknij Dodaj podmiot zabezpieczeń.
- W tym miejscu dodaj element
google-play-developer-notifications@system.gserviceaccount.com
i przypisz mu rolę Publikujący w Pub/Sub. - Zapisz zmiany uprawnień.
- Skopiuj Nazwę tematu nowo utworzonego tematu.
- Ponownie otwórz Konsolę Play i wybierz aplikację z listy Wszystkie aplikacje.
- Przewiń w dół i wybierz Zarabianie >”. Konfiguracja zarabiania.
- Wpisz pełny temat i zapisz zmiany.
Wszystkie zdarzenia płatności w Google Play zostaną opublikowane w tym temacie.
Przetwarzanie zdarzeń rozliczeniowych w App Store
Następnie wykonaj te same czynności w przypadku zdarzeń płatności w App Store. Istnieją 2 efektywne sposoby wdrażania aktualizacji obsługi zakupów w App Store. Jednym z nich jest wdrożenie webhooka, który dostarczasz Apple i którego używa on do komunikacji z Twoim serwerem. Drugim sposobem, który omówiliśmy w tym ćwiczeniu z programowania, jest połączenie się z interfejsem App Store Server API i ręczne uzyskanie informacji o subskrypcji.
W tym ćwiczeniu w Codelabs koncentrujemy się na drugim rozwiązaniu, ponieważ wdrożenie webhooka wymaga udostępnienia serwera w internecie.
W środowisku produkcyjnym najlepiej korzystać z obu tych funkcji. Webhook pobierający zdarzenia z App Store i z interfejsu Server API na wypadek, gdyby ominęło Cię jakieś zdarzenie lub musisz dokładnie sprawdzić stan subskrypcji.
Najpierw otwórz lib/app_store_purchase_handler.dart
i dodaj zależność AppStoreServerAPI:
lib/app_store_purchase_handler.dart
final AppStoreServerAPI appStoreServerAPI;
AppStorePurchaseHandler(
this.iapRepository,
this.appStoreServerAPI, // new
)
Zmodyfikuj konstruktor, aby dodać licznik czasu, który będzie wywoływać metodę _pullStatus
. Ten licznik czasu będzie wywoływać metodę _pullStatus
co 10 sekund. Możesz dostosować czas trwania minutnika do swoich potrzeb.
lib/app_store_purchase_handler.dart
AppStorePurchaseHandler(
this.iapRepository,
this.appStoreServerAPI,
) {
// Poll Subscription status every 10 seconds.
Timer.periodic(Duration(seconds: 10), (_) {
_pullStatus();
});
}
Następnie utwórz metodę _pullStatus w następujący sposób:
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,
));
}
}
}
}
Ta metoda działa w następujący sposób:
- Pobiera listę aktywnych subskrypcji z Firestore przy użyciu IapRepository.
- W przypadku każdego zamówienia żąda stanu subskrypcji do interfejsu App Store Server API.
- Uzyskuje ostatnią transakcję zakupu tej subskrypcji.
- Sprawdza datę ważności.
- Aktualizuje stan subskrypcji w Firestore. Jeśli wygaśnie, zostanie w ten sposób oznaczona.
Na koniec dodaj cały kod niezbędny do skonfigurowania dostępu do interfejsu App Store Server przez interfejs API:
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
),
};
Konfiguracja App Store
Następnie skonfiguruj App Store:
- Zaloguj się w App Store Connect i wybierz Users and Access (Użytkownicy i dostęp).
- Wybierz Typ klucza > > Zakup w aplikacji.
- Kliknij ikonę plusa. , aby dodać nową.
- Wpisz nazwę, np. „Klucz ćwiczeń z programowania”.
- Pobierz plik p8 zawierający klucz.
- Skopiuj go do folderu zasobów o nazwie
SubscriptionKey.p8
. - Skopiuj identyfikator klucza z nowo utworzonego klucza i w pliku
lib/constants.dart
ustaw dla niego stałąappStoreKeyId
. - Skopiuj identyfikator wydawcy znajdujący się u góry listy kluczy i ustaw w pliku
lib/constants.dart
stałą wartośćappStoreIssuerId
.
Śledzenie zakupów na urządzeniu
Najbezpieczniejszym sposobem śledzenia zakupów jest po stronie serwera, ponieważ trudno jest zabezpieczyć klienta, ale potrzebny jest sposób przekazywania informacji klientowi, tak aby aplikacja mogła wykorzystać informacje o stanie subskrypcji. Przechowując zakupy w Firestore, możesz łatwo synchronizować dane z klientem i aktualizować je automatycznie.
Zasób IAPRepo został już uwzględniony w aplikacji, która jest repozytorium Firestore, które zawiera wszystkie dane dotyczące zakupów użytkownika w List<PastPurchase> purchases
. Repozytorium zawiera też zasadę hasActiveSubscription,
, która ma wartość true (prawda), gdy zakup dokonany za pomocą productId storeKeySubscription
ma stan, który nie wygasł. Jeśli użytkownik nie jest zalogowany, lista jest pusta.
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();
});
}
Cała logika zakupu znajduje się w klasie DashPurchases
, w której należy zastosować lub usunąć subskrypcje. Dodaj więc iapRepo
jako właściwość do klasy i przypisz iapRepo
w konstruktorze. Następnie dodaj detektor bezpośrednio w konstruktorze i usuń go w metodzie dispose()
. Początkowo odbiornik może być tylko pustą funkcją. Ponieważ IAPRepo
to ChangeNotifier
i wywołujesz notifyListeners()
przy każdej zmianie zakupów w Firestore, metoda purchasesUpdate()
jest zawsze wywoływana w przypadku zmiany kupionych produktów.
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
}
Następnie przekaż IAPRepo
do konstruktora w main.dart.
. Możesz pobrać repozytorium za pomocą context.read
, ponieważ zostało ono już utworzone w Provider
.
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
context.read<FirebaseNotifier>(),
context.read<IAPRepo>(),
),
lazy: false,
),
Następnie napisz kod funkcji purchaseUpdate()
. W dash_counter.dart,
metody applyPaidMultiplier
i removePaidMultiplier
ustawiają mnożnik odpowiednio na 10 lub 1, więc nie musisz sprawdzać, czy subskrypcja została już zastosowana. Gdy zmieni się stan subskrypcji, aktualizujesz też stan produktu, który można kupić, dzięki czemu będziesz widzieć na stronie zakupu, że jest już aktywny. Ustaw właściwość _beautifiedDashUpgrade
w zależności od tego, czy kupiono uaktualnienie.
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();
}
}
Masz teraz pewność, że stan subskrypcji i licencji w usłudze backendu jest zawsze aktualny i zsynchronizowany z aplikacją. Aplikacja działa w odpowiedni sposób i stosuje funkcje subskrypcji oraz uaktualnienia w grze Dasher.
12. Wszystko gotowe
Gratulacje! Ćwiczenie z programowania zostało ukończone. Ukończony kod tego ćwiczenia z programowania znajdziesz w pełnym folderze.
Aby dowiedzieć się więcej, wykonaj inne ćwiczenia z programowania Flutter.