1. Einführung
Wenn Sie einer Flutter-App In-App-Käufe hinzufügen möchten, müssen Sie den App Store und den Play Store richtig einrichten, den Kauf bestätigen und die erforderlichen Berechtigungen gewähren, z. B. für Abovorteile.
In diesem Codelab fügen Sie einer App (die Ihnen zur Verfügung gestellt wird) drei Arten von In-App-Käufen hinzu und bestätigen diese Käufe mit einem Dart-Backend mit Firebase. Die bereitgestellte App „Dash Clicker“ enthält ein Spiel, in dem das Dash-Maskottchen als Währung verwendet wird. Fügen Sie die folgenden Kaufoptionen hinzu:
- Eine wiederholbare Kaufoption für 2.000 Dashes auf einmal.
- Ein einmaliger Kauf eines Upgrades, um das alte Dash-Design in ein modernes Dash-Design umzuwandeln.
- Ein Abo, mit dem die Anzahl der automatisch generierten Klicks verdoppelt wird.
Bei der ersten Kaufoption erhält der Nutzer direkt 2.000 Dashes. Sie sind direkt für den Nutzer verfügbar und können mehrmals gekauft werden. Dieser wird als Verbrauchsmaterial bezeichnet, da er direkt verbraucht und mehrmals verwendet werden kann.
Die zweite Option aktualisiert das Dashboard zu einem schöneren Dashboard. Dieser muss nur einmal gekauft werden und ist dann dauerhaft verfügbar. Ein solcher Kauf wird als nicht verbrauchbar bezeichnet, da er nicht von der App genutzt werden kann, aber dauerhaft gültig ist.
Die dritte und letzte Kaufoption ist ein Abo. Solange das Abo aktiv ist, erhält der Nutzer Dashes schneller. Wenn er das Abo nicht mehr bezahlt, entfallen auch die Vorteile.
Der Back-End-Dienst (wird ebenfalls für Sie bereitgestellt) wird als Dart-App ausgeführt, prüft, ob Käufe getätigt wurden, und speichert sie mit Firestore. Firestore wird verwendet, um den Prozess zu vereinfachen. In Ihrer Produktions-App können Sie jedoch jeden beliebigen Back-End-Dienst verwenden.
Aufgaben
- Sie erweitern eine App, um Verbrauchskäufe und Abos zu unterstützen.
- Außerdem erweitern Sie eine Dart-Backend-App, um die gekauften Artikel zu überprüfen und zu speichern.
Lerninhalte
- App Store und Play Store mit käuflichen Produkten konfigurieren
- Wie du mit den Geschäften kommunizierst, um Käufe zu bestätigen und in Firestore zu speichern.
- So verwalten Sie Käufe in Ihrer App.
Voraussetzungen
- Android Studio 4.1 oder höher
- Xcode 12 oder höher (für die iOS-Entwicklung)
- Flutter SDK
2. Entwicklungsumgebung einrichten
Laden Sie den Code herunter und ändern Sie die Bundle-ID für iOS und den Paketnamen für Android, um mit diesem Codelab zu beginnen.
Code herunterladen
Um das GitHub-Repository über die Befehlszeile zu klonen, verwenden Sie den folgenden Befehl:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Wenn Sie die Befehlszeile von GitHub installiert haben, verwenden Sie den folgenden Befehl:
gh repo clone flutter/codelabs flutter-codelabs
Der Beispielcode wird in ein Verzeichnis flutter-codelabs
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, die zeigen, wo Sie am Ende jedes benannten Schritts sein sollten. Der Startcode befindet sich in Schritt 0. So finden Sie die entsprechenden Dateien ganz einfach:
cd flutter-codelabs/in_app_purchases/step_00
Wenn Sie vorspulen oder sehen möchten, wie etwas nach einem Schritt aussehen sollte, suchen Sie im Verzeichnis, das nach dem gewünschten Schritt benannt ist. Der Code für den letzten Schritt befindet sich im Ordner complete
.
Startprojekt einrichten
Öffnen Sie das Starterprojekt aus step_00
in Ihrer bevorzugten IDE. Wir haben für die Screenshots Android Studio verwendet, aber auch Visual Studio Code ist eine gute Option. Achten Sie bei beiden Editoren darauf, dass die neuesten Dart- und Flutter-Plug-ins installiert sind.
Die von Ihnen erstellten Apps müssen mit dem App Store und dem Play Store kommunizieren, um zu erfahren, welche Produkte zu welchem Preis verfügbar sind. Jede App wird durch eine eindeutige ID identifiziert. Im iOS App Store wird dies als Bundle-ID bezeichnet, im Android Play Store als Anwendungs-ID. Diese IDs werden in der Regel in umgekehrter Domainnamenschreibweise angegeben. Wenn Sie beispielsweise eine App mit In-App-Käufen für flutter.dev erstellen, verwenden Sie dev.flutter.inapppurchase
. Überlegen Sie sich eine Kennung für Ihre App. Sie legen diese jetzt in den Projekteinstellungen fest.
Richten Sie zuerst die Paket-ID für iOS ein.
Klicken Sie bei geöffnetem Projekt in Android Studio mit der rechten Maustaste auf den iOS-Ordner, dann 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 Produkte befinden sich unter dem Runner-Projekt. Doppelklicken Sie auf Runner, um die Projekteinstellungen zu bearbeiten, und klicken Sie auf Signatur und Funktionen. Geben Sie die gerade ausgewählte Bundle-ID in das Feld Team ein, um Ihr Team festzulegen.
Sie können Xcode jetzt 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 die applicationId
(in Zeile 37 im Screenshot unten) in die Anwendungs-ID, die mit der iOS-Bundle-ID übereinstimmt. Die IDs für den iOS- und Android-Shop müssen nicht identisch sein. Es ist jedoch weniger fehleranfällig, wenn sie identisch sind. Daher verwenden wir in diesem Codelab auch identische Kennungen.
3. Plug-in installieren
In diesem Teil des Codelabs installieren Sie das Plug-in „in_app_purchase“.
Abhängigkeit in der pubspec hinzufügen
Fügen Sie der Pubspec in_app_purchase
hinzu, indem Sie in_app_purchase
zu den Abhängigkeiten in der Pubspec hinzufügen:
$ cd app $ flutter pub add in_app_purchase dev:in_app_purchase_platform_interface
Öffnen Sie pubspec.yaml
und prüfen Sie, ob in_app_purchase
jetzt als Eintrag unter dependencies
und in_app_purchase_platform_interface
unter dev_dependencies
aufgeführt ist.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
cloud_firestore: ^5.5.1
cupertino_icons: ^1.0.8
firebase_auth: ^5.3.4
firebase_core: ^3.8.1
google_sign_in: ^6.2.2
http: ^1.2.2
intl: ^0.20.1
provider: ^6.1.2
in_app_purchase: ^3.2.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^4.0.0
in_app_purchase_platform_interface: ^1.4.0
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-Produkte einrichten und auf iOS-Geräten testen möchten, müssen Sie im App Store eine neue App erstellen und dort Produkte zum Verkauf anbieten. Sie müssen nichts veröffentlichen oder die App zur Überprüfung an Apple senden. Dazu benötigen Sie ein Entwicklerkonto. Wenn Sie noch keines haben, melden Sie sich beim Apple-Entwicklerprogramm an.
Vereinbarungen zu kostenpflichtigen Apps
Wenn Sie In-App-Käufe verwenden möchten, benötigen Sie außerdem eine aktive Vereinbarung für kostenpflichtige Apps in App Store Connect. Rufen Sie https://appstoreconnect.apple.com/ auf und klicken Sie auf Vereinbarungen, Steuern und Banken.
Hier finden Sie Vereinbarungen für kostenlose und kostenpflichtige Apps. Der Status von kostenlosen Apps sollte „Aktiv“ und der Status von kostenpflichtigen Apps „Neu“ sein. Lesen Sie sich die Nutzungsbedingungen durch, akzeptieren Sie sie und geben Sie alle erforderlichen Informationen ein.
Wenn alles richtig eingerichtet ist, ist der Status für kostenpflichtige Apps „Aktiv“. Das ist sehr wichtig, da Sie ohne aktive Vereinbarung keine In-App-Käufe testen können.
App-ID registrieren
Erstellen Sie eine neue Kennung im Apple Developer Portal.
App-IDs auswählen
App auswählen
Geben Sie eine Beschreibung ein und legen Sie die Paket-ID so fest, dass sie mit dem Wert übereinstimmt, der zuvor in Xcode festgelegt wurde.
Weitere Informationen zum Erstellen einer neuen App-ID finden Sie in der Entwicklerkonto-Hilfe .
Neue App erstellen
Erstellen Sie in App Store Connect eine neue App mit Ihrer eindeutigen Bundle-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. Klicken Sie auf der Seite Nutzer und Zugriff unter Sandbox auf Tester, um ein neues Sandbox-Konto zu erstellen oder die vorhandenen Sandbox-Apple-IDs zu verwalten.
Sie können jetzt Ihren Sandbox-Nutzer auf Ihrem iPhone einrichten. Gehen Sie dazu zu Einstellungen > App Store > Sandbox-Konto.
In-App-Käufe konfigurieren
Konfigurieren Sie jetzt die drei käuflichen Artikel:
dash_consumable_2k
: Ein Verbrauchsartikel, der mehrmals gekauft werden kann und dem Nutzer pro Kauf 2.000 Dashes (die In-App-Währung) gewährt.dash_upgrade_3d
: Ein nicht konsumierbares „Upgrade“, das nur einmal gekauft werden kann und dem Nutzer ein optisch anderes Dashboard bietet.dash_subscription_doubler
: Ein Abo, das dem Nutzer für die Dauer des Abos doppelt so viele Dashes pro Klick gewährt.
Gehen Sie zu In-App-Käufe > Verwalten.
Erstellen Sie Ihre In-App-Käufe mit den angegebenen IDs:
- Richten Sie
dash_consumable_2k
als Verbrauchsartikel ein.
Verwenden Sie dash_consumable_2k
als Produkt-ID. Der Referenzname wird nur in App Store Connect verwendet. Legen Sie ihn einfach auf dash consumable 2k
fest und fügen Sie Ihre Lokalisierungen für den Kauf hinzu. Rufe den Kauf Spring is in the air
mit 2000 dashes fly out
als Beschreibung auf.
- Richten Sie
dash_upgrade_3d
als Nicht verbrauchbare Ressource ein.
Verwenden Sie dash_upgrade_3d
als Produkt-ID. Legen Sie den Referenznamen auf dash upgrade 3d
fest und fügen Sie Ihre Lokalisierungen für den Kauf hinzu. Rufe den Kauf 3D Dash
mit Brings your dash back to the future
als Beschreibung auf.
- Richten Sie
dash_subscription_doubler
als Abo mit automatischer Verlängerung ein.
Bei Abos sieht der Ablauf etwas anders aus. Legen Sie zuerst den Referenznamen und die Produkt-ID fest:
Als Nächstes müssen Sie eine Abogruppe erstellen. Wenn mehrere Abos zu derselben Gruppe gehören, kann ein Nutzer nur eines davon gleichzeitig abonnieren. Er kann jedoch ganz einfach zwischen diesen Abos wechseln. Nennen Sie diese Gruppe einfach subscriptions
.
Geben Sie als Nächstes die Abolaufzeit und die Lokalisierungen ein. Benennen Sie dieses Abo Jet Engine
und geben Sie die Beschreibung Doubles your clicks
an. 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.
Die drei Käufe sollten jetzt in der Liste der Käufe angezeigt werden:
5. Play Store einrichten
Wie beim App Store benötigen Sie auch für den Play Store ein Entwicklerkonto. Wenn Sie noch kein Konto haben, registrieren Sie sich hier.
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 aus.
- 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 um ein Spiel handelt. Sie können dies später ändern.
- Geben Sie an, ob Ihre App kostenlos oder kostenpflichtig ist.
- Fügen Sie eine E-Mail-Adresse hinzu, unter der Play Store-Nutzer Sie bezüglich dieser App kontaktieren können.
- Machen Sie die erforderlichen Angaben für die Deklarationen „Inhaltsrichtlinien“ und „Exportbestimmungen der USA“.
- Wählen Sie App erstellen aus.
Nachdem Sie Ihre App erstellt haben, rufen Sie das Dashboard auf und führen Sie alle Aufgaben im Bereich App einrichten aus. Hier geben Sie einige Informationen zu Ihrer App an, z. B. die Altersfreigabe und Screenshots.
Anwendung signieren
Damit Sie In-App-Käufe testen können, muss mindestens ein Build bei Google Play hochgeladen werden.
Dazu muss Ihr Release-Build mit etwas anderem als den Debug-Schlüsseln signiert sein.
Schlüsselspeicher erstellen
Wenn Sie bereits einen Schlüsselspeicher haben, fahren Sie mit dem nächsten Schritt fort. Falls nicht, erstellen Sie einen, indem Sie in der Befehlszeile Folgendes eingeben:
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 key.jks
-Datei 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. Behalten Sie die
keystore
Datei privat halten; nicht in die öffentliche Versionsverwaltung einchecken!
Schlüsselspeicher aus der App referenzieren
Erstellen Sie eine Datei mit dem Namen <your app dir>/android/key.properties
, die eine Referenz 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>
Signatur in Gradle 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 Informationen zum Schlüsselspeicher aus Ihrer Properties-Datei vor dem Block android
ein:
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 Objekt keystoreProperties
.
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 Ihrer App werden jetzt automatisch signiert.
Weitere Informationen zum Signieren Ihrer App finden Sie unter App signieren auf developer.android.com.
Ersten Build hochladen
Nachdem Ihre App für die Signatur konfiguriert wurde, können Sie sie mit dem folgenden Befehl erstellen:
flutter build appbundle
Mit diesem Befehl wird standardmäßig ein Release-Build generiert. Die Ausgabe finden Sie unter <your app dir>/build/app/outputs/bundle/release/
.
Klicken Sie im Dashboard der Google Play Console auf Release > Test > Geschlossener Test und erstellen Sie eine neue Release-Version für geschlossene Tests.
In diesem Codelab wird die App von Google signiert. Klicken Sie daher unter Play App Signing auf Weiter, um die Funktion zu aktivieren.
Laden Sie als Nächstes das app-release.aab
-App-Bundle hoch, das mit dem Befehl „build“ generiert wurde.
Klicken Sie auf Speichern und dann auf Release überprüfen.
Klicken Sie abschließend auf Roll-out für interne Tests starten, um den internen Testrelease zu aktivieren.
Testnutzer einrichten
Damit Sie In-App-Käufe testen können, müssen die Google-Konten Ihrer Tester an zwei Stellen in der Google Play Console hinzugefügt werden:
- Zum spezifischen Test-Track (interne Tests)
- Als Lizenztester
Fügen Sie den Tester zuerst dem internen Test-Track hinzu. Kehren Sie zu Release > Test > Interner Test zurück und klicken Sie auf den Tab Tester.
Klicken Sie auf E-Mail-Liste erstellen, um eine neue E-Mail-Liste zu erstellen. Geben Sie der Liste einen Namen und fügen Sie die E-Mail-Adressen der Google-Konten hinzu, die Zugriff auf das Testen von In-App-Käufen benötigen.
Klicken Sie dann auf das Kästchen für die Liste und dann auf Änderungen speichern.
Fügen Sie dann die Lizenztester hinzu:
- Kehren Sie in der Google Play Console zur Ansicht Alle Apps zurück.
- Gehen Sie zu Einstellungen > Lizenztest.
- Fügen Sie die E-Mail-Adressen der Tester hinzu, die In-App-Käufe testen sollen.
- Legen Sie für License response (Lizenzantwort) die Option
RESPOND_NORMALLY
fest. - Klicken Sie auf ROLLE ERSTELLEN.
In-App-Käufe konfigurieren
Jetzt 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 Verbrauchsartikel, der mehrmals gekauft werden kann und dem Nutzer pro Kauf 2.000 Dashes (die In-App-Währung) gewährt.dash_upgrade_3d
: Ein nicht verbrauchbares „Upgrade“, das nur einmal gekauft werden kann und dem Nutzer ein optisch anderes Dashboard bietet.dash_subscription_doubler
: Ein Abo, das dem Nutzer für die Dauer des Abos doppelt so viele Dashes pro Klick gewährt.
Fügen Sie zuerst die Verbrauchsmaterialien und die nicht abnutzbaren Artikel hinzu.
- Rufen Sie die Google Play Console auf und wählen Sie Ihre Anwendung aus.
- Gehen Sie zu Monetarisieren > Produkte > In-App-Produkte.
- Klicken Sie auf Produkt erstellen
.
- Geben Sie alle erforderlichen Informationen zu Ihrem Produkt ein. Die Produkt-ID muss genau mit der ID übereinstimmen, die Sie verwenden möchten.
- Klicken Sie auf Speichern.
- Klicken Sie auf Aktivieren.
- Wiederholen Sie den Vorgang für den nicht verbrauchbaren Kauf des Upgrades.
Fügen Sie als Nächstes das Abo hinzu:
- Rufen Sie die Google Play Console auf und wählen Sie Ihre Anwendung aus.
- Gehen Sie zu Monetarisieren > Produkte > Abos.
- Klicken Sie auf Abo erstellen.
- Geben Sie alle erforderlichen Informationen für Ihr Abo ein. Die Produkt-ID muss genau mit der ID übereinstimmen, die Sie verwenden möchten.
- Klicken Sie auf Speichern.
Ihre Käufe sollten jetzt in der Play Console eingerichtet sein.
6. Firebase einrichten
In diesem Codelab verwenden Sie einen Back-End-Dienst, um die Käufe von Nutzern zu bestätigen und zu verfolgen.
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 anzubieten, indem sie ihre Systemuhr zurückspulen.
Es gibt viele Möglichkeiten, einen Back-End-Dienst einzurichten. Sie verwenden dazu Cloud Functions und Firestore mit Firebase von Google.
Das Erstellen des Backends fällt nicht in den Rahmen dieses Codelabs. Der Startcode enthält daher bereits ein Firebase-Projekt, das grundlegende Käufe verarbeitet.
Firebase-Plug-ins sind ebenfalls in der Starter-App enthalten.
Jetzt müssen Sie nur noch ein eigenes Firebase-Projekt erstellen, die App und das Backend für Firebase konfigurieren und das Backend schließlich bereitstellen.
Firebase-Projekt erstellen
Rufen Sie die Firebase Console auf und erstellen Sie ein neues Firebase-Projekt. Nennen Sie das Projekt in diesem Beispiel „Dash Clicker“.
In der Back-End-App binden Sie Käufe an einen bestimmten Nutzer. Daher ist eine Authentifizierung erforderlich. Verwenden Sie dazu das Authentifizierungsmodul von Firebase mit Google Log-in.
- Rufen Sie im Firebase-Dashboard Authentifizierung auf und aktivieren Sie sie bei Bedarf.
- Rufen Sie den Tab Anmeldemethode auf und aktivieren Sie den Anmeldeanbieter Google.
Da Sie auch die Firestore-Datenbank von Firebase verwenden werden, aktivieren Sie diese Option ebenfalls.
So legen Sie Cloud Firestore-Regeln 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 für Flutter einrichten
Wir empfehlen, Firebase mit der FlutterFire CLI in der Flutter-App zu installieren. Folgen Sie der Anleitung auf der Einrichtungsseite.
Wählen Sie beim Ausführen von „flutterfire configure“ 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
Wählen Sie „Ja“, wenn Sie gefragt werden, ob Sie „firebase_options.dart“ überschreiben möchten.
? 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
Klicken Sie im Firebase-Dashboard auf 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 Google Sign-in im Debug-Modus zulassen möchten, müssen Sie den SHA-1-Hash-Fingerabdruck Ihres Debugzertifikats angeben.
Hash des Debug-Signaturzertifikats abrufen
Wechseln Sie im Stammverzeichnis Ihres Flutter-App-Projekts zum Ordner android/
und generieren Sie einen Signaturbericht.
cd android ./gradlew :app:signingReport
Es wird eine lange Liste von Signaturschlüsseln angezeigt. Da Sie nach dem Hash für das Debug-Zertifikat suchen, suchen Sie nach dem Zertifikat, für das die Eigenschaften Variant
und Config
auf debug
festgelegt sind. Der Schlüsselspeicher befindet sich wahrscheinlich in Ihrem 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 für die App-Einreichung aus.
Firebase für iOS einrichten: Weitere Schritte
Öffnen Sie die ios/Runnder.xcworkspace
mit Xcode
. oder mit Ihrer bevorzugten IDE.
Klicken Sie in VSCode mit der rechten Maustaste auf den Ordner ios/
und dann auf open in xcode
.
Klicken Sie in Android Studio mit der rechten Maustaste auf den Ordner ios/
, dann auf flutter
und dann auf die Option open iOS module in Xcode
.
Wenn Sie Google Sign-in auf iOS-Geräten zulassen möchten, fügen Sie Ihren Build-plist
-Dateien die Konfigurationsoption CFBundleURLTypes
hinzu. Weitere Informationen finden Sie in der google_sign_in
-Paketdokumentation. In diesem Fall sind das 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
ab, ohne das umgebende<string>..</string>
-Element. - Ersetzen Sie den Wert sowohl in der Datei
ios/Runner/Info-Debug.plist
als auch in der Dateiios/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. Kaufaktualisierungen anhören
In diesem Teil des Codelabs bereiten Sie die App auf den Kauf der Produkte vor. Dazu gehört auch das Abhören von Kaufaktualisierungen und Fehlern nach dem Starten der App.
Updates zu Käufen anhören
Suchen Sie in main.dart,
nach dem Widget MyHomePage
mit einer Scaffold
mit einer BottomNavigationBar
, die zwei Seiten enthält. Auf dieser Seite werden auch drei Provider
s für DashCounter
, DashUpgrades,
und DashPurchases
erstellt. DashCounter
überwacht die aktuelle Anzahl der Striche und erhöht sie automatisch. DashUpgrades
verwaltet die Upgrades, die Sie mit Dashes kaufen 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 beim Starten der App. Deaktiviere daher das Lazy-Loading für dieses Objekt mit lazy: false
:
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
),
lazy: false, // Add this line
),
Außerdem benötigen Sie eine Instanz der InAppPurchaseConnection
. Damit die App jedoch testbar bleibt, müssen Sie die Verbindung irgendwie simulieren. Erstellen Sie dazu eine Instanzmethode, die im Test überschrieben werden kann, und fügen Sie sie 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 leicht aktualisieren, damit er weiterhin funktioniert.
test/widget_test.dart
import 'package:dashclicker/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase/in_app_purchase.dart'; // Add this import
import 'package:in_app_purchase_platform_interface/src/in_app_purchase_platform_addition.dart'; // And this import
void main() {
testWidgets('App starts', (tester) async {
IAPConnection.instance = TestIAPConnection(); // Add this line
await tester.pumpWidget(const MyApp());
expect(find.text('Tim Sneath'), findsOneWidget);
});
}
class TestIAPConnection implements InAppPurchase { // Add from here
@override
Future<bool> buyConsumable(
{required PurchaseParam purchaseParam, bool autoConsume = true}) {
return Future.value(false);
}
@override
Future<bool> buyNonConsumable({required PurchaseParam purchaseParam}) {
return Future.value(false);
}
@override
Future<void> completePurchase(PurchaseDetails purchase) {
return Future.value();
}
@override
Future<bool> isAvailable() {
return Future.value(false);
}
@override
Future<ProductDetailsResponse> queryProductDetails(Set<String> identifiers) {
return Future.value(ProductDetailsResponse(
productDetails: [],
notFoundIDs: [],
));
}
@override
T getPlatformAddition<T extends InAppPurchasePlatformAddition?>() {
// TODO: implement getPlatformAddition
throw UnimplementedError();
}
@override
Stream<List<PurchaseDetails>> get purchaseStream =>
Stream.value(<PurchaseDetails>[]);
@override
Future<void> restorePurchases({String? applicationUserName}) {
// TODO: implement restorePurchases
throw UnimplementedError();
}
@override
Future<String> countryCode() {
// TODO: implement countryCode
throw UnimplementedError();
}
} // To here.
Rufe in lib/logic/dash_purchases.dart
den Code für DashPurchases ChangeNotifier
auf. Derzeit gibt es nur eine DashCounter
, die Sie Ihren gekauften Dashes hinzufügen können.
Fügen Sie eine Property für das Streamabo, _subscription
(vom Typ StreamSubscription<List<PurchaseDetails>> _subscription;
), die IAPConnection.instance,
und die Importe hinzu. Der Code sollte dann in etwa so aussehen:
lib/logic/dash_purchases.dart
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart'; // Add this import
import '../main.dart'; // And this import
import '../model/purchasable_product.dart';
import '../model/store_state.dart';
import 'dash_counter.dart';
class DashPurchases extends ChangeNotifier {
DashCounter counter;
StoreState storeState = StoreState.available;
late StreamSubscription<List<PurchaseDetails>> _subscription; // Add this line
List<PurchasableProduct> products = [
PurchasableProduct(
'Spring is in the air',
'Many dashes flying out from their nests',
'\$0.99',
),
PurchasableProduct(
'Jet engine',
'Doubles you clicks per second for a day',
'\$1.99',
),
];
bool get beautifiedDash => false;
final iapConnection = IAPConnection.instance; // And this line
DashPurchases(this.counter);
Future<void> buy(PurchasableProduct product) async {
product.status = ProductStatus.pending;
notifyListeners();
await Future<void>.delayed(const Duration(seconds: 5));
product.status = ProductStatus.purchased;
notifyListeners();
await Future<void>.delayed(const Duration(seconds: 5));
product.status = ProductStatus.purchasable;
notifyListeners();
}
}
Das Keyword late
wird _subscription
hinzugefügt, da _subscription
im Konstruktor initialisiert wird. Dieses Projekt ist so eingerichtet, dass standardmäßig keine Nullwerte zulässig sind (Non-Nullable By Default, NNBD). Das bedeutet, dass Eigenschaften, die nicht als nullable deklariert sind, einen Wert haben müssen, der nicht null ist. Mit dem late
-Qualifikator können Sie die Definition dieses Werts verschieben.
Rufe im Konstruktor den purchaseUpdated
-Stream ab und beginne mit dem Streamen. Kündige in der dispose()
-Methode das Stream-Abo.
lib/logic/dash_purchases.dart
class DashPurchases extends ChangeNotifier {
DashCounter counter;
StoreState storeState = StoreState.notAvailable; // Modify this line
late StreamSubscription<List<PurchaseDetails>> _subscription;
List<PurchasableProduct> products = [
PurchasableProduct(
'Spring is in the air',
'Many dashes flying out from their nests',
'\$0.99',
),
PurchasableProduct(
'Jet engine',
'Doubles you clicks per second for a day',
'\$1.99',
),
];
bool get beautifiedDash => false;
final iapConnection = IAPConnection.instance;
DashPurchases(this.counter) { // Add from here
final purchaseUpdated = iapConnection.purchaseStream;
_subscription = purchaseUpdated.listen(
_onPurchaseUpdate,
onDone: _updateStreamOnDone,
onError: _updateStreamOnError,
);
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
} // To here.
Future<void> buy(PurchasableProduct product) async {
product.status = ProductStatus.pending;
notifyListeners();
await Future<void>.delayed(const Duration(seconds: 5));
product.status = ProductStatus.purchased;
notifyListeners();
await Future<void>.delayed(const Duration(seconds: 5));
product.status = ProductStatus.purchasable;
notifyListeners();
}
// Add from here
void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
// Handle purchases here
}
void _updateStreamOnDone() {
_subscription.cancel();
}
void _updateStreamOnError(dynamic error) {
//Handle error here
} // To here.
}
Jetzt erhält die App die Kaufaktualisierungen. Im nächsten Abschnitt führen Sie einen Kauf durch.
Führen Sie vor dem Fortfahren die Tests mit „flutter test"
“ aus, um sicherzustellen, dass alles richtig eingerichtet ist.
$ flutter test
00:01 +1: All tests passed!
8. Einkaufen
In diesem Teil des Codelabs ersetzen Sie die derzeit vorhandenen Mockups durch echte, kaufbare Produkte. Diese Produkte werden aus den Shops geladen, in einer Liste angezeigt und können durch Tippen auf das Produkt gekauft werden.
PurchasableProduct anpassen
PurchasableProduct
zeigt ein Mockup eines Produkts. Aktualisieren Sie sie, damit tatsächliche Inhalte angezeigt werden. 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;
}
Entfernen Sie in dash_purchases.dart,
die Dummy-Käufe und ersetzen Sie sie durch eine leere Liste, List<PurchasableProduct> products = [];
Verfügbare Käufe laden
Wenn Sie Nutzern die Möglichkeit geben möchten, etwas zu kaufen, laden Sie die Käufe aus dem Store herunter. Prüfen Sie zuerst, ob der Shop verfügbar ist. Wenn der Store nicht verfügbar ist, wird dem Nutzer beim Festlegen von storeState
auf notAvailable
eine Fehlermeldung angezeigt.
lib/logic/dash_purchases.dart
Future<void> loadPurchases() async {
final available = await iapConnection.isAvailable();
if (!available) {
storeState = StoreState.notAvailable;
notifyListeners();
return;
}
}
Wenn der Store verfügbar ist, lade die verfügbaren Käufe. Aufgrund der vorherigen Firebase-Einrichtung sollten Sie storeKeyConsumable
, storeKeySubscription,
und storeKeyUpgrade
sehen. Wenn ein erwarteter Kauf nicht verfügbar ist, drucke diese Informationen in der Konsole aus. Du kannst diese Informationen auch an den Backend-Dienst senden.
Die await iapConnection.queryProductDetails(ids)
-Methode gibt sowohl die nicht gefundenen IDs als auch die gefundenen kaufbaren Produkte zurück. Verwende den Wert productDetails
aus der Antwort, um die Benutzeroberfläche zu aktualisieren, und setze StoreState
auf available
.
lib/logic/dash_purchases.dart
import '../constants.dart';
Future<void> loadPurchases() async {
final available = await iapConnection.isAvailable();
if (!available) {
storeState = StoreState.notAvailable;
notifyListeners();
return;
}
const ids = <String>{
storeKeyConsumable,
storeKeySubscription,
storeKeyUpgrade,
};
final response = await iapConnection.queryProductDetails(ids);
products = response.productDetails.map((e) => PurchasableProduct(e)).toList();
storeState = StoreState.available;
notifyListeners();
}
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 abschließend den Wert des Felds storeState
von StoreState.available
in StoreState.loading:
.
lib/logic/dash_purchases.dart
StoreState storeState = StoreState.loading;
Kaufbare Produkte anzeigen
Betrachten Sie die Datei purchase_page.dart
. Im PurchasePage
-Widget wird je nach StoreState
_PurchasesLoading
, _PurchaseList,
oder _PurchasesNotAvailable,
angezeigt. 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 käuflichen 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 sie in den Android- und iOS-Shops sehen können. 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 zum Kauf eines Produkts. Sie müssen nur die Verbrauchsmaterialien von den nicht abnutzbaren Materialien trennen. Das Upgrade und die Aboprodukte sind nicht abnutzbare Produkte.
lib/logic/dash_purchases.dart
Future<void> buy(PurchasableProduct product) async {
final purchaseParam = PurchaseParam(productDetails: product.productDetails);
switch (product.id) {
case storeKeyConsumable:
await iapConnection.buyConsumable(purchaseParam: purchaseParam);
case storeKeySubscription:
case storeKeyUpgrade:
await iapConnection.buyNonConsumable(purchaseParam: purchaseParam);
default:
throw ArgumentError.value(
product.productDetails, '${product.id} is not a known product');
}
}
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 _onPurchaseUpdate
-Methode empfängt die Kaufaktualisierungen, aktualisiert den Status des Produkts, der auf der Kaufseite angezeigt wird, und wendet den Kauf auf die Zählerlogik an. Es ist wichtig, completePurchase
nach der Bearbeitung des Kaufs aufzurufen, damit der Händler weiß, dass der Kauf korrekt verarbeitet 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) {
switch (purchaseDetails.productID) {
case storeKeySubscription:
counter.applyPaidMultiplier();
case storeKeyConsumable:
counter.addBoughtDashes(2000);
case storeKeyUpgrade:
_beautifiedDashUpgrade = true;
}
}
if (purchaseDetails.pendingCompletePurchase) {
await iapConnection.completePurchase(purchaseDetails);
}
}
9. Backend einrichten
Bevor Sie mit dem Tracking und der Überprüfung von Käufen fortfahren, richten Sie ein Dart-Backend ein, das dies unterstützt.
In diesem Abschnitt verwenden Sie den Ordner dart-backend/
als Stammordner.
Die folgenden Tools müssen installiert sein:
- Dart
- Firebase CLI
Übersicht über das Basisprojekt
Da einige Teile dieses Projekts für dieses Codelab nicht relevant sind, sind sie im Startcode enthalten. Es ist eine gute Idee, sich anzusehen, was bereits im Startercode enthalten ist, bevor Sie beginnen, 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 zu verwenden. Sie müssen jedoch von Ihrem Entwicklungsgerät (Android oder iPhone) eine Verbindung zum 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 einen Routenabschnitt, um den Kaufbestätigungsprozess zu verarbeiten.
Ein Teil, der bereits im Startcode enthalten ist, ist die IapRepository
in lib/iap_repository.dart
. Da das Interagieren mit Firestore oder Datenbanken im Allgemeinen für dieses Codelab nicht relevant ist, enthält der Startercode 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 ein Dienstkonto. Sie können einen privaten Schlüssel generieren, indem Sie die Firebase-Projekteinstellungen öffnen, zum Abschnitt Dienstkonten wechseln und dann Neuen privaten Schlüssel generieren auswählen.
Kopieren Sie die heruntergeladene JSON-Datei in den Ordner assets/
und benennen Sie sie in service-account-firebase.json
um.
Google Play-Zugriff einrichten
Wenn Sie auf den Play Store zugreifen möchten, um Käufe zu bestätigen, müssen Sie ein Dienstkonto mit diesen Berechtigungen generieren 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 Einrichten > API-Zugriff.
Wenn Sie in der Google Play Console aufgefordert werden, ein neues Projekt zu erstellen oder eine Verknüpfung mit einem vorhandenen Projekt herzustellen, tun Sie dies zuerst und kehren Sie dann zu dieser Seite zurück.
- Suchen Sie den Bereich, in dem Sie Dienstkonten definieren können, und klicken Sie auf Neues Dienstkonto erstellen.
- Klicken Sie im angezeigten Dialogfeld auf den Link Google Cloud Platform.
- Wählen Sie Ihr Projekt aus. Wenn Sie die Option nicht sehen, prüfen Sie, ob Sie oben rechts im Drop-down-Menü Konto im richtigen Google-Konto angemeldet sind.
- Wählen Sie Ihr Projekt aus und klicken Sie oben in der Menüleiste auf + Dienstkonto erstellen.
- Geben Sie einen Namen für das Dienstkonto und optional eine Beschreibung ein, damit Sie sich erinnern können, wofür es gedacht ist, 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 Ihr neues Dienstkonto auf Zugriff gewähren.
- Scrollen Sie auf der nächsten Seite nach unten zum Abschnitt Finanzdaten. Wählen Sie sowohl Finanzdaten, Bestellungen und Antworten aus der Kündigungsumfrage ansehen als auch Bestellungen und Abos verwalten aus.
- Klicken Sie auf Nutzer einladen.
- Nachdem das Konto eingerichtet ist, müssen Sie nur noch Anmeldedaten generieren. Suchen Sie in der Cloud Console in der Liste der Dienstkonten nach Ihrem Dienstkonto, klicken Sie auf die drei vertikalen Punkte 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 außerdem 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
Wenn Sie auf den App Store zugreifen und Käufe bestätigen möchten, müssen Sie ein gemeinsames Secret einrichten:
- Öffnen Sie App Store Connect.
- Gehen Sie zu Meine Apps und wählen Sie Ihre App aus.
- Klicken Sie in der Seitenleiste auf In-App-Käufe > Verwalten.
- Klicken Sie rechts oben in der Liste auf App-spezifisches freigegebenes Geheimnis.
- Erstellen Sie ein neues Secret und kopieren Sie es.
- Öffnen Sie
lib/constants.dart,
und ersetzen Sie den Wert vonappStoreSharedSecret
durch das gerade generierte gemeinsame Secret.
Konfigurationsdatei für Konstanten
Bevor Sie fortfahren, prüfen Sie, ob die folgenden Konstanten in der Datei lib/constants.dart
konfiguriert sind:
androidPackageId
: Paket-ID, die auf Android-Geräten verwendet wird, z. B.com.example.dashclicker
appStoreSharedSecret
: Gemeinsames Secret für den Zugriff auf App Store Connect zur Durchführung der Kaufbestätigung.bundleId
: Bundle-ID, die auf iOS-Geräten verwendet wird, z. B.com.example.dashclicker
Die restlichen Konstanten können Sie vorerst ignorieren.
10. Käufe bestätigen
Der allgemeine Ablauf für die Überprüfung von Käufen ist für iOS und Android ähnlich.
Bei beiden Shops erhält Ihre Anwendung bei einem Kauf ein Token.
Dieses Token wird von der App an Ihren Back-End-Dienst gesendet, der den Kauf dann mit dem bereitgestellten Token auf den Servern des jeweiligen Shops bestätigt.
Der Back-End-Dienst kann dann den Kauf speichern und an die Anwendung antworten, ob der Kauf gültig war oder nicht.
Wenn die Validierung durch den Backend-Dienst und nicht durch die Anwendung auf dem Gerät des Nutzers erfolgt, können Sie verhindern, dass der Nutzer Zugriff auf Premium-Funktionen erhält, indem er beispielsweise seine Systemuhr zurückdreht.
Flutter-Seite einrichten
Authentifizierung einrichten
Da Sie die Käufe an Ihren Backend-Dienst senden, sollten Sie darauf achten, dass der Nutzer beim Kauf authentifiziert ist. Der Großteil der Authentifizierungslogik wurde im Starterprojekt bereits für Sie hinzugefügt. Sie müssen nur dafür sorgen, dass die Schaltfläche „Anmelden“ auf der PurchasePage
angezeigt wird, 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({super.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 Bestätigung von Anrufen über die App
Erstellen Sie in der App die Funktion _verifyPurchase(PurchaseDetails purchaseDetails)
, die den Endpunkt /verifypurchase
in Ihrem Dart-Backend über einen HTTP-Postaufruf aufruft.
Senden Sie 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 als IP-Adresse Ihres lokalen Computers.
lib/logic/dash_purchases.dart
FirebaseNotifier firebaseNotifier;
DashPurchases(this.counter, this.firebaseNotifier) {
// omitted
}
firebaseNotifier
beim 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 im FirebaseNotifier einen Getter für den Nutzer hinzu, damit Sie die User-ID an die Funktion „verify purchase“ ü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 bestätigt 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) {
return true;
} else {
return false;
}
}
Rufe die Funktion _verifyPurchase
in _handlePurchase
kurz vor dem Anwenden des Kaufs auf. Sie sollten den Kauf erst anwenden, wenn er bestätigt wurde. In einer Produktions-App können Sie dies weiter spezifizieren, um beispielsweise ein Probeabo anzuwenden, wenn der Store vorübergehend nicht verfügbar ist. Für dieses Beispiel halten wir es jedoch einfach und wenden den Kauf nur an, wenn er 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();
case storeKeyConsumable:
counter.addBoughtDashes(1000);
}
}
}
if (purchaseDetails.pendingCompletePurchase) {
await iapConnection.completePurchase(purchaseDetails);
}
}
In der App ist jetzt alles für die Validierung der Käufe bereit.
Backend-Dienst einrichten
Richten Sie als Nächstes die Cloud-Funktion für die Überprüfung von Käufen im Backend ein.
Kauf-Handler erstellen
Da der Bestätigungsablauf für beide Geschäfte nahezu identisch ist, richten Sie eine abstrakte PurchaseHandler
-Klasse mit separaten Implementierungen für jedes Geschäft ein.
Fügen Sie dem Ordner lib/
zuerst eine purchase_handler.dart
-Datei hinzu. Dort definieren Sie eine abstrakte PurchaseHandler
-Klasse mit zwei abstrakten Methoden zur Überprüfung von zwei verschiedenen Arten von Käufen: Abos und nicht abobasierte 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, damit Sie Käufe dem Nutzer zuordnen können.productData:
Daten zum Produkt. Das definieren Sie gleich.token:
Das vom Händler bereitgestellte Token.
Um die Verwendung dieser Kauf-Handler zu vereinfachen, fügen Sie außerdem eine verifyPurchase()
-Methode hinzu, die sowohl für Abos als auch für nicht abonnierte Produkte 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 verifyPurchase
für beide Fälle aufrufen und trotzdem separate Implementierungen haben.
Die ProductData
-Klasse enthält grundlegende Informationen zu den verschiedenen käuflichen 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 abopflichtiger Artikel sein.
lib/products.dart
enum ProductType {
subscription,
nonSubscription,
}
Schließlich wird die Liste der Produkte in derselben Datei als Zuordnung 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 beginnen:
Erstellen Sie lib/google_play_purchase_handler.dart
und fügen Sie eine Klasse hinzu, die die von Ihnen gerade erstellte 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;
}
}
Derzeit gibt es für die Handlermethoden true
zurück. Dazu kommen wir später.
Wie Sie vielleicht bemerkt haben, nimmt der Konstruktor eine Instanz der IapRepository
an. Der Kauf-Handler verwendet diese Instanz, um Informationen zu Käufen später in Firestore zu speichern. Für die Kommunikation mit Google Play verwenden Sie die angegebene AndroidPublisherApi
.
Wiederholen Sie diese Schritte für den App-Shop-Handler. Erstellen Sie lib/app_store_purchase_handler.dart
und fügen Sie eine Klasse hinzu, 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! Sie haben jetzt zwei Kaufabwickler. Als Nächstes erstellen wir den API-Endpunkt für die Kaufbestätigung.
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.call);
}
({
String userId,
String source,
ProductData productData,
String token,
}) getPurchaseData(dynamic payload) {
if (payload
case {
'userId': String userId,
'source': String source,
'productId': String productId,
'verificationData': String token,
}) {
return (
userId: userId,
source: source,
productData: productDataMap[productId]!,
token: token,
);
} else {
throw const FormatException('Unexpected JSON');
}
}
Der Code oben führt Folgendes aus:
- Definieren Sie einen POST-Endpunkt, der von der zuvor erstellten App aufgerufen wird.
- Dekodieren Sie die JSON-Nutzlast und extrahieren Sie die folgenden Informationen:
userId
: Die ID des aktuell angemeldeten Nutzerssource
: Verwendeter Shop, entwederapp_store
odergoogle_play
.productData
: Wird aus dem zuvor erstelltenproductDataMap
abgerufen.token
: Enthält die Bestätigungsdaten, die an die Geschäfte gesendet werden sollen.- Rufen Sie die Methode
verifyPurchase
auf, je nach Quelle entweder für dieGooglePlayPurchaseHandler
oder dieAppStorePurchaseHandler
. - Wenn die Überprüfung erfolgreich war, gibt die Methode eine
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 im vorherigen Schritt abgerufenen Dienstkontoschlüssel laden 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: Kauf-Handler implementieren
Fahren Sie als Nächstes mit der Implementierung des Google Play-Kauf-Handlers fort.
Google stellt bereits Dart-Pakete für die Interaktion mit den APIs bereit, die Sie zur Überprüfung von Käufen benötigen. Sie haben sie in der Datei server.dart
initialisiert und verwenden sie jetzt in der Klasse GooglePlayPurchaseHandler
.
Implementieren Sie den Handler für Käufe, die nicht dem Abotyp entsprechen:
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;
}
Sie können den Handler für den Abokauf 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üge die folgende Methode zum Parsen von Bestell-IDs sowie zwei Methoden zum Parsen des Kaufstatus hinzu.
lib/google_play_purchase_handler.dart
NonSubscriptionStatus _nonSubscriptionStatusFrom(int? state) {
return switch (state) {
0 => NonSubscriptionStatus.completed,
2 => NonSubscriptionStatus.pending,
_ => NonSubscriptionStatus.cancelled,
};
}
SubscriptionStatus _subscriptionStatusFrom(int? state) {
return switch (state) {
// Payment pending
0 => SubscriptionStatus.pending,
// Payment received
1 => SubscriptionStatus.active,
// Free trial
2 => SubscriptionStatus.active,
// Pending deferred upgrade/downgrade
3 => SubscriptionStatus.pending,
// Expired or cancelled
_ => SubscriptionStatus.expired,
};
}
/// If a subscription suffix is present (..#) extract the orderId.
String extractOrderId(String orderId) {
final orderIdSplit = orderId.split('..');
if (orderIdSplit.isNotEmpty) {
orderId = orderIdSplit[0];
}
return orderId;
}
Ihre Google Play-Käufe sollten jetzt bestätigt und in der Datenbank gespeichert sein.
Fahren Sie als Nächstes mit App Store-Käufen für iOS fort.
iOS-Käufe bestätigen: Kauf-Handler implementieren
Für die Überprüfung von Käufen im App Store gibt es ein Drittanbieter-Dart-Paket namens app_store_server_sdk
, das den Vorgang vereinfacht.
Erstellen Sie zuerst die ITunesApi
-Instanz. Verwenden Sie die Sandbox-Konfiguration und aktivieren Sie die Protokollierung, um die Fehlerbehebung zu erleichtern.
lib/app_store_purchase_handler.dart
final _iTunesAPI = ITunesApi(
ITunesHttpClient(
ITunesEnvironment.sandbox(),
loggingEnabled: true,
),
);
Im Gegensatz zu den Google Play APIs verwendet der App Store dieselben API-Endpunkte sowohl für Abos als auch für nicht abonnierte Inhalte. Sie können also dieselbe Logik für beide Handler verwenden. Fügen Sie sie zusammen, damit 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 jetzt 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) {
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;
}
}
Deine App Store-Käufe sollten jetzt bestätigt und in der Datenbank gespeichert sein.
Backend ausführen
Jetzt können Sie dart bin/server.dart
ausführen, um den /verifypurchase
-Endpunkt bereitzustellen.
$ dart bin/server.dart
Serving at http://0.0.0.0:8080
11. Käufe im Blick behalten
Wir empfehlen, die Käufe Ihrer Nutzer über den Back-End-Dienst zu erfassen. Das liegt daran, dass Ihr Backend auf Ereignisse aus dem Store reagieren kann und daher weniger anfällig für veraltete Informationen aufgrund von Caching ist und auch weniger anfällig für Manipulationen ist.
Richten Sie zuerst die Verarbeitung von Ladengeschäftsereignissen im Backend mit dem von Ihnen erstellten Dart-Backend ein.
Geschäftsereignisse im Backend 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 richten Sie dies sowohl für den Google Play Store als auch für den Apple App Store ein.
Google Play-Abrechnungsereignisse verarbeiten
Google Play stellt Abrechnungsereignisse über ein sogenanntes Cloud Pub/Sub-Thema bereit. Das sind im Grunde Nachrichtenwarteschlangen, in denen Nachrichten veröffentlicht und abgerufen werden können.
Da es sich hierbei um eine Google Play-spezifische Funktion handelt, nehmen Sie sie in die GooglePlayPurchaseHandler
auf.
Öffnen Sie zuerst lib/google_play_purchase_handler.dart
und fügen Sie den Import „PubsubApi“ hinzu:
lib/google_play_purchase_handler.dart
import 'package:googleapis/pubsub/v1.dart' as pubsub;
Übergeben Sie dann die PubsubApi
an die GooglePlayPurchaseHandler
und ändern Sie den Klassenkonstruktor so, dass eine 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();
});
}
Der Timer
ist so konfiguriert, dass er die _pullMessageFromSubSub
-Methode alle zehn Sekunden aufruft. Sie können die Dauer nach Belieben anpassen.
Erstellen Sie dann die _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 Code, den Sie gerade hinzugefügt haben, kommuniziert alle zehn Sekunden mit dem Pub/Sub-Thema von Google Cloud und fragt nach neuen Nachrichten. Anschließend wird jede Nachricht in der _processMessage
-Methode verarbeitet.
Bei dieser Methode werden die eingehenden Nachrichten decodiert und die aktualisierten Informationen zu jedem Kauf abgerufen, sowohl für Abos als auch für Käufe ohne Abo. Dabei wird bei Bedarf die vorhandene handleSubscription
oder handleNonSubscription
aufgerufen.
Jede Nachricht muss mit der _askMessage
-Methode bestätigt werden.
Fügen Sie als Nächstes der Datei server.dart
die erforderlichen Abhängigkeiten hinzu. Fügen Sie der Anmeldedatenkonfiguration 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);
Und schließlich übergeben Sie sie an den Konstruktor von GooglePlayPurchaseHandler
:
bin/server.dart
return {
'google_play': GooglePlayPurchaseHandler(
androidPublisher,
iapRepository,
pubsubApi, // new
),
'app_store': AppStorePurchaseHandler(
iapRepository,
),
};
Google Play einrichten
Sie haben den Code zum Verbrauchen von Abrechnungsereignissen aus dem Pub/Sub-Thema geschrieben, aber das Pub/Sub-Thema nicht erstellt und keine Abrechnungsereignisse veröffentlicht. Jetzt ist es an der Zeit, die Einrichtung vorzunehmen.
Erstellen Sie zuerst ein Pub/Sub-Thema:
- Rufen Sie in der Google Cloud Console die Seite Cloud Pub/Sub 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 für
GOOGLE_PLAY_PUBSUB_BILLING_TOPIC
inconstants.ts
festgelegt ist. In diesem Fall nennen wir ihnplay_billing
. Wenn Sie eine andere Option auswählen, aktualisieren Sieconstants.ts
. Erstellen Sie das Thema. - Klicken Sie in der Liste Ihrer Pub/Sub-Themen auf das Dreipunkt-Menü für das gerade erstellte Thema und dann auf Berechtigungen aufrufen.
- 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 Berechtigungsänderungen.
- Kopieren Sie den Themennamen des Themas, das Sie gerade erstellt haben.
- Öffnen Sie die Play Console noch einmal und wählen Sie Ihre App in der Liste Alle Apps aus.
- Scrollen Sie nach unten zu Monetarisieren > Einrichtung der Monetarisierung.
- Geben Sie den vollständigen Titel ein und speichern Sie Ihre Änderungen.
Alle Google Play-Abrechnungsereignisse werden jetzt im Thema veröffentlicht.
App Store-Abrechnungsereignisse verarbeiten
Wiederholen Sie diese Schritte für die App Store-Abrechnungsereignisse. Es gibt zwei effektive Möglichkeiten, die Verarbeitung von Kaufaktualisierungen für den App Store zu implementieren. Eine Möglichkeit besteht darin, einen Webhook zu implementieren, den Sie Apple zur Verfügung stellen und über den Apple mit Ihrem Server kommunizieren kann. Die zweite Möglichkeit, die in diesem Codelab beschrieben wird, besteht darin, eine Verbindung zur App Store Server API herzustellen und die Aboinformationen manuell abzurufen.
In diesem Codelab liegt der Schwerpunkt auf der zweiten Lösung, da Sie Ihren Server für das Internet freigeben müssten, um den Webhook zu implementieren.
In einer Produktionsumgebung sollten Sie idealerweise beides haben. Der Webhook zum Abrufen von Ereignissen aus dem App Store und die Server API, falls ein Ereignis verpasst wurde oder der Abostatus überprüft werden muss.
Öffnen Sie zuerst lib/app_store_purchase_handler.dart
und fügen Sie die Abhängigkeit „AppStoreServerAPI“ hinzu:
lib/app_store_purchase_handler.dart
final AppStoreServerAPI appStoreServerAPI;
AppStorePurchaseHandler(
this.iapRepository,
this.appStoreServerAPI, // new
)
Ändern Sie den Konstruktor, um einen Timer hinzuzufügen, der die Methode _pullStatus
aufruft. Dieser Timer ruft die Methode _pullStatus
alle 10 Sekunden 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();
});
}
Erstelle dann die Methode _pullStatus so:
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,
));
}
}
}
}
So funktioniert diese Methode:
- Ruft die Liste der aktiven Abos mithilfe des IapRepository aus Firestore ab.
- Für jede Bestellung wird der Abostatus von der App Store Server API angefordert.
- Die letzte Transaktion für diesen Abokauf wird abgerufen.
- Prüft das Ablaufdatum.
- Aktualisiert den Abostatus in Firestore. Wenn das Abo abgelaufen ist, wird es entsprechend gekennzeichnet.
Fügen Sie abschließend den erforderlichen Code hinzu, um den Zugriff auf die App Store Server API zu konfigurieren:
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-Einrichtung
Richten Sie als Nächstes den App Store ein:
- Melden Sie sich in App Store Connect an und wählen Sie Nutzer und Zugriff aus.
- Gehen Sie zu Schlüsseltyp > In-App-Kauf.
- Tippe auf das Pluszeichen, um einen neuen hinzuzufügen.
- Geben Sie einen Namen für den Schlüssel ein, z.B. „Codelab-Schlüssel“.
- Laden Sie die P8-Datei mit dem Schlüssel herunter.
- Kopieren Sie sie unter dem Namen
SubscriptionKey.p8
in den Ordner „Assets“. - Kopieren Sie die Schlüssel-ID aus dem neu erstellten Schlüssel und legen Sie sie in der Datei
lib/constants.dart
als KonstanteappStoreKeyId
fest. - Kopieren Sie die Aussteller-ID ganz oben in der Schlüsselliste und legen Sie sie in der Datei
lib/constants.dart
als KonstanteappStoreIssuerId
fest.
Käufe auf dem Gerät verfolgen
Die sicherste Möglichkeit, Käufe zu erfassen, ist die serverseitige, da der Client schwer zu schützen ist. Sie müssen jedoch eine Möglichkeit haben, die Informationen an den Client zurückzugeben, damit die App auf die Informationen zum Abostatus reagieren kann. Wenn Sie die Käufe in Firestore speichern, können Sie die Daten ganz einfach mit dem Client synchronisieren und automatisch auf dem neuesten Stand halten.
Sie haben bereits das IAPRepo in die App aufgenommen. Das ist das Firestore-Repository, das alle Kaufdaten des Nutzers in List<PastPurchase> purchases
enthält. Das Repository enthält auch hasActiveSubscription,
, was „wahr“ bedeutet, wenn es einen Kauf mit productId storeKeySubscription
mit einem nicht abgelaufenen Status gibt. 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
. Dort sollten Abos angewendet oder entfernt werden. Fügen Sie also iapRepo
als Property in die Klasse ein und weisen Sie iapRepo
im Konstruktor zu. Fügen Sie als Nächstes direkt im Konstruktor einen Listener hinzu und entfernen Sie den Listener in der Methode dispose()
. Zuerst kann der Listener nur eine leere Funktion sein. Da IAPRepo
eine ChangeNotifier
ist und notifyListeners()
jedes Mal aufgerufen wird, 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() {
_subscription.cancel();
iapRepo.removeListener(purchasesUpdate);
super.dispose();
}
void purchasesUpdate() {
//TODO manage updates
}
Geben Sie als Nächstes die IAPRepo
in den Konstruktor in main.dart.
ein. 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,
wird mit den Methoden applyPaidMultiplier
und removePaidMultiplier
der Multiplikator auf 10 bzw. 1 festgelegt, sodass Sie nicht prüfen müssen, ob das Abo bereits angewendet wurde. Wenn sich der Abostatus ändert, aktualisierst du auch den Status des kaufbaren Produkts, damit auf der Kaufseite angezeigt wird, dass es bereits aktiv ist. Legen Sie die Property _beautifiedDashUpgrade
fest, je nachdem, ob das Upgrade gekauft wurde.
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 jetzt dafür gesorgt, dass der Abo- und Upgrade-Status im Backend-Dienst immer auf dem neuesten Stand ist und mit der App synchronisiert wird. Die App reagiert entsprechend und wendet die Abo- und Upgrade-Funktionen auf Ihr Dash-Klickspiel an.
12. Fertig!
Herzlichen Glückwunsch!!! Sie haben das Codelab abgeschlossen. Den vollständigen Code für dieses Codelab finden Sie im Ordner complete.
Weitere Informationen finden Sie in den anderen Flutter-Codelabs.