1. Giriş
Son Güncelleme: 11.07.2023
Flutter uygulamasına uygulama içi satın alma özelliği eklemek için Uygulama ve Play mağazalarının doğru şekilde ayarlanması, satın alma işleminin doğrulanması ve abonelik ayrıcalıkları gibi gerekli izinlerin verilmesi gerekir.
Bu codelab'de, bir uygulamaya (sizin için sağlanır) üç tür uygulama içi satın alma işlemi ekleyecek ve bu satın alma işlemlerini Firebase için Dart arka ucu kullanarak doğrulayacaksınız. Sağlanan uygulama olan Dash Clicker, para birimi olarak Dash maskotunun kullanıldığı bir oyun içeriyor. Aşağıdaki satın alma seçeneklerini eklersiniz:
- Tek seferde 2.000 kısa çizgi karşılığında tekrarlanabilir satın alma seçeneği.
- Eski tarz Dash'i modern tarzda bir Dash'e dönüştürmek için tek seferlik yükseltme satın alma işlemi.
- Otomatik olarak oluşturulan tıklamaları ikiye katlayan bir abonelik.
İlk satın alma seçeneği, kullanıcıya doğrudan 2.000 kısa çizgi sunar. Bunlar doğrudan kullanıcı tarafından kullanılabilir ve birçok kez satın alınabilir. Doğrudan tüketildiği ve birden fazla kez tüketildiği için buna tüketim öğesi denir.
İkinci seçenek, Kısa çizgiyi daha güzel bir Kısa çizgiye yükseltir. Yalnızca bir kez satın alınması gerekir ve süresiz olarak kullanılabilir. Bu tür bir satın alma işlemi, uygulama tarafından kullanılamayan ancak sonsuza kadar geçerli olduğu için tüketilemez olarak adlandırılır.
Üçüncü ve son satın alma seçeneği ise aboneliktir. Abonelik etkin durumdayken kullanıcı kısa çizgileri daha hızlı alır, ancak abonelik için ödeme yapmayı bıraktığında avantajlar da ortadan kalkar.
Sizin için de sağlanan arka uç hizmeti bir Dart uygulaması olarak çalışır, satın alma işlemlerinin yapıldığını doğrular ve bunları Firestore kullanarak depolar. Firestore, işlemi kolaylaştırmak için kullanılır ancak üretim uygulamanızda herhangi bir arka uç hizmetini kullanabilirsiniz.
Neler oluşturacaksınız?
- Uygulamayı, tüketilebilir satın alma işlemlerini ve abonelikleri destekleyecek şekilde genişleteceksiniz.
- Satın alınan öğeleri doğrulamak ve depolamak için bir Dart arka uç uygulaması da genişleteceksiniz.
Öğrenecekleriniz
- App Store ve Play Store'u satın alınabilir ürünlerle yapılandırma.
- Satın alma işlemlerini doğrulamak ve bunları Firestore'da depolamak için mağazalarla nasıl iletişim kuracağınız.
- Uygulamanızda satın alma işlemlerini yönetme.
Gerekenler
- Android Studio 4.1 veya sonraki sürümler
- Xcode 12 veya üzeri (iOS geliştirme için)
- Flutter SDK'sı
2. Geliştirme ortamını ayarlama
Bu codelab'i başlatmak için kodu indirip iOS için paket tanımlayıcısını ve Android için paket adını değiştirin.
Kodu indirme
GitHub deposunu komut satırından klonlamak için şu komutu kullanın:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Alternatif olarak, GitHub'ın cli aracı yüklüyse şu komutu kullanın:
gh repo clone flutter/codelabs flutter-codelabs
Örnek kod, codelab'ler koleksiyonunun kodunu içeren flutter-codelabs
dizinine klonlanır. Bu codelab'in kodu flutter-codelabs/in_app_purchases
dilindedir.
flutter-codelabs/in_app_purchases
altındaki dizin yapısı, adlandırılmış her adımın sonunda olmanız gereken bilgilerin bir anlık görüntüsünü içerir. Başlangıç kodu 0. adımdadır, bu nedenle eşleşen dosyaları bulmak çok kolay:
cd flutter-codelabs/in_app_purchases/step_00
İleri gitmek veya bir adımdan sonra bir öğenin nasıl görünmesi gerektiğini görmek isterseniz, ilgilendiğiniz adımın adını taşıyan dizine bakın. Son adımın kodu complete
klasörünün altında.
Başlangıç projesini oluşturma
En sevdiğiniz IDE'de step_00
cihazındaki başlangıç projesini açın. Ekran görüntüleri için Android Studio'yu kullandık. Ancak Visual Studio Code da mükemmel bir seçenek. Her iki düzenleyicide de en son Dart ve Flutter eklentilerinin yüklendiğinden emin olun.
Üreteceğiniz uygulamaların, hangi ürünlerin hangi fiyattan mevcut olduğunu öğrenmek için App Store ve Play Store ile iletişim kurması gerekir. Her uygulama benzersiz bir kimlikle tanımlanır. iOS App Store'da bu, paket kimliği, Android Play Store'da ise uygulama kimliğidir. Bu tanımlayıcılar genellikle ters alan adı gösterimi kullanılarak yapılır. Örneğin, flutter.dev için bir uygulama içi satın alma uygulaması yaparken dev.flutter.inapppurchase
kullanırız. Uygulamanız için bir tanımlayıcı düşünün. Şimdi bu tanımlayıcıyı proje ayarlarında belirleyeceksiniz.
İlk olarak iOS için paket tanımlayıcısını ayarlayın.
Proje Android Studio'da açıkken iOS klasörünü sağ tıklayın, Flutter'ı tıklayın ve Xcode uygulamasında modülü açın.
Xcode'un klasör yapısında Runner projesi en üstte, Flutter, Runner ve Products hedefleri ise Runner projesinin altındadır. Proje ayarlarınızı düzenlemek için Çalıştırıcı'yı çift tıklayın ve ardından İmzalama ve Özellikler. Ekibinizi ayarlamak için Ekip alanına az önce seçtiğiniz paket kimliğini girin.
Artık Xcode'u kapatabilir ve Android yapılandırmasını tamamlamak üzere Android Studio'ya geri dönebilirsiniz. Bunu yapmak için android/app,
altındaki build.gradle
dosyasını açın ve applicationId
öğenizi (aşağıdaki ekran görüntüsünde 37. satırda bulunan) iOS paket tanımlayıcısıyla aynı olan uygulama kimliğiyle değiştirin. iOS ve Android mağazalarının kimliklerinin aynı olması gerekmediğini unutmayın. Ancak kimlikleri aynı tutmak hataya daha az açık olduğundan bu codelab'de aynı tanımlayıcıları da kullanacağız.
3. Eklentiyi yükleme
Codelab'in bu bölümünde in_app_purchase eklentisini yükleyeceksiniz.
pubspec'e bağımlılık ekleme
pubspec'inizdeki bağımlılıklara in_app_purchase
ekleyerek pubspec'e in_app_purchase
ekleyin:
$ 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
..
Paketi indirmek için pub get'i tıklayın veya komut satırında flutter pub get
komutunu çalıştırın.
4. App Store'u kurma
Uygulama içi satın alma işlemlerini ayarlamak ve iOS'te test etmek için App Store'da yeni bir uygulama oluşturmanız ve orada satın alınabilir ürünler oluşturmanız gerekir. Herhangi bir içerik yayınlamanız veya uygulamayı incelenmesi için Apple'a göndermeniz gerekmez. Bu işlem için geliştirici hesabınızın olması gerekir. Hesabınız yoksa Apple geliştirici programına kaydolun.
Ücretli Uygulama Sözleşmeleri
Uygulama içi satın alma işlemlerini kullanabilmeniz için App Store Connect'te ücretli uygulamalarla ilgili etkin bir sözleşmeniz de olması gerekir. https://appstoreconnect.apple.com/ adresine gidip Sözleşmeler, Vergi ve Bankacılık'ı tıklayın.
Ücretsiz ve ücretli uygulamalar için sözleşmeler burada gösterilir. Ücretsiz uygulamaların durumu etkin olmalı, ücretli uygulamaların durumu ise yeni olmalıdır. Şartları görüntülediğinizden, kabul ettiğinizden ve gerekli tüm bilgileri girdiğinizden emin olun.
Her şey doğru şekilde ayarlandığında ücretli uygulamalar için durum etkinleştirilir. Etkin bir sözleşme olmadan uygulama içi satın alma işlemlerini deneyemeyeceğiniz için bu çok önemlidir.
Uygulama kimliğini kaydet
Apple geliştirici portalında yeni bir tanımlayıcı oluşturun.
Uygulama kimliklerini seçin
Uygulama Seçin
Bir açıklama girin ve paket kimliğini, paket kimliği ile XCode'da daha önce ayarlanan değerle eşleşecek şekilde ayarlayın.
Yeni bir uygulama kimliğinin nasıl oluşturulacağı hakkında daha fazla bilgi için Geliştirici Hesabı Yardımı'na bakın .
Yeni uygulama oluşturma
Benzersiz paket tanımlayıcınızı kullanarak App Store Connect'te yeni bir uygulama oluşturun.
Yeni bir uygulama oluşturma ve sözleşmeleri yönetme hakkında daha fazla yardım için App Store Connect yardımına bakın.
Uygulama içi satın alma işlemlerini test etmek için bir korumalı alan test kullanıcısı gerekir. Bu test kullanıcısı iTunes'a bağlı olmamalıdır. Yalnızca uygulama içi satın alma işlemlerini test etmek için kullanılır. Halihazırda bir Apple hesabı için kullanılan bir e-posta adresini kullanamazsınız. Kullanıcılar ve Erişim bölümünde, yeni bir korumalı alan hesabı oluşturmak veya mevcut korumalı alan Apple kimliklerini yönetmek için Korumalı alan altındaki Test kullanıcıları'na gidin.
Artık iPhone'unuzda Ayarlar > Uygulama Mağazası > Sandbox-account.
Uygulama içi satın alma işlemlerinizi yapılandırma
Şimdi, satın alınabilir üç öğeyi yapılandıracaksınız:
dash_consumable_2k
: Kullanıcılara satın alma işlemi başına 2.000 kısa çizgi (uygulama içi para birimi) veren, birden fazla kez satın alınabilen bir tüketim ürünü satın alma işlemidir.dash_upgrade_3d
: Kullanılamayan bir "yükseltme" kullanıcıya kozmetik olarak farklı bir Dash'i tıklamasını sağlayan bir satın alma işlemi sunar.dash_subscription_doubler
: Abonelik süresince kullanıcıya tıklama başına iki kat daha fazla Kısa çizgi veren bir abonelik.
Uygulama İçi Satın Alma İşlemleri > Yönet'e dokunun.
Belirtilen kimliklerle uygulama içi satın alma işlemlerinizi oluşturun:
dash_consumable_2k
uygulamasını Tüketilebilir olarak ayarlayın.
Ürün Kimliği olarak dash_consumable_2k
kullanın. Referans adı yalnızca App Store Connect'te kullanılır. Bu adı dash consumable 2k
olarak ayarlayıp satın alma işlemi için yerelleştirmelerinizi eklemeniz yeterlidir. Açıklama olarak 2000 dashes fly out
ile birlikte Spring is in the air
satın alma işlemini çağırın.
dash_upgrade_3d
öğesini Tüketilebilir olmayan öğe olarak ayarlayın.
Ürün Kimliği olarak dash_upgrade_3d
kullanın. Referans adını dash upgrade 3d
olarak ayarlayın ve satın alma işlemi için yerelleştirmelerinizi ekleyin. Açıklama olarak Brings your dash back to the future
ile birlikte 3D Dash
satın alma işlemini çağırın.
dash_subscription_doubler
hizmetini Otomatik yenilenen abonelik olarak ayarlayın.
Aboneliklerin akışı biraz farklıdır. Önce Referans Adını ve Ürün Kimliğini ayarlamanız gerekir:
Sonra, bir abonelik grubu oluşturmanız gerekir. Aynı grubun parçası olan birden çok abonelik olduğunda, bir kullanıcı bu aboneliklerden yalnızca birine aynı anda abone olabilir, ancak bu abonelikler arasında kolayca üst veya alt sürüme geçebilir. Bu grubu subscriptions
olarak adlandırmanız yeterli.
Ardından abonelik süresini ve yerelleştirmeleri girin. Bu aboneliğe Jet Engine
adını Doubles your clicks
açıklamasıyla ekleyin. Save'i (Kaydet) tıklayın.
Kaydet düğmesini tıkladıktan sonra bir abonelik fiyatı ekleyin. İstediğiniz fiyatı seçin.
Artık satın alma işlemleri listesinde bu üç satın alma işlemini görmeniz gerekir:
5. Play Store'u kurma
App Store'da olduğu gibi, Play Store için de bir geliştirici hesabınızın olması gerekir. Henüz bir hesabınız yoksa hesap oluşturun.
Yeni uygulama oluşturma
Google Play Console'da yeni bir uygulama oluşturun:
- Play Console'u açın.
- Tüm uygulamalar > Uygulama oluşturun.
- Varsayılan dili seçin ve uygulamanız için bir başlık ekleyin. Uygulamanızın adını Google Play'de görünmesini istediğiniz şekilde yazın. Adı daha sonra değiştirebilirsiniz.
- Uygulamanızın bir oyun olduğunu belirtin. Bunu daha sonra değiştirebilirsiniz.
- Uygulamanızın ücretsiz mi yoksa ücretli mi olduğunu belirtin.
- Play Store kullanıcılarının bu uygulamayla ilgili olarak sizinle iletişim kurmak için kullanabileceği bir e-posta adresi ekleyin.
- İçerik kuralları ve ABD ihracat yasaları beyanlarını doldurun.
- Uygulama oluştur'u seçin.
Uygulamanız oluşturulduktan sonra kontrol paneline gidin ve Uygulamanızı ayarlama bölümündeki tüm görevleri tamamlayın. Bu bölümde uygulamanızla ilgili içerik derecelendirmeleri ve ekran görüntüleri gibi bazı bilgiler sağlarsınız. .
Başvuruyu imzalayın
Uygulama içi satın alma işlemlerini test edebilmek için Google Play'e en az bir derleme yüklemiş olmanız gerekir.
Bunun için sürüm derlemenizin hata ayıklama anahtarları dışında bir şeyle imzalanması gerekir.
Anahtar deposu oluşturma
Mevcut bir anahtar deponuz varsa sonraki adıma geçin. Bu kod yoksa komut satırında aşağıdaki komutu çalıştırarak oluşturun.
Mac/Linux'ta, aşağıdaki komutu kullanın:
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
Windows'da aşağıdaki komutu kullanın:
keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias key
Bu komut, key.jks
dosyasını ana dizininizde depolar. Dosyayı başka bir yerde depolamak istiyorsanız -keystore
parametresine ilettiğiniz bağımsız değişkeni değiştirin. Acele etmeyin
keystore
file private; lütfen herkese açık kaynak kontrolüne girmeyin.
Uygulamadaki anahtar deposuna başvuruda bulunma
Anahtar deponuza referans içeren <your app dir>/android/key.properties
adlı bir dosya oluşturun:
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'da oturum açmayı yapılandırma
<your app dir>/android/app/build.gradle
dosyasını düzenleyerek uygulamanız için imzalamayı yapılandırın.
Özellikler dosyanızdaki anahtar deposu bilgilerini android
bloğundan önce ekleyin:
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
// omitted
}
key.properties
dosyasını keystoreProperties
nesnesine yükleyin.
Aşağıdaki kodu buildTypes
bloğunun önüne ekleyin:
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
}
}
Modülünüzün build.gradle
dosyasındaki signingConfigs
bloğunu imzalama yapılandırma bilgileriyle yapılandırın:
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
Uygulamanızın sürüm derlemeleri artık otomatik olarak imzalanacak.
Uygulamanızı imzalama hakkında daha fazla bilgi için developer.android.com adresinde Uygulamanızı imzalama konusuna bakın.
İlk derlemenizi yükleyin
Uygulamanız imzalama için yapılandırıldıktan sonra aşağıdaki komutu çalıştırarak uygulamanızı geliştirebilmeniz gerekir:
flutter build appbundle
Bu komut, varsayılan olarak bir sürüm derlemesi oluşturur. Çıkış, <your app dir>/build/app/outputs/bundle/release/
adresinde bulunabilir
Google Play Console'daki kontrol panelinden Sürüm > Test ediliyor > Kapalı test ve yeni, kapalı test sürümü oluşturun.
Bu codelab'de yalnızca Google'ın uygulamayı imzalamasına devam edeceksiniz. Etkinleştirmek için Play Uygulama İmzalama'nın altındaki Devam düğmesine basın.
Ardından, derleme komutu tarafından oluşturulan app-release.aab
uygulama paketini yükleyin.
Kaydet'i, ardından Sürümü incele'yi tıklayın.
Son olarak, dahili test sürümünü etkinleştirmek için Dahili test kanalına sunumu başlat'ı tıklayın.
Test kullanıcılarını ayarlama
Uygulama içi satın alma işlemlerini test edebilmek için test kullanıcılarınızın Google Hesapları, Google Play Console'a iki konuma eklenmelidir:
- Belirli test kanalına (Dahili test)
- Lisans test kullanıcısı olarak
İlk olarak, test kullanıcısını dahili test kanalına ekleyerek başlayın. Sürüm > Test ediliyor > Dahili test'i tıklayın ve Test kullanıcıları sekmesini tıklayın.
E-posta listesi oluştur'u tıklayarak yeni bir e-posta listesi oluşturun. Listeye bir ad verin ve uygulama içi satın almaları test etme erişimi gereken Google hesaplarının e-posta adreslerini ekleyin.
Ardından, listenin onay kutusunu işaretleyin ve Değişiklikleri kaydet'i tıklayın.
Ardından lisans test kullanıcılarını ekleyin:
- Google Play Console'un Tüm uygulamalar görünümüne geri dönün.
- Ayarlar > Lisans testi.
- Uygulama içi satın alma işlemlerini test edebilmeleri gereken test kullanıcılarıyla aynı e-posta adreslerini ekleyin.
- Lisans yanıtı'nı
RESPOND_NORMALLY
olarak ayarlayın. - Değişiklikleri Kaydet'i tıklayın.
Uygulama içi satın alma işlemlerinizi yapılandırma
Şimdi, uygulama içinde satın alınabilecek öğeleri yapılandıracaksınız.
Tıpkı App Store'da olduğu gibi, üç farklı satın alma işlemi tanımlamanız gerekir:
dash_consumable_2k
: Kullanıcılara satın alma işlemi başına 2.000 kısa çizgi (uygulama içi para birimi) veren, birden fazla kez satın alınabilen bir tüketim ürünü satın alma işlemidir.dash_upgrade_3d
: Kullanılamayan bir "yükseltme" Kullanıcıya kozmetik olarak farklı bir Dash'i tıklama fırsatı veren, yalnızca bir kez satın alınabilen satın alma işlemi.dash_subscription_doubler
: Abonelik süresince kullanıcıya tıklama başına iki kat daha fazla Kısa çizgi veren bir abonelik.
Öncelikle tüketilebilir olan ve olmayan öğeleri ekleyin.
- Google Play Console'a gidip uygulamanızı seçin.
- Para kazanın > Ürünler > Uygulama içi ürünler.
- Ürün oluştur'u tıklayın.
- Ürününüz için gerekli tüm bilgileri girin. Ürün kimliğinin, kullanmak istediğiniz kimlikle tam olarak eşleştiğinden emin olun.
- Kaydet'i tıklayın.
- Etkinleştir'i tıklayın.
- Kullanılamayan "yükseltme" işlemini, değeri için teklif verirsiniz.
Sonra, aboneliği ekleyin:
- Google Play Console'a gidip uygulamanızı seçin.
- Para kazanın > Ürünler > Abonelikler.
- Abonelik oluştur'u tıklayın.
- Aboneliğiniz için gerekli tüm bilgileri girin. Ürün kimliğinin, kullanmak istediğiniz kimlikle tam olarak eşleştiğinden emin olun.
- Kaydet'i tıklayın
Satın alma işlemlerinizin artık Play Console'da ayarlanmış olması gerekir.
6. Firebase'i ayarlayın
Bu codelab'de kullanıcıların geri bildirimlerini doğrulamak ve izlemek için bir arka uç hizmeti kullanacaksınız. değeri için teklif verirsiniz.
Bir arka uç hizmeti kullanmanın çeşitli avantajları vardır:
- İşlemleri güvenli bir şekilde doğrulayabilirsiniz.
- Uygulama mağazalarından faturalandırma etkinliklerine tepki verebilirsiniz.
- Satın alma işlemlerini bir veritabanında takip edebilirsiniz.
- Kullanıcılar sistem saatlerini geri sararak uygulamanızın premium özellikler sunmasını sağlayamazlar.
Arka uç hizmeti oluşturmanın birçok yolu olsa da bunu Cloud Functions ve Firestore'u ve Google'ın kendi Firebase'ini kullanarak yapabilirsiniz.
Arka ucu yazmak bu codelab'de kapsam dışındadır. Bu nedenle başlangıç kodu, başlamanıza yardımcı olacak temel satın alma işlemlerini gerçekleştiren bir Firebase projesi içerir.
Firebase eklentileri başlangıç uygulamasına da dahildir.
Şimdi yapmanız gereken kendi Firebase projenizi oluşturmak, Firebase için hem uygulamayı hem de arka ucu yapılandırmak ve son olarak arka ucu dağıtmaktır.
Firebase projesi oluşturma
Firebase konsoluna gidin ve yeni bir Firebase projesi oluşturun. Bu örnek için Dash Clicker projesini çağırın.
Arka uç uygulamasında satın alma işlemlerini belirli bir kullanıcıya bağlarsınız, bu nedenle kimlik doğrulama gerekir. Bunun için Google ile Oturum Açma ile Firebase'in kimlik doğrulama modülünden yararlanın.
- Firebase kontrol panelinde Kimlik Doğrulama'ya gidin ve gerekirse etkinleştirin.
- Oturum açma yöntemi sekmesine gidin ve Google oturum açma sağlayıcısını etkinleştirin.
Firebase'in Firestore veritabanını da kullanacağınız için bunu da etkinleştirin.
Aşağıdaki gibi Cloud Firestore kuralları ayarlayın:
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
}
}
}
Flutter için Firebase'i kurma
Flutter uygulamasına Firebase'i yüklemek için önerilen yöntem FlutterFire CLI'yı kullanmaktır. Kurulum sayfasında açıklanan talimatları uygulayın.
Flutterfire yapılandırmasını çalıştırırken, bir önceki adımda oluşturduğunuz projeyi seçin.
$ 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>
Daha sonra, iki platformu seçerek iOS ve Android'i etkinleştirin.
? Which platforms should your configuration support (use arrow keys & space to select)? ›
✔ android
✔ ios
macos
web
firebase_options.dart'ı geçersiz kılmayla ilgili sorulduğunda Evet'i seçin.
? Generated FirebaseOptions file lib/firebase_options.dart already exists, do you want to override it? (y/n) › yes
Android için Firebase kurulumu: Diğer adımlar
Firebase kontrol panelinden Projeye Genel Bakış'a gidin ve Ayarlar'ı, ardından Genel sekmesini seçin.
Uygulamalarınız bölümüne gidin ve dashclicker (android) uygulamasını seçin.
Hata ayıklama modunda Google ile oturum açmaya izin vermek için hata ayıklama sertifikanızın SHA-1 karma parmak izini sağlamanız gerekir.
Hata ayıklama imza sertifikanızın karmasını alın
Flutter uygulama projenizin kök dizininde, dizini android/
klasörü olarak değiştirin ve ardından bir imzalama raporu oluşturun.
cd android ./gradlew :app:signingReport
İmzalama anahtarlarından oluşan kapsamlı bir liste gösterilir. Hata ayıklama sertifikasının karmasını aradığınız için Variant
ve Config
özellikleri debug
olarak ayarlanmış sertifikayı arayın. Anahtar deposu büyük olasılıkla .android/debug.keystore
altındaki ana klasörünüzdedir.
> Task :app:signingReport
Variant: debug
Config: debug
Store: /<USER_HOME_FOLDER>/.android/debug.keystore
Alias: AndroidDebugKey
MD5: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
SHA1: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
SHA-256: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
Valid until: Tuesday, January 19, 2038
SHA-1 karmasını kopyalayın ve uygulama gönderim kalıcı iletişim kutusundaki son alanı doldurun.
iOS için Firebase'i kurma: Diğer adımlar
ios/Runnder.xcworkspace
uygulamasını Xcode
ile açın. Dilerseniz istediğiniz IDE ile.
VSCode'da ios/
klasörünü sağ tıklayın, ardından open in xcode
öğesini tıklayın.
Android Studio'da ios/
klasörünü sağ tıklayın, ardından flutter
seçeneğini ve ardından open iOS module in Xcode
seçeneğini tıklayın.
iOS'te Google ile oturum açılmasına izin vermek için derleme plist
dosyalarınıza CFBundleURLTypes
yapılandırma seçeneğini ekleyin. (Daha fazla bilgi için google_sign_in
paketi dokümanlarına göz atın.) Bu durumda, dosyalar ios/Runner/Info-Debug.plist
ve ios/Runner/Info-Release.plist
olur.
Anahtar/değer çifti zaten eklenmiş ancak değerlerinin değiştirilmesi gerekiyor:
- Etrafında
<string>..</string>
öğesi olmadan,GoogleService-Info.plist
dosyasındanREVERSED_CLIENT_ID
değerini alın. ios/Runner/Info-Debug.plist
veios/Runner/Info-Release.plist
dosyalarınızdaCFBundleURLTypes
anahtarının altında bulunan değeri değiştirin.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<!-- TODO Replace this value: -->
<!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
<string>com.googleusercontent.apps.REDACTED</string>
</array>
</dict>
</array>
Firebase kurulumunu tamamladınız.
7. Satın alma işlemleriyle ilgili güncellemeleri dinleme
Codelab'in bu bölümünde ürünleri satın almak için uygulamayı hazırlayacaksınız. Uygulama başlatıldıktan sonra satın alma güncellemelerini ve hataları dinlemek de bu sürece dahildir.
Satın alma işlemleriyle ilgili güncellemeleri dinleme
main.dart,
içinde, iki sayfa içeren BottomNavigationBar
içeren Scaffold
widget'ını bulun.MyHomePage
Bu sayfa ayrıca DashCounter
, DashUpgrades,
ve DashPurchases
için üç Provider
oluşturur. DashCounter
, mevcut kısa çizgi sayısını izler ve otomatik olarak artırır. DashUpgrades
, Dashes ile satın alabileceğiniz yükseltmeleri yönetir. Bu codelab, DashPurchases
konusuna odaklanmaktadır.
Varsayılan olarak, bir sağlayıcıya ait nesne ilk kez istendiğinde tanımlanır. Bu nesne, uygulama başlatıldığında doğrudan satın alma güncellemelerini dinler. Bu nedenle, lazy: false
ile bu nesnede geç yüklemeyi devre dışı bırakın:
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
),
lazy: false,
),
Ayrıca InAppPurchaseConnection
öğesinin bir örneğine de ihtiyacınız var. Ancak uygulamanın test edilebilir olmasını sağlamak için bağlantıyla alay edecek bir yönteme ihtiyacınız vardır. Bunu yapmak için testte geçersiz kılınabilecek bir örnek yöntemi oluşturun ve bunu main.dart
öğesine ekleyin.
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!;
}
}
Testin çalışmaya devam etmesini istiyorsanız testi biraz güncellemeniz gerekir. TestIAPConnection
ile ilgili kodun tamamını görmek için GitHub'da widget_test.dart etiketine bakın.
test/widget_test.dart
void main() {
testWidgets('App starts', (WidgetTester tester) async {
IAPConnection.instance = TestIAPConnection();
await tester.pumpWidget(const MyApp());
expect(find.text('Tim Sneath'), findsOneWidget);
});
}
lib/logic/dash_purchases.dart
içinde DashPurchases ChangeNotifier
koduna gidin. Şu anda satın aldığınız kısa çizgilere ekleyebileceğiniz yalnızca bir DashCounter
var.
Bir akış abonelik mülkü, _subscription
(StreamSubscription<List<PurchaseDetails>> _subscription;
türünde), IAPConnection.instance,
ve içe aktarma işlemleri ekleyin. Bu işlem sonucunda elde edilen kod aşağıdaki gibi görünmelidir:
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);
}
_subscription
oluşturucuda başlatıldığı için late
anahtar kelimesi _subscription
kampanyasına eklendi. Bu proje varsayılan olarak null olamaz (NNBD) şekilde ayarlanmıştır. Bu, boş değer olarak bildirilmeyen özelliklerin null olmayan bir değere sahip olması gerektiği anlamına gelir. late
niteleyicisi, bu değeri tanımlamayı ertelemenize olanak tanır.
Oluşturucuda purchaseUpdatedStream
öğesini alın ve akışı dinlemeye başlayın. dispose()
yönteminde yayın aboneliğini iptal edin.
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
}
}
Artık uygulama satın alma güncellemelerini aldığından bir sonraki bölümde satın alma işlemi yapacaksınız.
Devam etmeden önce her şeyin doğru şekilde ayarlandığını doğrulamak için "flutter test"
" ile test yapın.
$ flutter test
00:01 +1: All tests passed!
8. Satın alma işlemleri gerçekleştirme
Codelab'in bu bölümünde mevcut sahte ürünleri gerçek satın alınabilir ürünlerle değiştireceksiniz. Bu ürünler mağazalardan yüklenir, liste halinde gösterilir ve ürüne dokunulduğunda satın alınır.
PrchasableProduct
PurchasableProduct
, model bir ürün gösteriyor. purchasable_product.dart
öğesindeki PurchasableProduct
sınıfını aşağıdaki kodla değiştirerek gerçek içeriği gösterecek şekilde güncelleyin:
lib/model/purchasable_product.dart
import 'package:in_app_purchase/in_app_purchase.dart';
enum ProductStatus {
purchasable,
purchased,
pending,
}
class PurchasableProduct {
String get id => productDetails.id;
String get title => productDetails.title;
String get description => productDetails.description;
String get price => productDetails.price;
ProductStatus status;
ProductDetails productDetails;
PurchasableProduct(this.productDetails) : status = ProductStatus.purchasable;
}
dash_purchases.dart,
içinde, satın alınan model satın alımları kaldırın ve bunları boş bir listeyle (List<PurchasableProduct> products = [];
) değiştirin
Mevcut satın alma işlemlerini yükleme
Kullanıcıya satın alma olanağı sunmak için mağazadan satın alınan öğeleri yükleyin. İlk olarak mağazanın kullanılabilir olup olmadığını kontrol edin. Mağaza kullanılamadığında storeState
, notAvailable
olarak ayarlandığında kullanıcıya bir hata mesajı gösterilir.
lib/logic/dash_purchases.dart
Future<void> loadPurchases() async {
final available = await iapConnection.isAvailable();
if (!available) {
storeState = StoreState.notAvailable;
notifyListeners();
return;
}
}
Mağaza kullanılabilir olduğunda, satın alınan mevcut öğeleri yükleyin. Önceki Firebase kurulumu göz önüne alındığında storeKeyConsumable
, storeKeySubscription,
ve storeKeyUpgrade
değerleri görülebilir. Beklenen bir satın alma işlemi mevcut olmadığında, bu bilgileri konsola yazdırın; bu bilgiyi arka uç hizmetine göndermeniz faydalı olabilir.
await iapConnection.queryProductDetails(ids)
yöntemi, hem bulunan kimlikleri hem de bulunan satın alınabilir ürünleri döndürür. Kullanıcı arayüzünü güncellemek için yanıttaki productDetails
öğesini kullanın ve StoreState
değerini available
olarak ayarlayın.
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();
}
Oluşturucuda loadPurchases()
işlevini çağırın:
lib/logic/dash_purchases.dart
DashPurchases(this.counter) {
final purchaseUpdated = iapConnection.purchaseStream;
_subscription = purchaseUpdated.listen(
_onPurchaseUpdate,
onDone: _updateStreamOnDone,
onError: _updateStreamOnError,
);
loadPurchases();
}
Son olarak, storeState
alanının StoreState.available
olan değerini StoreState.loading:
olarak değiştirin
lib/logic/dash_purchases.dart
StoreState storeState = StoreState.loading;
Satın alınabilir ürünleri gösterme
purchase_page.dart
dosyasını kullanabilirsiniz. PurchasePage
widget'ı, StoreState
bağlı olarak _PurchasesLoading
, _PurchaseList,
veya _PurchasesNotAvailable,
gösterir. Widget ayrıca kullanıcının geçmiş satın alma işlemlerini de gösterir ve bunlar sonraki adımda kullanılır.
_PurchaseList
widget'ı, satın alınabilir ürünlerin listesini gösterir ve DashPurchases
nesnesine bir satın alma isteği gönderir.
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(),
);
}
}
Doğru şekilde yapılandırılmışlarsa, mevcut ürünleri Android ve iOS mağazalarında görebilirsiniz. İlgili konsollara girildiğinde, satın alınan öğelerin kullanılabilir hale gelmesinin biraz zaman alabileceğini unutmayın.
dash_purchases.dart
işlevine dönün ve ürün satın almak için işlevi uygulayın. Sadece sarf malzemelerini sarf malzemelerinden ayırmanız gerekir. Yükseltme ve abonelik ürünleri tüketilebilir değildir.
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');
}
}
Devam etmeden önce _beautifiedDashUpgrade
değişkenini oluşturun ve beautifiedDash
alıcısını bu değişkene referans verecek şekilde güncelleyin.
lib/logic/dash_purchases.dart
bool get beautifiedDash => _beautifiedDashUpgrade;
bool _beautifiedDashUpgrade = false;
_onPurchaseUpdate
yöntemi satın alma güncellemelerini alır, satın alma sayfasında gösterilen ürünün durumunu günceller ve satın alma işlemini sayaç mantığına uygular. Mağazanın satın alma işleminin doğru şekilde yapıldığını bilmesi için, satın alma işlemini gerçekleştirdikten sonra completePurchase
numaralı telefonu aramanız önemlidir.
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. Arka ucu ayarlama
Satın alma işlemlerini izlemeye ve doğrulamaya geçmeden önce, bunu destekleyecek bir Dart arka ucu oluşturun.
Bu bölümde, kök olarak dart-backend/
klasörü üzerinde çalışın.
Aşağıdaki araçların yüklü olduğundan emin olun:
- Dart
- Firebase KSA
Temel projeye genel bakış
Bu projenin bazı bölümleri, codelab için kapsam dışında kabul edildiğinden başlangıç koduna dahil edilir. Başlamadan önce öğeleri nasıl yapılandıracağınıza dair bir fikir edinmek için başlangıç kodunda zaten bulunan şeylerin üzerinden geçmek iyi bir fikirdir.
Bu arka uç kodu makinenizde yerel olarak çalışabilir. Bu kodu kullanmak için dağıtmanız gerekmez. Ancak, geliştirme cihazınızdan (Android veya iPhone) sunucunun çalışacağı makineye bağlanabilmeniz gerekir. Bunun için bunların aynı ağda olmaları ve makinenizin IP adresini bilmeniz gerekir.
Aşağıdaki komutu kullanarak sunucuyu çalıştırmayı deneyin:
$ dart ./bin/server.dart
Serving at http://0.0.0.0:8080
Dart arka ucu, API uç noktalarını sunmak için shelf
ve shelf_router
kullanır. Varsayılan olarak sunucu herhangi bir rota sağlamaz. Daha sonra, satın alma işlemlerini doğrulama işlemini gerçekleştirmek için bir rota oluşturacaksınız.
lib/iap_repository.dart
dilindeki IapRepository
, başlangıç kodunda halihazırda bulunan bir parçadır. Firestore veya genel olarak veritabanları ile nasıl etkileşimde bulunulacağını öğrenmenin bu codelab ile alakalı olmadığı düşünüldüğünden, başlangıç kodu, Firestore'da satın alma işlemleri oluşturmanız veya bunları güncellemenizin yanı sıra bu satın alımlara yönelik tüm sınıfları da içerir.
Firebase erişimini ayarlayın
Firebase Firestore'a erişmek için hizmet hesabı erişim anahtarı gerekir. Firebase proje ayarlarını açıp Hizmet hesapları bölümüne gidin ve Yeni özel anahtar oluştur'u seçin.
İndirilen JSON dosyasını assets/
klasörüne kopyalayın ve service-account-firebase.json
olarak yeniden adlandırın.
Google Play erişimini ayarlama
Satın alma işlemlerini doğrulamak üzere Play Store'a erişmek için bu izinlere sahip bir hizmet hesabı oluşturmanız ve bu hesaba ait JSON kimlik bilgilerini indirmeniz gerekir.
- Google Play Console'a gidin ve Tüm uygulamalar sayfasından başlayın.
- Kurulum > API erişimi. Google Play Console, bir proje oluşturmanızı veya mevcut bir projeye bağlantı vermenizi isterse bunu önce yapın, ardından bu sayfaya geri dönün.
- Hizmet hesaplarını tanımlayabileceğiniz bölümü bulun ve Yeni hizmet hesabı oluştur'u tıklayın.
- Açılan iletişim kutusunda Google Cloud Platform bağlantısını tıklayın.
- Projenizi seçin. Bu seçeneği görmüyorsanız sağ üstteki Hesap açılır listesinden doğru Google Hesabı'nda oturum açtığınızdan emin olun.
- Projenizi seçtikten sonra üst menü çubuğunda + Hizmet Hesabı Oluştur'u tıklayın.
- Hizmet hesabı için bir ad girin, neyle ilgili olduğunu hatırlamanız için isteğe bağlı olarak bir açıklama girin ve sonraki adıma geçin. .
- Hizmet hesabına Düzenleyici rolünü atayın.
- Sihirbazı tamamlayın, geliştirici konsolundaki API Erişimi sayfasına dönün ve Hizmet hesaplarını yenile'yi tıklayın. Yeni oluşturulan hesabınızı listede göreceksiniz. .
- Yeni hizmet hesabınız için Erişim izni ver'i tıklayın.
- Bir sonraki sayfada Finansal veriler bloğuna gidin. Hem Finansal verileri, siparişleri ve iptal anketine verilen yanıtları görüntüleme hem de Siparişleri ve abonelikleri yönet'i seçin.
- Kullanıcı davet et'i tıklayın.
- Hesap kurulumunu yaptığınıza göre yalnızca bazı kimlik bilgileri oluşturmanız gerekir. Cloud konsoluna dönün, hizmet hesapları listesinde hizmet hesabınızı bulun, üç dikey noktayı tıklayın ve Anahtarları yönet'i seçin.
- Yeni bir JSON anahtarı oluşturup indirin.
- İndirilen dosyanın adını
service-account-google-play.json,
olarak değiştirin veassets/
dizinine taşıyın.
Yapmamız gereken diğer bir işlem de lib/constants.dart,
uygulamasını açıp androidPackageId
değerini Android uygulamanız için seçtiğiniz paket kimliğiyle değiştirmektir.
Apple App Store erişimini ayarlama
Satın alma işlemlerini doğrulamak üzere App Store'a erişmek için, paylaşılan bir gizli anahtar oluşturmanız gerekir:
- App Store Connect'i açın.
- Uygulamalarım'a gidin ve uygulamanızı seçin.
- Kenar çubuğu gezinme menüsünde, Uygulama İçi Satın Alma İşlemleri > Yönet'e dokunun.
- Listenin sağ üst tarafında App-Specific Shared Secret (Uygulamaya Özel Paylaşılan Gizli Anahtar) seçeneğini tıklayın.
- Yeni gizli anahtar oluşturun ve kopyalayın.
lib/constants.dart,
dosyasını açın veappStoreSharedSecret
değerini, az önce oluşturduğunuz paylaşılan gizli anahtarla değiştirin.
Sabit değerler yapılandırma dosyası
Devam etmeden önce, lib/constants.dart
dosyasında aşağıdaki sabit değerlerin yapılandırıldığından emin olun:
androidPackageId
: Android'de kullanılan paket kimliği. ör.com.example.dashclicker
appStoreSharedSecret
: Satın alma işlemlerini doğrulamak üzere App Store Connect'e erişmek için paylaşılan gizli anahtar.bundleId
: iOS'te kullanılan paket kimliği. ör.com.example.dashclicker
Diğer sabit değerleri şimdilik göz ardı edebilirsiniz.
10. Satın alma işlemlerini doğrulayın
Satın alma işlemlerini doğrulamaya ilişkin genel akış iOS ve Android'de benzerdir.
Uygulamanız, her iki mağaza için de satın alma işlemi yapıldığında bir jeton alır.
Bu jeton, uygulama tarafından arka uç hizmetinize gönderilir ve ardından, sağlanan jetonu kullanarak satın alma işlemini ilgili mağazanın sunucularıyla doğrular.
Daha sonra arka uç hizmeti satın alma işlemini saklamayı seçebilir ve satın alma işleminin geçerli olup olmadığını uygulamaya yanıt verebilir.
Arka uç hizmetinin, doğrulamayı kullanıcınızın cihazında çalışan uygulama yerine mağazalar ile yapmasını sağlayarak (örneğin, sistem saatini geri sararak) kullanıcının premium özelliklere erişmesini önleyebilirsiniz.
Flutter tarafını ayarlama
Kimlik doğrulamasını ayarlayın
Satın alma işlemlerini arka uç hizmetinize göndereceğinizden, satın alma işlemi sırasında kullanıcının kimliğinin doğrulandığından emin olmak istersiniz. Kimlik doğrulama mantığının çoğu başlangıç projesine sizin için zaten eklenmiştir. Kullanıcı henüz giriş yapmadığında PurchasePage
öğesinin giriş düğmesini gösterdiğinden emin olmanız gerekir. PurchasePage
derleme yönteminin başına aşağıdaki kodu ekleyin:
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
Uygulamadan arama doğrulama uç noktası
Uygulamada, bir http post çağrısı kullanarak Dart arka ucunuzda /verifypurchase
uç noktasını çağıran _verifyPurchase(PurchaseDetails purchaseDetails)
işlevini oluşturun.
Seçilen mağazayı (Play Store için google_play
veya App Store için app_store
), serverVerificationData
ve productID
öğelerini gönderin. Sunucu, satın alma işleminin doğrulanıp doğrulanmadığını belirten bir durum kodu döndürür.
Uygulama sabit değerlerinde, sunucu IP'sini yerel makine IP adresinize göre yapılandırın.
lib/logic/dash_purchases.dart
FirebaseNotifier firebaseNotifier;
DashPurchases(this.counter, this.firebaseNotifier) {
// omitted
}
main.dart:
içinde, DashPurchases
oluşturarak firebaseNotifier
özelliğini ekleyin
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
context.read<FirebaseNotifier>(),
),
lazy: false,
),
FirebaseNotifier'da kullanıcı için bir alıcı ekleyerek kullanıcı kimliğini satın alma doğrulama işlevine iletebilirsiniz.
lib/logic/firebase_notifier.dart
User? get user => FirebaseAuth.instance.currentUser;
_verifyPurchase
işlevini DashPurchases
sınıfına ekleyin. Bu async
işlevi, satın alma işleminin doğrulanıp doğrulanmadığını gösteren bir boole döndürür.
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;
}
}
Satın alma işlemini uygulamadan hemen önce _handlePurchase
işlevinde _verifyPurchase
işlevini çağırın. Satın alma işlemini yalnızca doğrulandığında uygulamanız gerekir. Bir üretim uygulamasında bunu daha ayrıntılı şekilde belirtebilirsiniz (örneğin, mağaza geçici olarak kullanılamadığında deneme aboneliği uygulamak için). Ancak bu örnekte, basitliği koruyun ve satın alma işlemini yalnızca satın alma işlemi başarılı bir şekilde doğrulandığında uygulayın.
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);
}
}
Uygulamada artık satın alma işlemlerini doğrulamak için her şey hazır.
Arka uç hizmetini ayarlama
Ardından, arka uçta satın alma işlemlerini doğrulamak için Cloud Functions işlevini ayarlayın.
Satın alma işleyiciler oluşturma
Her iki mağazanın doğrulama akışı birbirine yakın olduğundan, her mağaza için ayrı uygulamalar içeren soyut bir PurchaseHandler
sınıfı oluşturun.
lib/
klasörüne bir purchase_handler.dart
dosyası ekleyerek başlayın. Bu dosyada, iki farklı satın alma türünü (abonelikler ve abonelik olmayanlar) doğrulamak için iki soyut yöntem içeren soyut bir PurchaseHandler
sınıfı tanımlarsınız.
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,
});
}
Gördüğünüz gibi her yöntem için üç parametre gerekir:
userId:
Satın alma işlemlerini kullanıcıyla ilişkilendirebilmeniz için giriş yapmış kullanıcının kimliği.productData:
Ürünle ilgili veriler. Bir dakika içinde tanımlayacaksınız.token:
Kullanıcıya mağaza tarafından sağlanan jeton.
Ayrıca bu satın alma işleyicilerin kullanımını kolaylaştırmak için hem abonelikler hem de abonelik olmayan öğeler için kullanılabilecek bir verifyPurchase()
yöntemi ekleyin:
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,
);
}
}
Artık her iki durum için de verifyPurchase
öğesini çağırabilirsiniz, ancak yine de ayrı uygulamaları kullanabilirsiniz.
ProductData
sınıfı, satın alınabilir farklı ürünlerle ilgili temel bilgileri içerir. Bu bilgiler arasında, ürün kimliği (bazen SKU olarak da adlandırılır) ve ProductType
yer alır.
lib/products.dart
class ProductData {
final String productId;
final ProductType type;
const ProductData(this.productId, this.type);
}
ProductType
, abonelik olabilir ya da olmayabilir.
lib/products.dart
enum ProductType {
subscription,
nonSubscription,
}
Son olarak, ürün listesi aynı dosyadaki bir harita olarak tanımlanır.
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,
),
};
Daha sonra, Google Play Store ve Apple App Store için bazı yer tutucu uygulamaları tanımlayın. Google Play ile başlayın:
lib/google_play_purchase_handler.dart
oluşturun ve az önce yazdığınız PurchaseHandler
öğesini genişleten bir sınıf ekleyin:
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;
}
}
Şimdilik işleyici yöntemleri için true
değerini döndürmektedir; bu adımları daha sonra ele alacağız.
Fark etmiş olabileceğiniz gibi, oluşturucu IapRepository
öğesinin bir örneğini alır. Satın alma işleyici, daha sonra Firestore'da yapılan satın alma işlemleriyle ilgili bilgileri depolamak için bu örneği kullanır. Google Play ile iletişim kurmak için, sağlanan AndroidPublisherApi
kullanılır.
Sonra, aynı işlemi uygulama mağazası işleyicisi için yapın. lib/app_store_purchase_handler.dart
oluşturun ve PurchaseHandler
öğesini tekrar genişleten bir sınıf ekleyin:
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;
}
}
Çok güzel! Artık iki satın alma işleyiciniz vardır. Şimdi satın alma doğrulama API'si uç noktası oluşturalım.
Satın alma işleyicileri kullanma
bin/server.dart
uygulamasını açın ve shelf_route
kullanarak bir API uç noktası oluşturun:
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');
}
}
Yukarıdaki kod aşağıdakileri yapıyor:
- Daha önce oluşturduğunuz uygulamadan çağrılacak bir POST uç noktası tanımlayın.
- JSON yükünün kodunu çözün ve aşağıdaki bilgileri çıkarın:
userId
: Şu anda giriş yapmış kullanıcı kimliğisource
: Kullanılan mağaza,app_store
veyagoogle_play
.productData
: Daha önce oluşturduğunuzproductDataMap
verisinden elde edilir.token
: Mağazalara gönderilecek doğrulama verilerini içerir.- Kaynağa bağlı olarak
GooglePlayPurchaseHandler
veyaAppStorePurchaseHandler
içinverifyPurchase
yöntemine çağrı. - Doğrulama başarılı olursa yöntem istemciye bir
Response.ok
döndürür. - Doğrulama başarısız olursa yöntem, istemciye bir
Response.internalServerError
döndürür.
API uç noktasını oluşturduktan sonra iki satın alma işleyiciyi yapılandırmanız gerekir. Bunun için önceki adımda edindiğiniz hizmet hesabı anahtarlarını yüklemeniz ve Android Publisher API ile Firebase Firestore API gibi farklı hizmetlere erişimi yapılandırmanız gerekir. Ardından, farklı bağımlılıklara sahip iki satın alma işleyici oluşturun:
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 satın alma işlemlerini doğrulama: Satın alma işleyicisini uygulama
Sonra, Google Play satın alma işleyiciyi uygulamaya devam edin.
Google, satın alma işlemlerini doğrulamanız gereken API'lerle etkileşimde bulunmanız için Dart paketleri sağlamaktadır. Bu anahtar kelimeleri server.dart
dosyasında başlattınız ve şimdi GooglePlayPurchaseHandler
sınıfında kullanıyorsunuz.
Abonelik türü olmayan satın alma işlemleri için işleyiciyi uygulayın:
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;
}
Abonelik satın alma işleyicisini benzer bir şekilde güncelleyebilirsiniz:
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;
}
}
Sipariş kimliklerinin ayrıştırılmasını kolaylaştırmak için aşağıdaki yöntemi ve satın alma durumunu ayrıştırmak için iki yöntem ekleyin.
lib/google_play_purchase_handler.dart
/// If a subscription suffix is present (..#) extract the orderId.
String extractOrderId(String orderId) {
final orderIdSplit = orderId.split('..');
if (orderIdSplit.isNotEmpty) {
orderId = orderIdSplit[0];
}
return orderId;
}
NonSubscriptionStatus _nonSubscriptionStatusFrom(int? state) {
return switch (state) {
0 => NonSubscriptionStatus.completed,
2 => NonSubscriptionStatus.pending,
_ => NonSubscriptionStatus.cancelled,
};
}
SubscriptionStatus _subscriptionStatusFrom(int? state) {
return switch (state) {
// Payment pending
0 => SubscriptionStatus.pending,
// Payment received
1 => SubscriptionStatus.active,
// Free trial
2 => SubscriptionStatus.active,
// Pending deferred upgrade/downgrade
3 => SubscriptionStatus.pending,
// Expired or cancelled
_ => SubscriptionStatus.expired,
};
}
Google Play satın alma işlemleriniz artık doğrulanmalı ve veritabanında depolanmalıdır.
Ardından, iOS için App Store'dan satın alma işlemlerine geçin.
iOS satın alma işlemlerini doğrulama: Satın alma işleyiciyi uygulayın
App Store ile satın alma işlemlerini doğrulamak için işlemi kolaylaştıran app_store_server_sdk
adlı bir üçüncü taraf Dart paketi sunulmaktadır.
ITunesApi
örneğini oluşturarak başlayın. Korumalı alan yapılandırmasını kullanın ve hata ayıklamayı kolaylaştırmak için günlük kaydını etkinleştirin.
lib/app_store_purchase_handler.dart
final _iTunesAPI = ITunesApi(
ITunesHttpClient(
ITunesEnvironment.sandbox(),
loggingEnabled: true,
),
);
Artık Google Play API'lerinin aksine App Store, hem abonelikler hem de abonelik olmayan öğeler için aynı API uç noktalarını kullanıyor. Bu, her iki işleyici için de aynı mantığı kullanabileceğiniz anlamına gelir. Aynı uygulamayı çağırmaları için bunları birleştirin:
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 {
//..
}
Şimdi handleValidation
uygulayın:
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;
}
}
App Store'dan satın aldığınız öğeler artık doğrulanıp veritabanında depolanıyor olmalıdır.
Arka ucu çalıştırma
Bu noktada, /verifypurchase
uç noktasını sunmak için dart bin/server.dart
çalıştırabilirsiniz.
$ dart bin/server.dart
Serving at http://0.0.0.0:8080
11. Satın alma işlemlerini takip edin
Kullanıcılarınızın düzenli olarak arka uç hizmetinde olduğunu varsayalım. Bunun nedeni, arka ucunuzun mağazadaki etkinliklere yanıt verebilmesi ve böylece, önbelleğe alma nedeniyle eski bilgilerle karşılaşma ihtimalinin daha düşük olması ve ayrıca oynanabilirliğe daha açık olmasıdır.
İlk olarak, geliştirmekte olduğunuz Dart arka ucuyla arka uçta mağaza etkinliklerinin işlenmesini ayarlayın.
Mağaza etkinliklerini arka uçta işleme
Mağazalar, aboneliklerin yenilenmesi gibi gerçekleşen faturalandırma etkinlikleri hakkında arka ucunuzu bilgilendirebilir. Veritabanınızdaki satın alma işlemlerini güncel tutmak için arka ucunuzda bu etkinlikleri işleyebilirsiniz. Bu bölümde hem Google Play Store hem de Apple App Store için kurulum yapın.
Google Play faturalandırma etkinliklerini işleme
Google Play, faturalandırma etkinlikleri bulut pub/sub konusu olarak adlandırdığı etkinlikler sunar. Bunlar temel olarak iletilerin yayınlanabileceği ve gönderilebileceği ileti sıralarıdır.
Bu işlev Google Play'e özgü olduğundan, söz konusu işlev GooglePlayPurchaseHandler
içinde yer almaktadır.
lib/google_play_purchase_handler.dart
uygulamasını açıp PubsubApi içe aktarımını ekleyerek başlayın:
lib/google_play_purchase_handler.dart
import 'package:googleapis/pubsub/v1.dart' as pubsub;
Ardından, PubsubApi
öğesini GooglePlayPurchaseHandler
öğesine iletin ve sınıf oluşturucuyu aşağıdaki gibi bir Timer
oluşturacak şekilde değiştirin:
lib/google_play_purchase_handler.dart
class GooglePlayPurchaseHandler extends PurchaseHandler {
final ap.AndroidPublisherApi androidPublisher;
final IapRepository iapRepository;
final pubsub.PubsubApi pubsubApi; // new
GooglePlayPurchaseHandler(
this.androidPublisher,
this.iapRepository,
this.pubsubApi, // new
) {
// Poll messages from Pub/Sub every 10 seconds
Timer.periodic(Duration(seconds: 10), (_) {
_pullMessageFromPubSub();
});
}
Timer
, _pullMessageFromSubSub
yöntemini her on saniyede bir çağıracak şekilde yapılandırılmıştır. Süre'yi kendi tercihinize göre ayarlayabilirsiniz.
Ardından, _pullMessageFromSubSub
oluşturun
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,
);
}
Az önce eklediğiniz kod, her on saniyede bir Google Cloud'dan Pub/Sub konusu ile iletişim kurar ve yeni mesajlar ister. Ardından, her iletiyi _processMessage
yönteminde işler.
Bu yöntem gelen mesajların kodunu çözer ve hem abonelik hem de abonelik olmayan her bir satın alma işlemiyle ilgili güncel bilgileri alır ve gerekirse mevcut handleSubscription
veya handleNonSubscription
çağırır.
Her mesajın _askMessage
yöntemiyle onaylanması gerekir.
Ardından, gerekli bağımlılıkları server.dart
dosyasına ekleyin. PubsubApi.cloudPlatformScope'u kimlik bilgisi yapılandırmasına ekleyin:
bin/server.dart
final clientGooglePlay =
await auth.clientViaServiceAccount(clientCredentialsGooglePlay, [
ap.AndroidPublisherApi.androidpublisherScope,
pubsub.PubsubApi.cloudPlatformScope, // new
]);
Daha sonra, PubsubApi örneğini oluşturun:
bin/server.dart
final pubsubApi = pubsub.PubsubApi(clientGooglePlay);
Ve son olarak, bunu GooglePlayPurchaseHandler
oluşturucuya iletin:
bin/server.dart
return {
'google_play': GooglePlayPurchaseHandler(
androidPublisher,
iapRepository,
pubsubApi, // new
),
'app_store': AppStorePurchaseHandler(
iapRepository,
),
};
Google Play kurulumu
Pub/sub konusundan faturalandırma etkinliklerini kullanmak için kodu yazdınız ancak pub/sub konusunu oluşturmadınız veya herhangi bir faturalandırma etkinliği yayınlamadınız. Bunu ayarlamanın zamanı geldi.
Öncelikle bir pub/sub konusu oluşturun:
- Google Cloud Console'da Cloud Pub/Sub sayfasını ziyaret edin.
- Firebase projenizde olduğunuzdan emin olun ve + Konu Oluştur'u tıklayın.
- Yeni konuya,
constants.ts
içindeGOOGLE_PLAY_PUBSUB_BILLING_TOPIC
için ayarlanan değere benzer bir ad verin. Bu örnekte, etiketiplay_billing
olarak adlandırın. Başka bir seçenek seçersenizconstants.ts
uygulamasını güncellemeyi unutmayın. Konuyu oluşturun. . - Pub/Sub konularınızın listesinde, az önce oluşturduğunuz konuyla ilgili üç dikey noktayı ve ardından İzinleri görüntüle'yi tıklayın.
- Sağdaki kenar çubuğunda Ana hesap ekle'yi seçin.
- Buraya
google-play-developer-notifications@system.gserviceaccount.com
adlı kullanıcıyı ekleyin ve Pub/Sub Yayıncısı rolü verin. - İzin değişikliklerini kaydedin.
- Yeni oluşturduğunuz konunun Konu adı'nı kopyalayın.
- Play Console'u tekrar açın ve Tüm Uygulamalar listesinden uygulamanızı seçin.
- Ekranı aşağı kaydırın ve Para kazanın > Para Kazanma Ayarları.
- Konunun tamamını doldurun ve değişikliklerinizi kaydedin. .
Tüm Google Play faturalandırma etkinlikleri artık konu hakkında yayınlanacak.
App Store faturalandırma etkinliklerini işleme
Sonra, aynı işlemi App Store faturalandırma etkinlikleri için de yapın. App Store'daki satın alma işlemlerinde güncellemeleri işlemenin iki etkili yolu vardır. Bunlardan biri, Apple'a sağladığınız ve Apple'ın sunucunuzla iletişim kurmak için kullandığı bir webhook'u uygulamaktır. Bu codelab'de yer alan ikinci yöntem ise App Store Server API'ye bağlanıp abonelik bilgilerini manuel olarak almaktır.
Bu codelab'in ikinci çözüme odaklanmasının nedeni, webhook'u uygulamak için sunucunuzu internete açık hale getirmenizin gerekmesidir.
Bir üretim ortamında, ideal olarak her ikisine de sahip olmanız gerekir. Bir etkinliği kaçırdıysanız veya abonelik durumunu tekrar kontrol etmeniz gerekirse App Store'dan ve Server API'den etkinlikleri almak için kullanılan webhook.
lib/app_store_purchase_handler.dart
uygulamasını açıp AppStoreServerAPI bağımlılığını ekleyerek başlayın:
lib/app_store_purchase_handler.dart
final AppStoreServerAPI appStoreServerAPI;
AppStorePurchaseHandler(
this.iapRepository,
this.appStoreServerAPI, // new
)
_pullStatus
yöntemini çağıracak bir zamanlayıcı eklemek için oluşturucuyu değiştirin. Bu zamanlayıcı, _pullStatus
yöntemini 10 saniyede bir çağırır. Bu zamanlayıcı süresini ihtiyaçlarınıza göre ayarlayabilirsiniz.
lib/app_store_purchase_handler.dart
AppStorePurchaseHandler(
this.iapRepository,
this.appStoreServerAPI,
) {
// Poll Subscription status every 10 seconds.
Timer.periodic(Duration(seconds: 10), (_) {
_pullStatus();
});
}
Ardından, aşağıdaki gibi _pullStatus yöntemini oluşturun:
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,
));
}
}
}
}
Bu yöntem aşağıdaki gibi çalışır:
- IapRepository'i kullanarak Firestore'dan etkin aboneliklerin listesini alır.
- Her sipariş için App Store Server API'ye abonelik durumu istenir.
- Söz konusu abonelik satın alma işlemi için son işlemi sağlar.
- Son kullanma tarihini kontrol eder.
- Firestore'daki abonelik durumunu günceller. Süresi dolmuşsa abonelik bu şekilde işaretlenir.
Son olarak, App Store Server API erişimini yapılandırmak için gereken tüm kodu ekleyin:
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 kurulumu
Sonra, App Store'u kurun:
- App Store Connect'e giriş yapın, ardından Kullanıcılar ve Erişim'i seçin.
- Key Type > (Anahtar Türü) > Uygulama içi satın alma.
- "Artı"ya dokunun. simgesine dokunun.
- Bir ad verin, ör. "Codelab anahtarı".
- Anahtarı içeren p8 dosyasını indirin.
- Dosyayı
SubscriptionKey.p8
adlı öğeler klasörüne kopyalayın. - Yeni oluşturulan anahtarın anahtar kimliğini kopyalayın ve
lib/constants.dart
dosyasındaappStoreKeyId
sabit değeri olarak ayarlayın. - Anahtar listesinin sağ üst tarafındaki Düzenleyen Kimliği'ni kopyalayın ve
lib/constants.dart
dosyasındaappStoreIssuerId
sabit değeri olarak ayarlayın.
Cihazdaki satın alma işlemlerini izleme
Satın alma işlemlerinizi izlemenin en güvenli yolu sunucu tarafındadır. Çünkü istemcinin güvenliğini sağlamak zordur ancak uygulamanın abonelik durumu bilgilerine göre işlem yapabilmesi için bilgileri istemciye geri ulaştırmanın bir yolunu bulmanız gerekir. Satın alınan öğeleri Firestore'da saklayarak verileri istemciyle kolayca senkronize edebilir ve otomatik olarak güncellenmesini sağlayabilirsiniz.
IAPRepo'yu uygulamaya zaten eklediniz. Bu, List<PastPurchase> purchases
uygulamasındaki kullanıcının tüm satın alma verilerini içeren Firestore deposudur. Depo ayrıca, süresi dolmamış productId storeKeySubscription
değerine sahip bir satın alma olduğunda doğru olan hasActiveSubscription,
değerini de içerir. Kullanıcı giriş yapmadığında liste boş olur.
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();
});
}
Tüm satın alma mantığı DashPurchases
sınıfındadır ve aboneliklerin uygulanması veya kaldırılması gereken yerdir. Dolayısıyla, iapRepo
öğesini sınıfa bir özellik olarak ekleyin ve oluşturucuda iapRepo
öğesini atayın. Daha sonra, oluşturucuya doğrudan bir işleyici ekleyin ve dispose()
yönteminde işleyiciyi kaldırın. Dinleyici, başlangıçta boş bir işlev olabilir. IAPRepo
bir ChangeNotifier
olduğundan ve Firestore'da her satın alma işlemi değiştiğinde notifyListeners()
yöntemini çağırdığınızdan, satın alınan ürünler değiştiğinde her zaman purchasesUpdate()
yöntemi çağrılır.
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
}
Ardından, IAPRepo
öğesini main.dart.
öğesinde oluşturucuya sağlayın. Zaten bir Provider
içinde oluşturulduğu için context.read
kullanarak depoyu alabilirsiniz.
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
context.read<FirebaseNotifier>(),
context.read<IAPRepo>(),
),
lazy: false,
),
Ardından, purchaseUpdate()
işlevinin kodunu yazın. dash_counter.dart,
ürününde applyPaidMultiplier
ve removePaidMultiplier
yöntemleri, çarpanı sırasıyla 10 veya 1 olarak ayarlar. Böylece aboneliğin önceden uygulanıp uygulanmadığını kontrol etmeniz gerekmez. Abonelik durumu değiştiğinde, satın alınabilir ürünün durumunu da güncellersiniz. Böylece, satın alma sayfasında ürünün zaten etkin olduğunu gösterebilirsiniz. _beautifiedDashUpgrade
özelliğini, yükseltmenin satın alıp almadığına göre ayarlayın.
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();
}
}
Artık abonelik ve yükseltme durumunun arka uç hizmetinde her zaman güncel olduğundan ve uygulamayla senkronize edildiğinden emin oldunuz. Uygulama buna uygun şekilde hareket ederek Dash tıklama oyununa abonelik ve yükseltme özelliklerini uygular.
12. Hepsi bu kadar.
Tebrikler!!! Codelab'i tamamladınız. Bu codelab için tamamlanmış kodu eksiksiz klasörde bulabilirsiniz.
Daha fazla bilgi edinmek için diğer Flutter codelab'lerini deneyin.