1. Einführung
Zuletzt aktualisiert:11.07.2023
Damit Sie einer Flutter-App In-App-Käufe hinzufügen können, müssen die App und der Play Store korrekt eingerichtet, der Kauf bestätigt und die erforderlichen Berechtigungen, z. B. Abovorteile, gewährt werden.
In diesem Codelab fügen Sie einer App drei Arten von In-App-Käufen hinzu und bestätigen diese Käufe mithilfe eines Dart-Back-Ends mit Firebase. Die App „Dash Clicker“ enthält ein Spiel, in dem das Maskottchen „Dash“ als Währung verwendet wird. Sie fügen die folgenden Kaufoptionen hinzu:
- Eine wiederholbare Kaufoption für 2.000 Striche auf einmal.
- Ein einmaliges Upgrade, um aus dem alten Design einen modernen Dash zu machen.
- Ein Abo, bei dem sich die Anzahl der automatisch generierten Klicks verdoppelt.
Die erste Kaufoption bietet dem Nutzer einen direkten Vorteil von 2000 Dashes. Diese sind für den Nutzer direkt verfügbar und können mehrfach gekauft werden. Dies wird als Verbrauchsmaterial bezeichnet, da es direkt verbraucht wird und mehrmals konsumiert werden kann.
Die zweite Option erhöht den Dash auf einen schöneren Dash. Dies muss nur einmal gekauft werden und ist für immer verfügbar. Ein solcher Kauf wird als nicht konsumierbar bezeichnet, weil er von der App nicht verarbeitet werden kann, aber für immer gültig ist.
Die dritte und letzte Kaufoption ist ein Abo. Während das Abo aktiv ist, kann der Nutzer Dashes schneller erhalten, aber wenn er nicht mehr für das Abo bezahlt, stehen ihm auch die Vorteile nicht mehr zur Verfügung.
Der Back-End-Dienst (auch für Sie bereitgestellt) wird als Dart-App ausgeführt, überprüft, ob die Käufe getätigt wurden, und speichert sie mit Firestore. Firestore wird verwendet, um den Prozess zu vereinfachen. In Ihrer Produktionsanwendung können Sie jedoch jede Art von Back-End-Dienst verwenden.
Aufgaben
- Du erweiterst eine App, um kurzfristig nutzbare Käufe und Abos zu unterstützen.
- Außerdem erweitern Sie eine Dart-Backend-App, um die gekauften Artikel zu verifizieren und zu speichern.
Lerninhalte
- App Store und Play Store mit käuflichen Produkten konfigurieren
- Mit den Geschäften kommunizieren, um Käufe zu bestätigen und in Firestore zu speichern
- Käufe in Ihrer App verwalten
Voraussetzungen
- Android Studio 4.1 oder höher
- Xcode 12 oder höher (für iOS-Entwicklung)
- Flutter-SDK
2. Entwicklungsumgebung einrichten
Laden Sie den Code herunter und ändern Sie den Bundle-Identifikator für iOS und den Paketnamen für Android, um dieses Codelab zu starten.
Code herunterladen
Verwenden Sie den folgenden Befehl, um das GitHub-Repository über die Befehlszeile zu klonen:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Wenn Sie das GitHub-Cli-Tool installiert haben, verwenden Sie den folgenden Befehl:
gh repo clone flutter/codelabs flutter-codelabs
Der Beispielcode wird in ein flutter-codelabs
-Verzeichnis geklont, das den Code für eine Sammlung von Codelabs enthält. Der Code für dieses Codelab befindet sich in flutter-codelabs/in_app_purchases
.
Die Verzeichnisstruktur unter flutter-codelabs/in_app_purchases
enthält eine Reihe von Snapshots davon, wo Sie sich am Ende jedes benannten Schritts befinden sollten. Der Startcode befindet sich in Schritt 0, sodass die Suche nach den übereinstimmenden Dateien so einfach ist wie:
cd flutter-codelabs/in_app_purchases/step_00
Wenn Sie vorwärts springen oder sehen möchten, wie nach einem Schritt etwas aussehen sollte, suchen Sie in dem Verzeichnis, das nach dem gewünschten Schritt benannt ist. Der Code des letzten Schritts befindet sich im Ordner complete
.
Startprojekt einrichten
Öffnen Sie das Startprojekt aus step_00
in Ihrer bevorzugten IDE. Für die Screenshots haben wir Android Studio verwendet, aber auch Visual Studio Code ist eine gute Option. In beiden Editoren müssen die neuesten Dart- und Flutter-Plug-ins installiert sein.
Die von dir erstellten Apps müssen mit dem App Store und dem Play Store kommunizieren, damit sie wissen, welche Produkte zu welchem Preis verfügbar sind. Jede Anwendung wird durch eine eindeutige ID identifiziert. Im iOS App Store wird dies als Paket-ID bezeichnet und für den Android Play Store als App-ID. Diese IDs werden in der Regel mit der umgekehrten Domainnamen-Notation erstellt. Beim Kauf einer App für In-App-Käufe für flutter.dev würden wir beispielsweise dev.flutter.inapppurchase
verwenden. Überlegen Sie sich eine Kennung für Ihre App, die Sie nun in den Projekteinstellungen festlegen.
Richten Sie zuerst den Paket-Identifikator für iOS ein.
Öffnen Sie das Projekt in Android Studio, klicken Sie mit der rechten Maustaste auf den iOS-Ordner, klicken Sie auf Flutter und öffnen Sie das Modul in der Xcode App.
In der Ordnerstruktur von Xcode befindet sich das Runner-Projekt oben und die Ziele Flutter, Runner und Products unterhalb des Runner-Projekts. Doppelklicken Sie auf Runner, um Ihre Projekteinstellungen zu bearbeiten, und klicken Sie auf Signing & Funktionen. Geben Sie im Feld Team die Bundle-ID ein, die Sie gerade ausgewählt haben, um Ihr Team festzulegen.
Sie können jetzt Xcode schließen und zu Android Studio zurückkehren, um die Konfiguration für Android abzuschließen. Öffnen Sie dazu die Datei build.gradle
unter android/app,
und ändern Sie Ihre applicationId
(in Zeile 37 im Screenshot unten) in die App-ID, die mit dem iOS-Paket-Identifikator identisch ist. Die IDs für den iOS- und Android-Shop müssen nicht identisch sein. Wenn Sie sie identisch halten, ist das weniger fehleranfällig. Deshalb verwenden wir in diesem Codelab auch identische IDs.
3. Plug-in installieren
In diesem Teil des Codelabs installieren Sie das Plug-in „in_app_purchase“.
Abhängigkeit in pubspec hinzufügen
Fügen Sie in_app_purchase
zur Pubspec. Fügen Sie dazu in_app_purchase
den Abhängigkeiten in Ihrer Pubspec hinzu:
$ 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
..
Klicken Sie auf pub get, um das Paket herunterzuladen, oder führen Sie flutter pub get
in der Befehlszeile aus.
4. App Store einrichten
Wenn Sie In-App-Käufe einrichten und auf iOS-Geräten testen möchten, müssen Sie im App Store eine neue App mit kaufbaren Produkten erstellen. Du musst nichts veröffentlichen oder die App zur Überprüfung an Apple senden. Dafür benötigst du ein Entwicklerkonto. Wenn Sie keines haben, registrieren Sie sich für das Apple-Entwicklerprogramm.
Vereinbarungen zu kostenpflichtigen Apps
Um In-App-Käufe nutzen zu können, benötigen Sie außerdem eine aktive Vereinbarung für kostenpflichtige Apps in App Store Connect. Rufe https://appstoreconnect.apple.com/ auf und klicke auf Vereinbarungen, Steuern und Bankwesen.
Hier finden Sie Vereinbarungen für kostenlose und kostenpflichtige Apps. Kostenlose Apps sollten „Aktiv“ und kostenpflichtige Apps einen neuen Status haben. Lesen Sie die Nutzungsbedingungen, akzeptieren Sie sie und geben Sie alle erforderlichen Informationen ein.
Wenn alles korrekt eingerichtet ist, ist der Status für kostenpflichtige Apps „Aktiv“. Dies ist sehr wichtig, da Sie In-App-Käufe ohne aktive Vereinbarung nicht testen können.
App-ID registrieren
Erstellen Sie im Apple-Entwicklerportal eine neue ID.
App-IDs auswählen
App auswählen
Geben Sie eine Beschreibung ein und legen Sie die Bundle-ID so fest, dass sie der Bundle-ID mit dem zuvor in XCode festgelegten Wert entspricht.
Weitere Informationen zum Erstellen einer neuen App-ID finden Sie in der Entwicklerkonto-Hilfe .
Neue App erstellen
Erstellen Sie im App Store Connect eine neue App mit Ihrer eindeutigen Paket-ID.
Weitere Informationen zum Erstellen einer neuen App und zum Verwalten von Vereinbarungen finden Sie in der App Store Connect-Hilfe.
Zum Testen der In-App-Käufe benötigen Sie einen Sandbox-Testnutzer. Dieser Testnutzer sollte nicht mit iTunes verbunden sein. Er wird nur zum Testen von In-App-Käufen verwendet. Sie können keine E-Mail-Adresse verwenden, die bereits für ein Apple-Konto verwendet wird. Gehen Sie in Nutzer und Zugriff zu Tester unter Sandbox, um ein neues Sandbox-Konto zu erstellen oder die vorhandenen Sandbox-Apple-IDs zu verwalten.
Jetzt können Sie Ihren Sandbox-Nutzer auf Ihrem iPhone einrichten. Rufen Sie dazu Einstellungen > App Store > Sandbox-Konto.
In-App-Käufe konfigurieren
Konfigurieren Sie nun die drei käuflichen Artikel:
dash_consumable_2k
: Ein kurzfristig nutzbarer Kauf, der mehrmals gekauft werden kann und dem Nutzer 2.000 Dashes (die In-App-Währung) pro Kauf gewährt.dash_upgrade_3d
: Ein nicht nutzbares „Upgrade“ Kauf, der nur einmal gekauft werden kann, und gibt dem Nutzer einen optisch anderen Dash, auf den er klicken kann.dash_subscription_doubler
: Ein Abo, das dem Nutzer während der gesamten Laufzeit doppelt so viele Striche pro Klick gewährt.
Rufen Sie In-App-Käufe > Verwalten.
Erstellen Sie Ihre In-App-Käufe mit den angegebenen IDs:
- Richte
dash_consumable_2k
als Consumable ein.
Verwenden Sie dash_consumable_2k
als Produkt-ID. Der Referenzname wird nur in App Store Connect verwendet. Setzen Sie ihn einfach auf dash consumable 2k
und fügen Sie Ihre Lokalisierungen für den Kauf hinzu. Rufen Sie den Kauf Spring is in the air
mit 2000 dashes fly out
als Beschreibung auf.
- Richte
dash_upgrade_3d
als Nicht verbraucht ein.
Verwenden Sie dash_upgrade_3d
als Produkt-ID. Lege den Referenznamen auf „dash upgrade 3d
“ fest und füge deine Lokalisierungen für den Kauf hinzu. Rufen Sie den Kauf 3D Dash
mit Brings your dash back to the future
als Beschreibung auf.
- Richte
dash_subscription_doubler
als Abo mit automatischer Verlängerung ein.
Der Ablauf für Abos ist etwas anders. Zuerst musst du den Referenznamen und die Produkt-ID festlegen:
Als Nächstes musst du eine Abogruppe erstellen. Wenn mehrere Abos Teil derselben Gruppe sind, kann ein Nutzer nur eines dieser Abos gleichzeitig abonnieren. Zwischen diesen Abos kann jedoch problemlos ein Upgrade oder Downgrade ausgeführt werden. Nennen Sie diese Gruppe einfach subscriptions
.
Geben Sie als Nächstes die Abodauer und die Lokalisierungen ein. Geben Sie dem Abo den Namen „Jet Engine
“ und die Beschreibung „Doubles your clicks
“. Klicken Sie auf Speichern.
Nachdem Sie auf die Schaltfläche Speichern geklickt haben, fügen Sie einen Abopreis hinzu. Wählen Sie einen beliebigen Preis aus.
Sie sollten jetzt die drei Käufe in der Liste der Käufe sehen:
5. Play Store einrichten
Wie für den App Store benötigen Sie auch für den Play Store ein Entwicklerkonto. Wenn Sie noch kein Konto haben, registrieren Sie ein Konto.
Neue App erstellen
So erstellen Sie eine neue App in der Google Play Console:
- Öffnen Sie die Play Console.
- Wählen Sie Alle Apps > App erstellen.
- Wählen Sie eine Standardsprache und einen Titel für die App aus. Geben Sie den Namen der App so ein, wie er bei Google Play erscheinen soll. Sie können den Namen bei Bedarf später ändern.
- Geben Sie an, dass es sich bei Ihrer App um ein Spiel handelt. Sie können dies später ändern.
- Geben Sie an, ob Ihre Anwendung kostenlos oder kostenpflichtig ist.
- Füge eine E-Mail-Adresse hinzu, über die Play Store-Nutzer dich bezüglich dieser App kontaktieren können.
- Füllen Sie die Erklärungen zu Inhaltsrichtlinien und Exportbestimmungen der USA aus.
- Wählen Sie App erstellen aus.
Rufen Sie nach dem Erstellen Ihrer App das Dashboard auf und führen Sie alle Aufgaben im Abschnitt App einrichten aus. Hier geben Sie einige Informationen zu Ihrer App an, z. B. Altersfreigaben und Screenshots.
Anwendung signieren
Um In-App-Käufe testen zu können, muss mindestens ein Build bei Google Play hochgeladen sein.
Dazu muss der Release-Build nicht mit den Schlüsseln zur Fehlerbehebung signiert werden.
Schlüsselspeicher erstellen
Wenn bereits ein Schlüsselspeicher vorhanden ist, fahren Sie mit dem nächsten Schritt fort. Falls nicht, erstellen Sie eines, indem Sie in der Befehlszeile Folgendes ausführen.
Verwenden Sie unter Mac/Linux den folgenden Befehl:
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
Verwenden Sie unter Windows den folgenden Befehl:
keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias key
Mit diesem Befehl wird die Datei key.jks
in Ihrem Basisverzeichnis gespeichert. Wenn Sie die Datei an einem anderen Ort speichern möchten, ändern Sie das Argument, das Sie an den Parameter -keystore
übergeben. Die
keystore
file private; nicht in die öffentliche Versionsverwaltung einchecken!
Schlüsselspeicher über die Anwendung referenzieren
Erstellen Sie eine Datei mit dem Namen <your app dir>/android/key.properties
, die einen Verweis auf Ihren Schlüsselspeicher enthält:
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 für die Anmeldung konfigurieren
Konfigurieren Sie die Signatur für Ihre App, indem Sie die Datei <your app dir>/android/app/build.gradle
bearbeiten.
Fügen Sie die Schlüsselspeicherinformationen aus Ihrer Property-Datei vor dem android
-Block hinzu:
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
// omitted
}
Laden Sie die Datei key.properties
in das keystoreProperties
-Objekt.
Fügen Sie den folgenden Code vor dem Block buildTypes
ein:
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
}
}
Konfigurieren Sie den Block signingConfigs
in der Datei build.gradle
Ihres Moduls mit den Informationen zur Signaturkonfiguration:
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
Release-Builds deiner App werden jetzt automatisch signiert.
Weitere Informationen zum Signieren Ihrer App finden Sie auf developer.android.com unter App signieren.
Ersten Build hochladen
Nachdem Ihre App für das Signieren konfiguriert wurde, sollten Sie sie mit folgendem Befehl erstellen können:
flutter build appbundle
Dieser Befehl generiert standardmäßig einen Release-Build. Die Ausgabe finden Sie unter <your app dir>/build/app/outputs/bundle/release/
.
Gehen Sie im Dashboard der Google Play Console zu Release > Testen > Geschlossener Test und erstellen Sie einen neuen, geschlossenen Testrelease.
In diesem Codelab bleiben Sie beim Signieren der App durch Google. Drücken Sie also unter Play App-Signatur auf Weiter, um sich anzumelden.
Laden Sie als Nächstes das App Bundle app-release.aab
hoch, das mit dem Build-Befehl generiert wurde.
Klicken Sie auf Speichern und dann auf Release überprüfen.
Klicken Sie abschließend auf Für interne Tests einführen, um den internen Testrelease zu aktivieren.
Testnutzer einrichten
Um In-App-Käufe testen zu können, müssen die Google-Konten Ihrer Tester in der Google Play Console an zwei Orten hinzugefügt werden:
- An den spezifischen Test-Track (interner Test)
- Als Lizenztester
Fügen Sie den Tester zuerst dem internen Test-Track hinzu. Gehen Sie zurück zu Release > Testen > Interne Tests und klicken Sie auf den Tab Tester.
Erstellen Sie eine neue E-Mail-Liste, indem Sie auf E-Mail-Liste erstellen klicken. Benennen Sie die Liste und fügen Sie die E-Mail-Adressen der Google-Konten hinzu, die Zugriff zum Testen von In-App-Käufen benötigen.
Klicken Sie auf das Kästchen für die Liste und dann auf Änderungen speichern.
Fügen Sie dann die Lizenztester hinzu:
- Kehren Sie zur Ansicht Alle Apps in der Google Play Console zurück.
- Gehen Sie zu Einstellungen > Lizenztests.
- Füge dieselben E-Mail-Adressen der Tester hinzu, die In-App-Käufe testen können müssen.
- Setze Lizenzantwort auf
RESPOND_NORMALLY
. - Klicken Sie auf ROLLE ERSTELLEN.
In-App-Käufe konfigurieren
Nun konfigurieren Sie die Artikel, die in der App gekauft werden können.
Genau wie im App Store müssen Sie drei verschiedene Käufe definieren:
dash_consumable_2k
: Ein kurzfristig nutzbarer Kauf, der mehrmals gekauft werden kann und dem Nutzer 2.000 Dashes (die In-App-Währung) pro Kauf gewährt.dash_upgrade_3d
: Ein nicht nutzbares „Upgrade“ -Kauf, der nur einmal gekauft werden kann. Dadurch erhält der Nutzer einen optisch anderen Dash, auf den er klicken muss.dash_subscription_doubler
: Ein Abo, das dem Nutzer während der gesamten Laufzeit doppelt so viele Striche pro Klick gewährt.
Füge zuerst das Verbrauchsmaterial und das Nicht-Verbrauchsmaterial hinzu.
- Rufen Sie die Google Play Console auf und wählen Sie Ihre App aus.
- Rufe Monetarisieren > Produkte > In-App-Produkte
- Klicken Sie auf Produkt erstellen
- Geben Sie alle erforderlichen Informationen für das Produkt ein. Achten Sie darauf, dass die Produkt-ID genau mit der ID übereinstimmt, die Sie verwenden möchten.
- Klicken Sie auf Speichern.
- Klicken Sie auf Aktivieren.
- Wiederholen Sie den Vorgang für das nicht nutzbare „Upgrade“. kaufen.
Fügen Sie als Nächstes das Abo hinzu:
- Rufen Sie die Google Play Console auf und wählen Sie Ihre App aus.
- Rufe Monetarisieren > Produkte > Abos.
- Klicke auf Abo erstellen
- Geben Sie alle erforderlichen Informationen für Ihr Abo ein. Achten Sie darauf, dass die Produkt-ID genau mit der ID übereinstimmt, die Sie verwenden möchten.
- Klicken Sie auf Speichern.
Deine Käufe sollten jetzt in der Play Console eingerichtet sein.
6. Firebase einrichten
In diesem Codelab nutzen Sie einen Back-End-Dienst, um die Käufe.
Die Verwendung eines Back-End-Dienstes bietet mehrere Vorteile:
- Sie können Transaktionen sicher bestätigen.
- Sie können auf Abrechnungsereignisse aus den App-Shops reagieren.
- Sie können die Käufe in einer Datenbank verfolgen.
- Nutzer können Ihre App nicht dazu bringen, Premium-Funktionen bereitzustellen, indem sie ihre Systemuhr zurückspulen.
Es gibt viele Möglichkeiten, einen Back-End-Dienst einzurichten. Verwenden Sie dazu Cloud Functions und Firestore und verwenden Sie dazu Firebase von Google.
Das Schreiben des Backends ist in diesem Codelab nicht enthalten. Daher enthält der Startcode bereits ein Firebase-Projekt, das grundlegende Käufe verarbeitet, um Ihnen den Einstieg zu erleichtern.
In der Start-App sind auch Firebase-Plug-ins enthalten.
Jetzt müssen Sie ein eigenes Firebase-Projekt erstellen, die App und das Backend für Firebase konfigurieren und schließlich das Backend bereitstellen.
Firebase-Projekt erstellen
Rufen Sie die Firebase Console auf und erstellen Sie ein neues Firebase-Projekt. In diesem Beispiel nennen wir das Projekt „Dash Clicker“.
In der Back-End-App verknüpfen Sie Käufe mit einem bestimmten Nutzer. Daher ist eine Authentifizierung erforderlich. Nutzen Sie dazu das Authentifizierungsmodul von Firebase mit Google Log-in.
- Gehen Sie im Firebase-Dashboard zu Authentifizierung und aktivieren Sie die Authentifizierung bei Bedarf.
- Gehen Sie zum Tab Anmeldemethode und aktivieren Sie den Google-Anmeldeanbieter.
Da Sie auch die Firestore-Datenbank von Firebase verwenden, sollten Sie diese ebenfalls aktivieren.
Legen Sie die Cloud Firestore-Regeln so fest:
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 for Flutter einrichten
Die empfohlene Methode zum Installieren von Firebase in der Flutter-App ist die Verwendung der FlutterFire CLI. Folgen Sie der Anleitung auf der Einrichtungsseite.
Wenn Sie Flutterfire konfigurieren, wählen Sie das Projekt aus, das Sie im vorherigen Schritt erstellt haben.
$ 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>
Aktivieren Sie als Nächstes iOS und Android, indem Sie die beiden Plattformen auswählen.
? Which platforms should your configuration support (use arrow keys & space to select)? ›
✔ android
✔ ios
macos
web
Wenn Sie aufgefordert werden, „firebase_options.dart“ zu überschreiben, wählen Sie „yes“ (Ja).
? Generated FirebaseOptions file lib/firebase_options.dart already exists, do you want to override it? (y/n) › yes
Firebase für Android einrichten: Weitere Schritte
Gehen Sie im Firebase-Dashboard zu Projektübersicht, wählen Sie Einstellungen und dann den Tab Allgemein aus.
Scrollen Sie nach unten zu Meine Apps und wählen Sie die App Dashclicker (Android) aus.
Wenn Sie die Google-Anmeldung im Debug-Modus zulassen möchten, müssen Sie den SHA-1-Hash-Fingerabdruck Ihres Fehlerbehebungszertifikats angeben.
Hash des Signaturzertifikats für die Fehlerbehebung abrufen
Wechseln Sie im Stammverzeichnis Ihres Flutter-App-Projekts in den Ordner android/
und generieren Sie einen Signaturbericht.
cd android ./gradlew :app:signingReport
Es wird eine umfangreiche Liste von Signaturschlüsseln angezeigt. Da Sie nach dem Hash für das Debug-Zertifikat suchen, suchen Sie nach dem Zertifikat, bei dem die Attribute Variant
und Config
auf debug
festgelegt sind. Der Schlüsselspeicher befindet sich wahrscheinlich im Basisordner unter .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
Kopieren Sie den SHA-1-Hash und füllen Sie das letzte Feld im modalen Dialogfeld zum Senden der App aus.
Firebase für iOS einrichten: Weitere Schritte
Öffnen Sie ios/Runnder.xcworkspace
mit Xcode
. oder mit der IDE Ihrer Wahl.
Klicke in VSCode mit der rechten Maustaste auf den Ordner ios/
und dann auf open in xcode
.
Klicke in Android Studio mit der rechten Maustaste auf den Ordner ios/
, dann auf flutter
und dann auf die Option open iOS module in Xcode
.
Um Google Log-in unter iOS zu ermöglichen, fügen Sie den Build-plist
-Dateien die Konfigurationsoption CFBundleURLTypes
hinzu. Weitere Informationen finden Sie in der Dokumentation zum google_sign_in
-Paket. In diesem Fall sind die Dateien ios/Runner/Info-Debug.plist
und ios/Runner/Info-Release.plist
.
Das Schlüssel/Wert-Paar wurde bereits hinzugefügt, die Werte müssen jedoch ersetzt werden:
- Rufen Sie den Wert für
REVERSED_CLIENT_ID
aus der DateiGoogleService-Info.plist
ohne das Element<string>..</string>
ab. - Ersetzen Sie den Wert in den Dateien
ios/Runner/Info-Debug.plist
undios/Runner/Info-Release.plist
unter dem SchlüsselCFBundleURLTypes
.
<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>
Die Firebase-Einrichtung ist jetzt abgeschlossen.
7. Benachrichtigungen zu Käufen anhören
In diesem Teil des Codelabs bereiten Sie die App für den Kauf der Produkte vor. Dieser Prozess umfasst das Überwachen von Kaufaktualisierungen und Fehlern nach dem Start der App.
Informationen zu Käufen anhören
Suchen Sie in main.dart,
nach dem Widget MyHomePage
, das eine Scaffold
mit einer BottomNavigationBar
enthält, die zwei Seiten enthält. Auf dieser Seite werden außerdem drei Provider
s für DashCounter
, DashUpgrades,
und DashPurchases
erstellt. DashCounter
verfolgt die aktuelle Anzahl an Bindestrichen und erhöht sie automatisch. DashUpgrades
verwaltet die Upgrades, die Sie mit Dashes erwerben können. In diesem Codelab geht es um DashPurchases
.
Standardmäßig wird das Objekt eines Anbieters definiert, wenn dieses Objekt zum ersten Mal angefordert wird. Dieses Objekt überwacht Kaufaktualisierungen direkt, wenn die App gestartet wird. Deaktivieren Sie daher Lazy Loading für dieses Objekt mit lazy: false
:
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
),
lazy: false,
),
Außerdem benötigen Sie eine Instanz von InAppPurchaseConnection
. Damit die App jedoch testbar bleibt, müssen Sie die Verbindung simulieren. Erstellen Sie dazu eine Instanzmethode, die im Test überschrieben werden kann, und fügen Sie sie zu main.dart
hinzu.
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!;
}
}
Sie müssen den Test geringfügig aktualisieren, damit er weiterhin funktioniert. Den vollständigen Code für TestIAPConnection
finden Sie unter widget_test.dart auf 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);
});
}
Rufen Sie in lib/logic/dash_purchases.dart
den Code für DashPurchases ChangeNotifier
auf. Derzeit gibt es nur ein DashCounter
, das du deinen gekauften Dashes hinzufügen kannst.
Fügen Sie die Stream-Abo-Property _subscription
(vom Typ StreamSubscription<List<PurchaseDetails>> _subscription;
), die IAPConnection.instance,
und die Importe hinzu. Der Code sollte so aussehen:
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);
}
Das Keyword late
wird zu _subscription
hinzugefügt, da _subscription
im Konstruktor initialisiert wird. Dieses Projekt ist standardmäßig so eingerichtet, dass keine Nullwerte zulässig sind (NNBD). Das bedeutet, dass Attribute, für die keine Nullwerte deklariert werden, einen Wert haben müssen, der nicht null ist. Mit dem Qualifier late
können Sie die Definition dieses Werts verzögern.
Rufen Sie im Konstruktor den purchaseUpdatedStream
ab und beginnen Sie mit dem Anhören des Streams. Kündigen Sie in der Methode dispose()
das Streamabo.
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
}
}
Jetzt erhält die App die Kauf-Updates, sodass Sie im nächsten Abschnitt einen Kauf tätigen!
Führe die Tests mit „flutter test"
“ aus, um zu prüfen, ob alles korrekt eingerichtet ist, bevor du fortfährst.
$ flutter test
00:01 +1: All tests passed!
8. Einkaufen
In diesem Teil des Codelabs ersetzen Sie die vorhandenen simulierten Produkte durch echte käufliche Produkte. Diese Produkte werden in den Geschäften geladen, in einer Liste angezeigt und gekauft, wenn sie auf das Produkt tippen.
Kaufbare Produkte anpassen
PurchasableProduct
zeigt ein simuliertes Produkt an. Aktualisieren Sie sie, um die tatsächlichen Inhalte anzuzeigen. Ersetzen Sie dazu die Klasse PurchasableProduct
in purchasable_product.dart
durch den folgenden Code:
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;
}
Entferne in dash_purchases.dart,
die Dummy-Käufe und ersetze sie durch eine leere Liste: List<PurchasableProduct> products = [];
Verfügbare Käufe laden
Damit Nutzer die Möglichkeit haben, etwas zu kaufen, laden Sie die Käufe aus dem Store. Prüfen Sie zuerst, ob das Geschäft verfügbar ist. Wenn der Shop nicht verfügbar ist, wird dem Nutzer eine Fehlermeldung angezeigt, wenn du storeState
auf notAvailable
setzt.
lib/logic/dash_purchases.dart
Future<void> loadPurchases() async {
final available = await iapConnection.isAvailable();
if (!available) {
storeState = StoreState.notAvailable;
notifyListeners();
return;
}
}
Wenn der Shop verfügbar ist, laden Sie die verfügbaren Käufe. Bei der bisherigen Firebase-Einrichtung können Sie mit storeKeyConsumable
, storeKeySubscription,
und storeKeyUpgrade
rechnen. Wenn ein erwarteter Kauf nicht verfügbar ist, geben Sie diese Informationen in der Konsole aus. können Sie diese Informationen
an den Back-End-Dienst senden.
Die Methode await iapConnection.queryProductDetails(ids)
gibt sowohl die nicht gefundenen IDs als auch die gefundenen kaufbaren Produkte zurück. Verwenden Sie productDetails
aus der Antwort, um die UI zu aktualisieren, und legen Sie StoreState
auf available
fest.
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();
}
Rufen Sie die Funktion loadPurchases()
im Konstruktor auf:
lib/logic/dash_purchases.dart
DashPurchases(this.counter) {
final purchaseUpdated = iapConnection.purchaseStream;
_subscription = purchaseUpdated.listen(
_onPurchaseUpdate,
onDone: _updateStreamOnDone,
onError: _updateStreamOnError,
);
loadPurchases();
}
Ändern Sie zuletzt den Wert des Felds storeState
von StoreState.available
in StoreState.loading:
.
lib/logic/dash_purchases.dart
StoreState storeState = StoreState.loading;
Kaufbare Produkte anzeigen
Sehen Sie sich die Datei purchase_page.dart
an. Das Widget PurchasePage
zeigt je nach StoreState
_PurchasesLoading
, _PurchaseList,
oder _PurchasesNotAvailable,
an. Das Widget zeigt auch die bisherigen Käufe des Nutzers an, die im nächsten Schritt verwendet werden.
Das _PurchaseList
-Widget zeigt die Liste der kaufbaren Produkte an und sendet eine Kaufanfrage an das DashPurchases
-Objekt.
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(),
);
}
}
Wenn die Produkte richtig konfiguriert sind, sollten Sie die verfügbaren Produkte im Android- und iOS-Store sehen. Hinweis: Es kann einige Zeit dauern, bis die Käufe in den jeweiligen Konsolen verfügbar sind.
Kehren Sie zu dash_purchases.dart
zurück und implementieren Sie die Funktion, um ein Produkt zu kaufen. Du musst nur die Verbrauchsmaterialien von den nicht Verbrauchsgütern trennen. Das Upgrade und die Aboprodukte sind nicht nutzbar.
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');
}
}
Bevor Sie fortfahren, erstellen Sie die Variable _beautifiedDashUpgrade
und aktualisieren Sie den beautifiedDash
-Getter, um darauf zu verweisen.
lib/logic/dash_purchases.dart
bool get beautifiedDash => _beautifiedDashUpgrade;
bool _beautifiedDashUpgrade = false;
Die Methode _onPurchaseUpdate
empfängt die Kaufaktualisierungen, aktualisiert den Status des auf der Kaufseite angezeigten Produkts und wendet den Kauf auf die Zählerlogik an. Es ist wichtig, dass Sie completePurchase
anrufen, nachdem der Kauf bearbeitet wurde, damit der Händler weiß, dass er ordnungsgemäß abgewickelt wird.
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 einrichten
Bevor Sie mit dem Tracking und der Verifizierung von Käufen fortfahren, richten Sie ein Dart-Backend ein, das dies unterstützt.
In diesem Abschnitt arbeiten Sie vom Ordner dart-backend/
als Stammverzeichnis aus.
Stellen Sie sicher, dass die folgenden Tools installiert sind:
- Dart
- Firebase CLI
Basisprojekt – Übersicht
Da einige Teile dieses Projekts für dieses Codelab als nicht vorgesehen angesehen werden, sind sie im Startcode enthalten. Es ist ratsam, vor Beginn durchzugehen, was bereits im Startcode enthalten ist, um eine Vorstellung davon zu bekommen, wie Sie die Dinge strukturieren werden.
Dieser Back-End-Code kann lokal auf Ihrem Computer ausgeführt werden. Sie müssen ihn nicht bereitstellen, um ihn verwenden zu können. Sie müssen jedoch eine Verbindung von Ihrem Entwicklungsgerät (Android oder iPhone) zu dem Computer herstellen können, auf dem der Server ausgeführt wird. Dazu müssen sie sich im selben Netzwerk befinden und Sie müssen die IP-Adresse Ihres Computers kennen.
Versuchen Sie, den Server mit dem folgenden Befehl auszuführen:
$ dart ./bin/server.dart
Serving at http://0.0.0.0:8080
Das Dart-Back-End verwendet shelf
und shelf_router
, um API-Endpunkte bereitzustellen. Standardmäßig stellt der Server keine Routen bereit. Später erstellen Sie eine Route für die Bestätigung von Käufen.
Ein Teil, der bereits im Startcode enthalten ist, ist IapRepository
in lib/iap_repository.dart
. Da es für dieses Codelab nicht relevant ist, die Interaktion mit Firestore oder Datenbanken im Allgemeinen zu erlernen, enthält der Startcode Funktionen zum Erstellen oder Aktualisieren von Käufen in Firestore sowie alle Klassen für diese Käufe.
Firebase-Zugriff einrichten
Für den Zugriff auf Firebase Firestore benötigen Sie einen Zugriffsschlüssel für das Dienstkonto. Generieren Sie einen Schlüssel in den Firebase-Projekteinstellungen, gehen Sie zum Bereich Dienstkonten und wählen Sie Neuen privaten Schlüssel generieren aus.
Kopieren Sie die heruntergeladene JSON-Datei in den Ordner assets/
und benennen Sie sie in service-account-firebase.json
um.
Zugriff auf Google Play einrichten
Wenn Sie zum Bestätigen von Käufen auf den Play Store zugreifen möchten, müssen Sie ein Dienstkonto mit diesen Berechtigungen erstellen und die JSON-Anmeldedaten dafür herunterladen.
- Rufen Sie die Google Play Console auf und beginnen Sie auf der Seite Alle Apps.
- Gehen Sie zu Einrichtung > API-Zugriff. Falls du in der Google Play Console aufgefordert wirst, ein Projekt zu erstellen oder eine Verknüpfung zu einem vorhandenen Projekt herzustellen, musst du das zuerst tun und dann zu dieser Seite zurückkehren.
- Klicken Sie im Abschnitt zum Definieren von Dienstkonten auf Neues Dienstkonto erstellen.
- Klicken Sie im angezeigten Dialogfeld auf den Link Google Cloud Platform.
- Wählen Sie Ihr Projekt aus. Wenn Sie es nicht sehen, prüfen Sie, ob Sie oben rechts in der Drop-down-Liste Konto im richtigen Google-Konto angemeldet sind.
- Nachdem Sie Ihr Projekt ausgewählt haben, klicken Sie in der oberen Menüleiste auf + Dienstkonto erstellen.
- Geben Sie einen Namen für das Dienstkonto und optional eine Beschreibung ein, damit Sie sich daran erinnern, und fahren Sie mit dem nächsten Schritt fort.
- Weisen Sie dem Dienstkonto die Rolle Bearbeiter zu.
- Schließen Sie den Assistenten ab, kehren Sie in der Entwicklerkonsole zur Seite API-Zugriff zurück und klicken Sie auf Dienstkonten aktualisieren. Das neu erstellte Konto sollte in der Liste angezeigt werden.
- Klicken Sie für das neue Dienstkonto auf Zugriff gewähren.
- Scrollen Sie auf der nächsten Seite nach unten zum Block Finanzdaten. Wählen Sie Finanzdaten, Bestellungen und Antworten aus der Kündigungsumfrage ansehen sowie Bestellungen und Abos verwalten aus.
- Klicken Sie auf Nutzer einladen.
- Nachdem das Konto eingerichtet ist, müssen Sie nur noch einige Anmeldedaten generieren. Gehen Sie zurück zur Cloud Console und suchen Sie in der Liste der Dienstkonten nach Ihrem Dienstkonto. Klicken Sie auf das Dreipunkt-Menü und wählen Sie Schlüssel verwalten aus.
- Erstellen Sie einen neuen JSON-Schlüssel und laden Sie ihn herunter.
- Benennen Sie die heruntergeladene Datei in
service-account-google-play.json,
um und verschieben Sie sie in das Verzeichnisassets/
.
Öffnen Sie lib/constants.dart,
und ersetzen Sie den Wert von androidPackageId
durch die Paket-ID, die Sie für Ihre Android-App ausgewählt haben.
Zugriff auf den Apple App Store einrichten
Um auf den App Store zur Bestätigung von Käufen zugreifen zu können, müssen Sie ein gemeinsames Secret einrichten:
- Öffnen Sie App Store Connect.
- Gehen Sie zu Meine Apps und wählen Sie Ihre App aus.
- Wählen Sie in der Navigationsleiste In-App-Käufe > Verwalten.
- Klicken Sie rechts oben in der Liste auf App-Specific Shared Secret (App-spezifisches gemeinsames Secret).
- Generieren Sie ein neues Secret und kopieren Sie es.
- Öffnen Sie
lib/constants.dart,
und ersetzen Sie den Wert vonappStoreSharedSecret
durch das soeben generierte gemeinsame Secret.
Konstanten-Konfigurationsdatei
Bevor Sie fortfahren, prüfen Sie, ob die folgenden Konstanten in der Datei lib/constants.dart
konfiguriert sind:
androidPackageId
: unter Android verwendete Paket-ID. z.B.com.example.dashclicker
appStoreSharedSecret
: gemeinsames Secret für den Zugriff auf App Store Connect zur Bestätigung von Käufen.bundleId
: Bundle-ID, die unter iOS verwendet wird. z.B.com.example.dashclicker
Die übrigen Konstanten können Sie vorerst ignorieren.
10. Käufe bestätigen
Der allgemeine Ablauf zum Bestätigen von Käufen ist für iOS und Android ähnlich.
In beiden Stores erhält Ihre Anwendung bei einem Kauf ein Token.
Dieses Token wird von der App an Ihren Back-End-Dienst gesendet, der wiederum den Kauf mithilfe des bereitgestellten Tokens auf den Servern des jeweiligen Shops verifiziert.
Der Back-End-Dienst kann dann festlegen, dass der Kauf gespeichert wird, und der Anwendung antworten, unabhängig davon, ob der Kauf gültig war oder nicht.
Wenn der Back-End-Dienst die Validierung in den Shops und nicht in der Anwendung durchführt, die auf dem Gerät des Nutzers ausgeführt wird, können Sie verhindern, dass der Nutzer Zugriff auf Premium-Funktionen erhält, indem Sie beispielsweise seine Systemuhr zurückspulen.
Flutter-Seite einrichten
Authentifizierung einrichten
Wenn Sie die Käufe an Ihren Backend-Dienst senden, möchten Sie sicherstellen, dass der Nutzer während eines Kaufs authentifiziert ist. Der größte Teil der Authentifizierungslogik wurde bereits im Startprojekt für Sie hinzugefügt. Sie müssen nur darauf achten, dass PurchasePage
die Anmeldeschaltfläche anzeigt, wenn der Nutzer noch nicht angemeldet ist. Fügen Sie den folgenden Code am Anfang der Build-Methode von PurchasePage
ein:
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
Endpunkt für die Anrufbestätigung über die App
Erstelle in der App die Funktion _verifyPurchase(PurchaseDetails purchaseDetails)
, die den Endpunkt /verifypurchase
auf deinem Dart-Backend über einen HTTP-Post-Aufruf aufruft.
Sende den ausgewählten Store (google_play
für den Play Store oder app_store
für den App Store), die serverVerificationData
und die productID
. Der Server gibt einen Statuscode zurück, der angibt, ob der Kauf bestätigt wurde.
Konfigurieren Sie in den App-Konstanten die Server-IP-Adresse für die IP-Adresse Ihres lokalen Computers.
lib/logic/dash_purchases.dart
FirebaseNotifier firebaseNotifier;
DashPurchases(this.counter, this.firebaseNotifier) {
// omitted
}
firebaseNotifier
durch Erstellen von DashPurchases
in main.dart:
hinzufügen
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
context.read<FirebaseNotifier>(),
),
lazy: false,
),
Fügen Sie in FirebaseNotifier einen Getter für den Nutzer hinzu, damit Sie die User-ID an die Funktion zur Bestätigung des Kaufs übergeben können.
lib/logic/firebase_notifier.dart
User? get user => FirebaseAuth.instance.currentUser;
Fügen Sie der Klasse DashPurchases
die Funktion _verifyPurchase
hinzu. Diese async
-Funktion gibt einen booleschen Wert zurück, der angibt, ob der Kauf validiert wurde.
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;
}
}
Rufen Sie die Funktion _verifyPurchase
in _handlePurchase
auf, bevor Sie den Kauf anwenden. Du solltest einen Kauf erst dann anwenden, wenn er bestätigt ist. In einer Produktions-App können Sie dies beispielsweise genauer festlegen, wenn Sie beispielsweise ein Probeabo nutzen möchten, wenn der Store vorübergehend nicht verfügbar ist. In diesem Beispiel sollten Sie es einfach halten und den Kauf nur anwenden, wenn er erfolgreich bestätigt wurde.
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);
}
}
In der App ist jetzt alles bereit, um die Käufe zu validieren.
Back-End-Dienst einrichten
Als Nächstes richten Sie die Cloud Functions-Funktion zum Überprüfen von Käufen im Backend ein.
Kauf-Handler erstellen
Da der Bestätigungsvorgang für beide Geschäfte fast identisch ist, richten Sie eine abstrakte PurchaseHandler
-Klasse mit separaten Implementierungen für jedes Geschäft ein.
Füge dem Ordner lib/
zuerst eine purchase_handler.dart
-Datei hinzu, in der du eine abstrakte PurchaseHandler
-Klasse mit zwei abstrakten Methoden zur Bestätigung von zwei verschiedenen Arten von Käufen definierst: Abos und andere Käufe.
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,
});
}
Wie Sie sehen, sind für jede Methode drei Parameter erforderlich:
userId:
Die ID des angemeldeten Nutzers, anhand derer du die Käufe dem Nutzer zuordnen kannst.productData:
Daten zum Produkt. Sie definieren dies gleich.token:
Das Token, das dem Nutzer vom Store bereitgestellt wird.
Zur einfacheren Verwendung dieser Kauf-Handler fügen Sie außerdem eine verifyPurchase()
-Methode hinzu, die sowohl für Abos als auch für Nicht-Abos verwendet werden kann:
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,
);
}
}
Jetzt können Sie einfach für beide Fälle verifyPurchase
aufrufen, haben aber immer noch separate Implementierungen.
Die Klasse ProductData
enthält grundlegende Informationen zu den verschiedenen kaufbaren Produkten, darunter die Produkt-ID (manchmal auch als SKU bezeichnet) und die ProductType
.
lib/products.dart
class ProductData {
final String productId;
final ProductType type;
const ProductData(this.productId, this.type);
}
ProductType
kann entweder ein Abo oder ein Nicht-Abo sein.
lib/products.dart
enum ProductType {
subscription,
nonSubscription,
}
Schließlich wird die Liste der Produkte als Zuordnung in derselben Datei definiert.
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,
),
};
Definieren Sie als Nächstes einige Platzhalterimplementierungen für den Google Play Store und den Apple App Store. Mit Google Play loslegen:
Erstellen Sie lib/google_play_purchase_handler.dart
und fügen Sie eine Klasse hinzu, die die soeben geschriebene PurchaseHandler
erweitert:
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;
}
}
Vorerst wird true
für die Handler-Methoden zurückgegeben. kommen Sie später darauf zurück.
Wie Sie vielleicht bemerkt haben, verwendet der Konstruktor eine Instanz von IapRepository
. Der Kauf-Handler verwendet diese Instanz, um später Informationen zu Käufen in Firestore zu speichern. Für die Kommunikation mit Google Play verwendest du das bereitgestellte AndroidPublisherApi
.
Wiederholen Sie anschließend den Vorgang für den App-Shop-Handler. Erstellen Sie lib/app_store_purchase_handler.dart
und fügen Sie eine Klasse hinzu, die die PurchaseHandler
noch einmal erweitert:
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;
}
}
Sehr gut! Jetzt haben Sie zwei Kauf-Handler. Als Nächstes erstellen wir den API-Endpunkt für die Kaufüberprüfung.
Kauf-Handler verwenden
Öffnen Sie bin/server.dart
und erstellen Sie mit shelf_route
einen API-Endpunkt:
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');
}
}
Der obige Code führt Folgendes aus:
- Definieren Sie einen POST-Endpunkt, der von der zuvor erstellten Anwendung aufgerufen wird.
- Decodieren Sie die JSON-Nutzlast und extrahieren Sie die folgenden Informationen:
userId
: Derzeit angemeldete User-IDsource
: Belegter Speicher,app_store
odergoogle_play
.productData
: Aus der zuvor erstelltenproductDataMap
.token
: Enthält die Verifizierungsdaten, die an die Geschäfte gesendet werden sollen.- Rufen Sie die Methode
verifyPurchase
auf, je nach Quelle fürGooglePlayPurchaseHandler
oderAppStorePurchaseHandler
. - Wenn die Überprüfung erfolgreich war, gibt die Methode
Response.ok
an den Client zurück. - Wenn die Überprüfung fehlschlägt, gibt die Methode eine
Response.internalServerError
an den Client zurück.
Nachdem Sie den API-Endpunkt erstellt haben, müssen Sie die beiden Kauf-Handler konfigurieren. Dazu müssen Sie die Dienstkontoschlüssel laden, die Sie im vorherigen Schritt erhalten haben, und den Zugriff auf die verschiedenen Dienste konfigurieren, einschließlich der Android Publisher API und der Firebase Firestore API. Erstellen Sie dann die beiden Kauf-Handler mit den verschiedenen Abhängigkeiten:
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-Käufe bestätigen: Kaufvorgang implementieren
Implementieren Sie als Nächstes den Google Play-Kauf-Handler.
Google stellt bereits Dart-Pakete für die Interaktion mit den APIs zur Verfügung, die Sie zum Bestätigen von Käufen benötigen. Sie haben sie in der Datei server.dart
initialisiert und jetzt in der Klasse GooglePlayPurchaseHandler
verwendet.
Implementieren Sie den Handler für Käufe ohne Abo:
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;
}
Der Handler für den Abokauf lässt sich auf ähnliche Weise aktualisieren:
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;
}
}
Fügen Sie die folgende Methode hinzu, um das Parsen von Bestell-IDs sowie zwei Methoden zum Parsen des Kaufstatus zu erleichtern.
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,
};
}
Ihre Google Play-Käufe sollten jetzt bestätigt und in der Datenbank gespeichert sein.
Fahren Sie als Nächstes mit den App Store-Käufen für iOS fort.
iOS-Käufe bestätigen: Kauf-Handler implementieren
Für die Verifizierung von Käufen im App Store gibt es ein Drittanbieter-Dart-Paket mit dem Namen app_store_server_sdk
, das den Vorgang vereinfacht.
Erstellen Sie zuerst die Instanz ITunesApi
. Verwenden Sie die Sandbox-Konfiguration und aktivieren Sie das Logging, um die Fehlerbehebung zu erleichtern.
lib/app_store_purchase_handler.dart
final _iTunesAPI = ITunesApi(
ITunesHttpClient(
ITunesEnvironment.sandbox(),
loggingEnabled: true,
),
);
Jetzt verwendet der App Store im Gegensatz zu den Google Play-APIs dieselben API-Endpunkte für Abos und andere Nutzer. Das bedeutet, dass Sie für beide Handler dieselbe Logik verwenden können. Führen Sie sie zusammen, sodass sie dieselbe Implementierung aufrufen:
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 {
//..
}
Implementieren Sie nun 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;
}
}
Ihre App Store-Käufe sollten jetzt bestätigt und in der Datenbank gespeichert sein.
Back-End ausführen
Jetzt können Sie dart bin/server.dart
ausführen, um den Endpunkt /verifypurchase
bereitzustellen.
$ dart bin/server.dart
Serving at http://0.0.0.0:8080
11. Käufe im Blick behalten
Die empfohlene Methode zum Erfassen der Käufe befindet sich im Back-End-Dienst. Das liegt daran, dass Ihr Back-End auf Ereignisse aus dem Speicher reagieren kann und somit weniger anfällig für veraltete Informationen durch Caching ist. Außerdem ist es weniger anfällig für Manipulationen.
Richten Sie zuerst die Verarbeitung von Ladenereignissen auf dem Back-End mit dem Dart-Back-End ein, das Sie erstellt haben.
Speicherereignisse auf dem Back-End verarbeiten
Geschäfte können Ihr Backend über alle Abrechnungsereignisse informieren, z. B. wenn Abos verlängert werden. Sie können diese Ereignisse in Ihrem Backend verarbeiten, um die Käufe in Ihrer Datenbank auf dem neuesten Stand zu halten. In diesem Abschnitt können Sie dies sowohl für den Google Play Store als auch für den Apple App Store einrichten.
Google Play Billing-Ereignisse verarbeiten
Google Play stellt Abrechnungsereignisse über sogenannte Cloud Pub/Sub-Themen bereit. Dies sind im Wesentlichen Nachrichtenwarteschlangen, in denen Nachrichten veröffentlicht und aus denen sie abgerufen werden können.
Da diese Funktion spezifisch für Google Play ist, nehmen Sie diese Funktion in die GooglePlayPurchaseHandler
auf.
Öffnen Sie zuerst lib/google_play_purchase_handler.dart
und fügen Sie den PubsubApi-Import hinzu:
lib/google_play_purchase_handler.dart
import 'package:googleapis/pubsub/v1.dart' as pubsub;
Übergeben Sie dann PubsubApi
an GooglePlayPurchaseHandler
und ändern Sie den Klassenkonstruktor so, dass ein Timer
erstellt wird:
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
ist so konfiguriert, dass die Methode _pullMessageFromSubSub
alle zehn Sekunden aufgerufen wird. Sie können die Dauer nach Bedarf anpassen.
Erstellen Sie dann den _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,
);
}
Der soeben hinzugefügte Code kommuniziert alle zehn Sekunden mit dem Pub/Sub-Thema von Google Cloud und fragt nach neuen Nachrichten. Anschließend wird jede Nachricht in der Methode _processMessage
verarbeitet.
Mit dieser Methode werden die eingehenden Nachrichten decodiert und die aktualisierten Informationen zu jedem Kauf (sowohl Abos als auch Nicht-Abos) werden abgerufen, wobei bei Bedarf die vorhandene handleSubscription
oder handleNonSubscription
aufgerufen wird.
Jede Nachricht muss mit der Methode _askMessage
bestätigt werden.
Als Nächstes fügen Sie der Datei server.dart
die erforderlichen Abhängigkeiten hinzu. Fügen Sie der Konfiguration der Anmeldedaten den PubsubApi.cloudPlatformScope hinzu:
bin/server.dart
final clientGooglePlay =
await auth.clientViaServiceAccount(clientCredentialsGooglePlay, [
ap.AndroidPublisherApi.androidpublisherScope,
pubsub.PubsubApi.cloudPlatformScope, // new
]);
Erstellen Sie dann die PubsubApi-Instanz:
bin/server.dart
final pubsubApi = pubsub.PubsubApi(clientGooglePlay);
Übergeben Sie es abschließend an den GooglePlayPurchaseHandler
-Konstruktor:
bin/server.dart
return {
'google_play': GooglePlayPurchaseHandler(
androidPublisher,
iapRepository,
pubsubApi, // new
),
'app_store': AppStorePurchaseHandler(
iapRepository,
),
};
Google Play einrichten
Sie haben den Code zur Verarbeitung von Abrechnungsereignissen aus dem Pub/Sub-Thema geschrieben, aber weder das Pub/Sub-Thema erstellt noch veröffentlichen Sie Abrechnungsereignisse. Jetzt ist es Zeit für die Einrichtung.
Erstellen Sie zuerst ein Pub/Sub-Thema:
- Rufen Sie in der Google Cloud Console die Cloud Pub/Sub-Seite auf.
- Achten Sie darauf, dass Sie sich in Ihrem Firebase-Projekt befinden, und klicken Sie auf + Thema erstellen.
- Geben Sie dem neuen Thema einen Namen, der mit dem Wert identisch ist, der in
constants.ts
fürGOOGLE_PLAY_PUBSUB_BILLING_TOPIC
festgelegt wurde. Nennen Sie es in diesem Fallplay_billing
. Wenn du etwas anderes auswählst, aktualisiereconstants.ts
. Erstellen Sie das Thema. - Klicken Sie in der Liste der Pub/Sub-Themen neben dem gerade erstellten Thema auf das Dreipunkt-Menü und dann auf Berechtigungen ansehen.
- Wählen Sie in der rechten Seitenleiste Hauptkonto hinzufügen aus.
- Fügen Sie hier
google-play-developer-notifications@system.gserviceaccount.com
hinzu und weisen Sie ihm die Rolle Pub/Sub-Publisher zu. - Speichern Sie die Änderungen an den Berechtigungen.
- Kopieren Sie den Namen des Themas, das Sie gerade erstellt haben.
- Öffnen Sie die Play Console wieder und wählen Sie Ihre App aus der Liste Alle Apps aus.
- Scrolle nach unten und gehe zu Monetarisieren > Einrichtung der Monetarisierung:
- Füllen Sie das vollständige Thema aus und speichern Sie Ihre Änderungen.
Alle Google Play Billing-Ereignisse werden jetzt zu diesem Thema veröffentlicht.
App Store-Abrechnungsereignisse verarbeiten
Wiederhole den Vorgang für die Abrechnungsereignisse im App Store. Es gibt zwei effektive Möglichkeiten, die Verarbeitung von Updates bei Käufen für den App Store zu implementieren. Eine Möglichkeit besteht darin, einen Webhook zu implementieren, den Sie für Apple bereitstellen und der für die Kommunikation mit Ihrem Server verwendet wird. Die zweite Möglichkeit, die Sie in diesem Codelab finden, besteht darin, eine Verbindung zur App Store Server API herzustellen und die Aboinformationen manuell abzurufen.
Dieses Codelab konzentriert sich hauptsächlich auf die zweite Lösung, weil Sie Ihren Server zum Implementieren des Webhooks im Internet verfügbar machen müssen.
In einer Produktionsumgebung würden Sie idealerweise beides haben. Der Webhook, um Ereignisse aus dem App Store und der Server API abzurufen, falls Sie ein Ereignis verpasst haben oder den Abostatus noch einmal prüfen müssen.
Öffnen Sie zuerst lib/app_store_purchase_handler.dart
und fügen Sie die AppStoreServerAPI-Abhängigkeit hinzu:
lib/app_store_purchase_handler.dart
final AppStoreServerAPI appStoreServerAPI;
AppStorePurchaseHandler(
this.iapRepository,
this.appStoreServerAPI, // new
)
Fügen Sie dem Konstruktor einen Timer hinzu, der die Methode _pullStatus
aufruft. Dieser Timer ruft alle 10 Sekunden die Methode _pullStatus
auf. Sie können die Dauer des Timers an Ihre Bedürfnisse anpassen.
lib/app_store_purchase_handler.dart
AppStorePurchaseHandler(
this.iapRepository,
this.appStoreServerAPI,
) {
// Poll Subscription status every 10 seconds.
Timer.periodic(Duration(seconds: 10), (_) {
_pullStatus();
});
}
Erstellen Sie dann die _pullStatus-Methode wie folgt:
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,
));
}
}
}
}
Diese Methode funktioniert so:
- Ruft die Liste der aktiven Abos aus Firestore mithilfe des IapRepository ab.
- Für jede Bestellung wird der Abostatus an die App Store Server API gesendet.
- Erhält die letzte Transaktion für diesen Abonnementkauf.
- Prüft das Ablaufdatum.
- Aktualisiert den Abostatus in Firestore. Wenn das Abo abgelaufen ist, wird es entsprechend markiert.
Fügen Sie abschließend den gesamten erforderlichen Code zum Konfigurieren des App Store Server-API-Zugriffs hinzu:
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 Store einrichten
Als Nächstes richten Sie den App Store ein:
- Melden Sie sich in App Store Connect an und wählen Sie Users and Access (Nutzer und Zugriff) aus.
- Gehen Sie zu Schlüsseltyp > In-App-Käufe:
- Tippen Sie auf das Pluszeichen. um ein neues hinzuzufügen.
- Geben Sie einen Namen ein, z.B. „Codelab-Schlüssel“.
- Laden Sie die p8-Datei herunter, die den Schlüssel enthält.
- Kopieren Sie es in den Asset-Ordner mit dem Namen
SubscriptionKey.p8
. - Kopieren Sie die Schlüssel-ID aus dem neu erstellten Schlüssel und setzen Sie sie in der Datei
lib/constants.dart
auf die KonstanteappStoreKeyId
. - Kopieren Sie die Aussteller-ID ganz oben in der Schlüsselliste und setzen Sie sie in der Datei
lib/constants.dart
auf die KonstanteappStoreIssuerId
.
Käufe auf dem Gerät verfolgen
Die sicherste Methode, Ihre Käufe zu verfolgen, ist serverseitig, da der Client schwer zu sichern ist. Sie müssen jedoch eine Möglichkeit haben, die Informationen an den Client zurückzusenden, damit die App auf die Informationen zum Abostatus reagieren kann. Durch das Speichern der Käufe in Firestore können Sie die Daten ganz einfach mit dem Client synchronisieren und sie automatisch aktualisieren lassen.
Sie haben IAPRepo bereits in die Anwendung eingefügt. Dies ist das Firestore-Repository, das alle Kaufdaten des Nutzers in List<PastPurchase> purchases
enthält. Das Repository enthält außerdem hasActiveSubscription,
, was „true“ ist, wenn es einen Kauf mit productId storeKeySubscription
mit einem Status gibt, der nicht abgelaufen ist. Wenn der Nutzer nicht angemeldet ist, ist die Liste leer.
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();
});
}
Die gesamte Kauflogik befindet sich in der Klasse DashPurchases
. Hier sollten Abos angewendet oder entfernt werden. Fügen Sie der Klasse also das iapRepo
als Attribut hinzu und weisen Sie das iapRepo
im Konstruktor zu. Als Nächstes fügen Sie dem Konstruktor direkt einen Listener hinzu und entfernen den Listener in der Methode dispose()
. Zunächst kann der Listener einfach eine leere Funktion sein. Da die IAPRepo
ein ChangeNotifier
ist und Sie jedes Mal notifyListeners()
aufrufen, wenn sich die Käufe in Firestore ändern, wird die Methode purchasesUpdate()
immer aufgerufen, wenn sich die gekauften Produkte ändern.
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
}
Geben Sie als Nächstes das IAPRepo
an den Konstruktor in main.dart.
. Sie können das Repository mit context.read
abrufen, da es bereits in einem Provider
erstellt wurde.
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
context.read<FirebaseNotifier>(),
context.read<IAPRepo>(),
),
lazy: false,
),
Schreiben Sie als Nächstes den Code für die Funktion purchaseUpdate()
. In dash_counter.dart,
setzen die Methoden applyPaidMultiplier
und removePaidMultiplier
den Multiplikator auf 10 bzw. 1, sodass du nicht prüfen musst, ob das Abo bereits angewendet wird. Wenn sich der Abostatus ändert, aktualisieren Sie auch den Status des käuflichen Produkts, sodass auf der Kaufseite angezeigt wird, dass es bereits aktiv ist. Legen Sie das Attribut _beautifiedDashUpgrade
abhängig davon fest, ob das Upgrade gekauft wird.
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();
}
}
Sie haben nun dafür gesorgt, dass der Abo- und Upgradestatus im Backend-Dienst immer aktuell ist und mit der App synchronisiert wird. Die App verhält sich entsprechend und wendet die Abo- und Upgrade-Funktionen auf Ihr Dash-Clicker-Spiel an.
12. Fertig
Herzlichen Glückwunsch!!! Du hast das Codelab abgeschlossen. Den fertigen Code für dieses Codelab findest du im Ordner Vollständig.
Weitere Informationen finden Sie in den anderen Flutter-Codelabs.