Bu codelab hakkında
1. Giriş
Flutter uygulamasına uygulama içi satın alma işlemleri eklemek için App ve Play mağazalarını doğru şekilde ayarlamanız, satın alma işlemini doğrulamanız ve abonelik avantajları gibi gerekli izinleri vermeniz gerekir.
Bu codelab'de, bir uygulamaya (sizin için sağlanmıştır) üç tür uygulama içi satın alma işlemi ekleyecek ve Firebase ile Dart arka ucu kullanarak bu satın alma işlemlerini doğrulayacaksınız. Sağlanan Dash Clicker uygulaması, Dash maskotunu para birimi olarak kullanan bir oyun içeriyor. Aşağıdaki satın alma seçeneklerini eklersiniz:
- Tek seferde 2.000 Dash satın alabileceğiniz, tekrarlanabilir bir satın alma seçeneği.
- Eski tarz Dash'i modern tarz Dash'e dönüştürmek için tek seferlik bir yükseltme satın alma işlemi.
- Otomatik olarak oluşturulan tıklamaları iki katına çıkaran abonelik.
İlk satın alma seçeneği, kullanıcıya doğrudan 2.000 Dash avantajı sağlar. Bu öğeler doğrudan kullanıcı tarafından kullanılabilir ve birden çok kez satın alınabilir. Doğrudan tüketilen ve birden çok kez tüketilebilen bu öğeye tüketim öğesi denir.
İkinci seçenek, Dash\'i daha güzel bir Dash\'e yükseltir. Bu özellik yalnızca bir kez satın alınmalıdır ve sonsuza kadar kullanılabilir. Uygulama tarafından kullanılamamasına rağmen sonsuza kadar geçerli olduğu için bu tür satın alma işlemlerine "tükenebilir olmayan" denir.
Üçüncü ve son satın alma seçeneği aboneliktir. Abonelik etkinken kullanıcı daha hızlı Dash kazanır ancak abonelik için ödemeyi durdurduğunda avantajlar da sona erer.
Arka uç hizmeti (sizin için de sağlanır) bir Dart uygulaması olarak çalışır, satın alma işlemlerinin yapıldığını doğrular ve Firestore'u kullanarak bunları depolar. Firestore, süreci kolaylaştırmak için kullanılır ancak üretim uygulamanızda herhangi bir arka uç hizmeti türünü kullanabilirsiniz.
Ne oluşturacaksınız?
- Bir uygulamayı, tüketim amaçlı satın alma işlemlerini ve abonelikleri destekleyecek şekilde genişletirsiniz.
- Ayrıca, 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 Firestore'da depolamak için mağazalarla iletişime geçme.
- Uygulamanızdaki satın alma işlemlerini yönetme
İhtiyacınız olanlar
- 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 indirin ve iOS için paket tanımlayıcısını, Android için de paket adını değiştirin.
Kodu indirme
GitHub deposunu komut satırından kopyalamak için aşağıdaki komutu kullanın:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Alternatif olarak, GitHub'ın cli aracı yüklüyse aşağıdaki komutu kullanın:
gh repo clone flutter/codelabs flutter-codelabs
Örnek kod, bir kod deneme koleksiyonu için kodu içeren flutter-codelabs
dizine klonlanır. Bu codelab'in kodu flutter-codelabs/in_app_purchases
'tedir.
flutter-codelabs/in_app_purchases
altındaki dizin yapısı, adlandırılmış her adımın sonunda nerede olmanız gerektiğini gösteren bir dizi anlık görüntü içerir. Başlangıç kodu 0. adımdadır. Bu nedenle, eşleşen dosyaları bulmak için:
cd flutter-codelabs/in_app_purchases/step_00
İleri atlamak veya bir adımdan sonra bir şeyin nasıl görünmesi gerektiğini görmek istiyorsanız ilgilendiğiniz adlandırmaya sahip dizinde bakın. Son adımın kodu complete
klasöründedir.
Başlangıç projesini oluşturma
Favori IDE'nizde step_00
'ten başlangıç projesini açın. Ekran görüntüleri için Android Studio'yu kullandık ancak Visual Studio Code de iyi bir seçenektir. Her iki düzenleyicide de en son Dart ve Flutter eklentilerinin yüklü olduğundan emin olun.
Hazırlayacağınız uygulamaların, hangi ürünlerin hangi fiyattan satıldığını öğ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 buna paket tanımlayıcısı, Android Play Store'da ise uygulama kimliği denir. Bu tanımlayıcılar genellikle ters alan adı gösterimi kullanılarak oluşturulur. Örneğin, flutter.dev için bir uygulama içi satın alma uygulaması oluştururken dev.flutter.inapppurchase
değerini kullanırsınız. Uygulamanız için bir tanımlayıcı düşünün. Bunu proje ayarlarında ayarlayacaksınız.
Öncelikle 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 modülü Xcode uygulamasında açın.
Xcode'un klasör yapısında Runner projesi en üstte yer alır. Flutter, Runner ve Ürünler hedefleri ise Runner projesinin altındadır. Proje ayarlarınızı düzenlemek için Runner'ı çift tıklayın ve İmza ve Yetenekler'i tıklayın. Ekibinizi ayarlamak için Ekip alanının altına yeni seçtiğiniz paket tanımlayıcısını girin.
Artık Xcode'u kapatabilir ve Android yapılandırmasını tamamlamak için Android Studio'ya dönebilirsiniz. Bunu yapmak için android/app,
altındaki build.gradle
dosyasını açın ve applicationId
değerinizi (aşağıdaki ekran görüntüsünde 37. satırda) iOS paket tanımlayıcısı ile aynı olan uygulama kimliğiyle değiştirin. iOS ve Android mağazalarının kimliklerinin aynı olması gerekmez ancak aynı tutulması hata olasılığını azaltır. Bu nedenle bu kod laboratuvarında da aynı tanımlayıcıları 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 in_app_purchase
'ü pubspec'e ekleyin:
$ cd app $ flutter pub add in_app_purchase dev:in_app_purchase_platform_interface
pubspec.yaml
dosyanızı açın ve dependencies
altında in_app_purchase
, dev_dependencies
altında ise in_app_purchase_platform_interface
girişinin listelendiğini onaylayın.
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
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 ayarlama
Uygulama içi satın alma işlemlerini ayarlamak ve iOS'te test etmek için App Store'da yeni bir uygulama ve satın alınabilir ürünler oluşturmanız gerekir. Herhangi bir şey yayınlamanız veya uygulamayı inceleme için Apple'a göndermeniz gerekmez. Bunu yapmak için geliştirici hesabınız olmalıdır. Hesabınız yoksa Apple geliştirici programına kaydolun.
Ücretli Uygulama Sözleşmeleri
Uygulama içi satın alma işlemlerini kullanmak için App Store Connect'te ücretli uygulamalarla ilgili etkin bir sözleşmenizin de olması gerekir. https://appstoreconnect.apple.com/ adresine gidin ve Sözleşmeler, Vergi ve Bankacılık'ı tıklayın.
Burada ücretsiz ve ücretli uygulamalarla ilgili sözleşmeleri görürsünüz. Ücretsiz uygulamaların durumu etkin, ü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ın durumu etkin olur. Etkin bir sözleşme olmadan uygulama içi satın alma işlemlerini deneyemezsiniz. Bu nedenle, bu işlem çok önemlidir.
Uygulama kimliğini kaydetme
Apple geliştirici portalında yeni bir tanımlayıcı oluşturun.
Uygulama kimliklerini seçme
Uygulama Seçin
Bir açıklama girin ve paket kimliğini, XCode'ta daha önce ayarlanan değerle eşleşecek şekilde ayarlayın.
Yeni uygulama kimliği oluşturma hakkında daha fazla bilgi için Geliştirici Hesabı Yardımı başlıklı makaleyi inceleyin .
Yeni uygulama oluşturma
Benzersiz paket tanımlayıcınızla App Store Connect'te yeni bir uygulama oluşturun.
Yeni uygulama oluşturma ve sözleşmeleri yönetme hakkında daha fazla bilgi için App Store Connect yardım sayfasını inceleyin.
Uygulama içi satın alma işlemlerini test etmek için korumalı alan test kullanıcısına ihtiyacınız vardır. 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 e-posta adreslerini 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 Testerler'e gidin.
Artık iPhone'unuzda Ayarlar > App Store > Korumalı alan hesabı'na giderek korumalı alan kullanıcınızı ayarlayabilirsiniz.
Uygulama içi satın alma işlemlerinizi yapılandırma
Ardından, satın alınabilecek üç öğeyi yapılandırın:
dash_consumable_2k
: Çok sayıda satın alınabilen ve kullanıcıya her satın alma işleminde 2.000 Dash (uygulama içi para birimi) kazandıran, tüketilebilecek bir satın alma işlemi.dash_upgrade_3d
: Kullanıcıya tıklaması için görsel açıdan farklı bir Dash sunan, yalnızca bir kez satın alınabilen, tüketilemeyen bir "yükseltme" satın alma işlemi.dash_subscription_doubler
: Abonelik süresi boyunca 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 gidin.
Uygulama içi satın alma işlemlerinizi belirtilen kimliklerle oluşturun:
dash_consumable_2k
öğesini tükenebilir olarak ayarlayın.
Ürün kimliği olarak dash_consumable_2k
kullanın. Referans adı yalnızca App Store Connect'te kullanılır. Adı dash consumable 2k
olarak ayarlayın ve satın alma işlemi için yerelleştirmelerinizi ekleyin. Satın alma işlemini Spring is in the air
olarak adlandırın ve açıklama olarak 2000 dashes fly out
kullanın.
dash_upgrade_3d
öğesini tükenmeyen 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. Satın alma işlemini 3D Dash
olarak adlandırın ve açıklama olarak Brings your dash back to the future
kullanın.
dash_subscription_doubler
'ü otomatik yenilenen abonelik olarak ayarlayın.
Abonelikler için akış biraz farklıdır. Öncelikle referans adını ve ürün kimliğini ayarlamanız gerekir:
Ardından, bir abonelik grubu oluşturmanız gerekir. Aynı grupta birden fazla abonelik olduğunda kullanıcılar aynı anda bunlardan yalnızca birine abone olabilir ancak bu abonelikler arasında kolayca geçiş yapabilir. Bu grubu subscriptions
olarak adlandırmanız yeterlidir.
Ardından, abonelik süresini ve yerelleştirmeleri girin. Bu aboneliğe Jet Engine
adını ve Doubles your clicks
açıklamasını verin. Kaydet'i tıklayın.
Kaydet düğmesini tıkladıktan sonra abonelik fiyatı ekleyin. İstediğiniz fiyatı seçin.
Artık satın alma işlemleri listesinde üç satın alma işlemini göreceksiniz:
5. Play Store'u ayarlama
App Store'da olduğu gibi Play Store için de geliştirici hesabınız olmalıdır. Henüz bir hesabınız yoksa hesap kaydedin.
Yeni uygulama oluşturma
Google Play Console'da yeni bir uygulama oluşturun:
- Play Console'u açın.
- Tüm uygulamalar > Uygulama oluştur'u seçin.
- Varsayılan dili seçin, uygulama 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 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 kurabileceğ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 gidip Uygulamanızı ayarlayın bölümündeki tüm görevleri tamamlayın. Burada, uygulamanızla ilgili içerik derecelendirmeleri ve ekran görüntüleri gibi bazı bilgileri sağlarsınız.
Uygulamayı imzalama
Uygulama içi satın alma işlemlerini test edebilmek için Google Play'e en az bir derleme yüklemeniz gerekir.
Bunun için sürüm derlemenizin hata ayıklama anahtarları dışında bir anahtarla imzalanması gerekir.
Anahtar deposu oluşturma
Mevcut bir anahtar deponuz varsa sonraki adıma geçin. Aksi takdirde, komut satırında aşağıdaki komutu çalıştırarak bir tane oluşturun.
Mac/Linux'te 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 saklar. Dosyayı başka bir yerde depolamak istiyorsanız -keystore
parametresine ilettiğiniz bağımsız değişkeni değiştirin. Keep the
keystore
Dosyayı gizli tutun; herkese açık kaynak kontrolüne eklemeyin.
Uygulamadan anahtar deposuna referans verme
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'de imzalamayı yapılandırma
<your app dir>/android/app/build.gradle
dosyasını düzenleyerek uygulamanız için imzalamayı yapılandırın.
android
bloğundan önce properties dosyanızdaki anahtar mağazası bilgilerini 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ğundan önce 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ında 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 imzalanır.
Uygulamanızı imzalama hakkında daha fazla bilgi için developer.android.com adresindeki Uygulamanızı imzalama başlıklı makaleyi inceleyin.
İlk derlemenizi yükleme
Uygulamanız imzalama için yapılandırıldıktan sonra aşağıdakileri çalıştırarak uygulamanızı derleyebilirsiniz:
flutter build appbundle
Bu komut varsayılan olarak bir sürüm derlemesi oluşturur ve çıkışı <your app dir>/build/app/outputs/bundle/release/
adresinde bulabilirsiniz.
Google Play Console'daki kontrol panelinde Sürüm > Test > Kapalı test'e gidin ve yeni bir kapalı test sürümü oluşturun.
Bu kod laboratuvarında uygulamayı Google'ın imzalamasını tercih edeceğiz. Bu nedenle, Play Uygulama İmzalama bölümünde Devam'a basarak bu özelliği etkinleştirin.
Ardından, derleme komutu tarafından oluşturulan app-release.aab
uygulama paketini yükleyin.
Kaydet'i ve ardından Sürümü incele'yi tıklayın.
Son olarak, dahili test sürümünü etkinleştirmek için Dahili test için yayınlamaya başla'yı tıklayın.
Test kullanıcıları oluşturma
Uygulama içi satın alma işlemlerini test edebilmek için test kullanıcılarınızın Google Hesapları'nın Google Play Console'a iki konumda eklenmesi gerekir:
- Belirli bir test kanalına (Dahili test)
- Lisans test kullanıcısı olarak
Öncelikle test kullanıcısını dahili test kanalına ekleyin. Sürüm > Test > Dahili test'e geri dönü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 alma işlemlerini test etmeye erişmesi 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'ne gidin.
- Uygulama içi satın alma işlemlerini test edebilmesi gereken test kullanıcılarının 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
Artık uygulama içinde satın alınabilecek öğeleri yapılandıracaksınız.
App Store'da olduğu gibi üç farklı satın alma işlemi tanımlamanız gerekir:
dash_consumable_2k
: Çok sayıda satın alınabilen ve kullanıcıya her satın alma işleminde 2.000 Dash (uygulama içi para birimi) kazandıran, tüketilebilecek bir satın alma işlemi.dash_upgrade_3d
: Kullanıcıya tıklaması için görsel olarak farklı bir Dash sunan, yalnızca bir kez satın alınabilen, tüketilemeyen bir "yükseltme" satın alma işlemi.dash_subscription_doubler
: Abonelik süresi boyunca kullanıcıya tıklama başına iki kat daha fazla kısa çizgi veren bir abonelik.
Öncelikle tüketilebilir ve tüketilemez öğeleri ekleyin.
- Google Play Console'a gidip uygulamanızı seçin.
- Para kazanma > Ürünler > Uygulama içi ürünler'e gidin.
- Ürün oluştur'u tıklayın
- Ürününüzle ilgili 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ıma sunulmuş olmayan "yükseltme" satın alma işlemi için bu işlemi tekrarlayın.
Ardından aboneliği ekleyin:
- Google Play Console'a gidip uygulamanızı seçin.
- Para kazanma > Ürünler > Abonelikler'e gidin.
- 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şlemleriniz Play Console'da ayarlanmış olmalıdır.
6. Firebase'i ayarlama
Bu codelab'de, kullanıcıların satın alma işlemlerini doğrulamak ve izlemek için bir arka uç hizmeti kullanacaksınız.
Arka uç hizmeti kullanmanın bazı avantajları vardır:
- İşlemleri güvenli bir şekilde doğrulayabilirsiniz.
- Faturalandırma etkinliklerine uygulama mağazalarından yanıt verebilirsiniz.
- Satın alma işlemlerini bir veritabanında takip edebilirsiniz.
- Kullanıcılar, sistem saatlerini geri sararak uygulamanızı premium özellikler sunmaya kandıramaz.
Arka uç hizmeti oluşturmanın birçok yolu olsa da bunu Google'ın kendi Firebase'ini kullanarak Cloud Functions ve Firestore'u kullanacaksınız.
Arka uç yazmak bu codelab'in kapsamı dışındadır. Bu nedenle, başlangıçtaki kodda, işe başlamanıza yardımcı olmak için temel satın alma işlemlerini yöneten bir Firebase projesi zaten mevcuttur.
Başlatıcı uygulamaya Firebase eklentileri de dahildir.
Geriye kendi Firebase projenizi oluşturmak, hem uygulamayı hem de arka uç sunucusunu Firebase için yapılandırmak ve son olarak arka uç sunucusunu dağıtmak kalıyor.
Firebase projesi oluşturma
Firebase Konsolu'na gidip yeni bir Firebase projesi oluşturun. Bu örnekte projeyi Dash Clicker olarak adlandı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 yapmanız gerekir. Bunun için Firebase'in Google ile oturum açma kimlik doğrulama modülünden yararlanın.
- Firebase kontrol panelinden Kimlik doğrulaması'na gidin ve gerekirse bu özelliği 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.
Cloud Firestore kurallarını aşağıdaki gibi 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 ayarlama
Firebase'i Flutter uygulamasına yüklemenin önerilen yolu FlutterFire CLI'yi kullanmaktır. Kurulum sayfasında açıklanan talimatları uygulayın.
flutterfire configure'i çalıştırırken ö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>
Ardından, 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 dosyasını geçersiz kılma hakkında soru 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'i ayarlama: Sonraki adımlar
Firebase kontrol panelinden Projeye Genel Bakış'a gidin, Ayarlar'ı ve Genel sekmesini seçin.
Uygulamalarınız'a gidip dashclicker (android) uygulamasını seçin.
Hata ayıklama modunda Google ile oturum açma özelliğine izin vermek için hata ayıklama sertifikanızı SHA-1 karma oluşturma parmak izini sağlamanız gerekir.
Hata ayıklama imzalama sertifikanızı alma
Flutter uygulama projenizin kökünde dizini android/
klasörü olarak değiştirin ve ardından bir imzalama raporu oluşturun.
cd android ./gradlew :app:signingReport
Size çok sayıda imzalama anahtarı listesi gösterilir. Hata ayıklama sertifikasının karma oluşturma işlevini aradığınız için Variant
ve Config
özelliklerinin debug
olarak ayarlandığı sertifikayı arayın. Anahtar deposunun, .android/debug.keystore
altındaki ana klasörünüzde olması muhtemeldir.
> 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önderme modal iletişim kutusunun son alanını doldurun.
iOS için Firebase'i ayarlama: Sonraki adımlar
ios/Runnder.xcworkspace
dosyasını Xcode
ile açın. Dilerseniz tercih ettiğiniz IDE ile de kullanabilirsiniz.
VSCode'da ios/
klasörünü, ardından open in xcode
'ı sağ tıklayın.
Android Studio'da ios/
klasörünü sağ tıklayın, ardından flutter
'yi ve open iOS module in Xcode
seçeneğini tıklayın.
iOS'te Google ile oturum açma özelliğine 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 belgelerini inceleyin.) 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:
REVERSED_CLIENT_ID
değerini,GoogleService-Info.plist
dosyasından, etrafındaki<string>..</string>
öğesi olmadan alın.CFBundleURLTypes
anahtarının altındaki hemios/Runner/Info-Debug.plist
hem deios/Runner/Info-Release.plist
dosyalarınızdaki 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 kurulumu tamamlandı.
7. Satın alma güncellemelerini dinleme
Codelab'in bu bölümünde, uygulamayı ürün satın almaya hazır hale getireceksiniz. Bu işlem, uygulama başladıktan sonra satın alma güncellemelerini ve hatalarını dinlemeyi içerir.
Satın alma güncellemelerini dinleme
main.dart,
içinde, iki sayfa içeren bir BottomNavigationBar
içeren Scaffold
öğesine sahip MyHomePage
widget'ını bulun. Bu sayfa, DashCounter
, DashUpgrades,
ve DashPurchases
için üç Provider
de oluşturur. DashCounter
, kısa çizgilerin mevcut sayısını izler ve bunları otomatik olarak artırır. DashUpgrades
, Dashes ile satın alabileceğiniz yükseltmeleri yönetir. Bu codelab, DashPurchases
'e odaklanmaktadır.
Varsayılan olarak, bir sağlayıcının nesnesi, söz konusu nesne ilk kez istendiğinde tanımlanır. Bu nesne, satın alma güncellemelerini doğrudan uygulama başladığında dinler. Bu nedenle, lazy: false
ile bu nesnede yavaş yüklemeyi devre dışı bırakın:
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
),
lazy: false, // Add this line
),
Ayrıca InAppPurchaseConnection
örneğine de ihtiyacınız vardır. Ancak uygulamanın test edilebilir kalması için bağlantıyı taklit etmeniz gerekir. Bunu yapmak için testte geçersiz kılınabilecek bir örnek yöntemi oluşturun ve 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.
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.
lib/logic/dash_purchases.dart
'te DashPurchases ChangeNotifier
için kodu bulun. Şu anda satın aldığınız Dash'lara yalnızca DashCounter
ekleyebilirsiniz.
Bir akış aboneliği özelliği (_subscription
, StreamSubscription<List<PurchaseDetails>> _subscription;
türü), IAPConnection.instance,
ve içe aktarma işlemleri ekleyin. Sonuçta elde edilen kod aşağıdaki gibi görünmelidir:
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();
}
}
_subscription
, oluşturucuda başlatıldığı için late
anahtar kelimesi _subscription
'a eklenir. Bu proje, varsayılan olarak boş olmayan (NNBD) şekilde ayarlanmıştır. Bu, boş olarak tanımlanmayan özelliklerin boş olmayan bir değere sahip olması gerektiği anlamına gelir. late
niteleyicisi, bu değeri tanımlamayı ertelemenize olanak tanır.
Oluşturucuda purchaseUpdated
akışını alın ve akışı dinlemeye başlayın. dispose()
yönteminde, akış aboneliğini iptal edin.
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.
}
Artık uygulama, satın alma güncellemelerini alıyor. Bu nedenle, bir sonraki bölümde satın alma işlemi gerçekleştireceksiniz.
Devam etmeden önce, her şeyin doğru şekilde ayarlandığından emin olmak için testleri "flutter test"
ile çalıştırı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 örnek ürünleri satın alınabilen gerçek ürünlerle değiştireceksiniz. Bu ürünler mağazalardan yüklenir, bir listede gösterilir ve ürüne dokunulduğunda satın alınır.
PurchasableProduct'ı uyarlama
PurchasableProduct
, sahte bir ürün gösteriyor. purchasable_product.dart
içindeki 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,
dosyasında, sahte satın alma işlemlerini kaldırın ve boş bir listeyle değiştirin, List<PurchasableProduct> products = [];
Mevcut satın alma işlemlerini yükleme
Kullanıcının satın alma işlemi gerçekleştirebilmesi için satın alma işlemlerini mağazadan yükleyin. Öncelikle mağazanın kullanılabilir olup olmadığını kontrol edin. Mağaza kullanılamadığında storeState
değerini notAvailable
olarak ayarladığınızda 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ıma sunulduğunda mevcut satın alma işlemlerini yükleyin. Önceki Firebase kurulumuna göre storeKeyConsumable
, storeKeySubscription,
ve storeKeyUpgrade
değerlerini görebilirsiniz. Beklenen bir satın alma işlemi mevcut değilse bu bilgileri konsola yazdırın. Bu bilgileri arka uç hizmetine de gönderebilirsiniz.
await iapConnection.queryProductDetails(ids)
yöntemi hem bulunamayan kimlikleri hem de bulunan ve satın alınabilen ürünleri döndürür. Kullanıcı arayüzünü güncellemek için yanıttaki productDetails
öğesini kullanın ve StoreState
öğesini 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);
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 değerini StoreState.available
yerine StoreState.loading:
olarak değiştirin.
lib/logic/dash_purchases.dart
StoreState storeState = StoreState.loading;
Satın alınabilecek ürünleri gösterme
purchase_page.dart
dosyasını ele alalım. PurchasePage
widget'ı, StoreState
'e bağlı olarak _PurchasesLoading
, _PurchaseList,
veya _PurchasesNotAvailable,
gösterir. Widget'ta, sonraki adımda kullanılan kullanıcının geçmiş satın alma işlemleri de gösterilir.
_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 Android ve iOS mağazalarında mevcut ürünleri görebilirsiniz. Satın alma işlemlerinin ilgili konsollara girildikten sonra kullanılabilir hale gelmesinin biraz zaman alabileceğini unutmayın.
dash_purchases.dart
bölümüne geri dönün ve ürün satın alma işlevini uygulayın. Yalnızca sarf malzemelerini, sarf malzemesi olmayanlardan ayırmanız gerekir. Yükseltme ve abonelik ürünleri tüketim amaçlı 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);
case storeKeySubscription:
case storeKeyUpgrade:
await iapConnection.buyNonConsumable(purchaseParam: purchaseParam);
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. Satın alma işlemini gerçekleştirdikten sonra completePurchase
numaralı telefonu aramanız önemlidir. Böylece mağaza, satın alma işleminin doğru şekilde yapıldığını bilir.
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. Arka ucu ayarlama
Satın alma işlemlerini izlemeye ve doğrulamaya geçmeden önce, bu işlemleri desteklemek için bir Dart arka uç ayarlayın.
Bu bölümde, kök olarak dart-backend/
klasöründen çalışın.
Aşağıdaki araçların yüklü olduğundan emin olun:
- Dart
- Firebase CLI
Temel projeye genel bakış
Bu projenin bazı bölümleri bu kod laboratuvarının kapsamı dışında kabul edildiğinden başlangıç koduna eklenmiştir. Başlamadan önce, nasıl bir yapı oluşturacağınız hakkında fikir edinmek için başlangıç kodunda neler olduğunu incelemeniz önerilir.
Bu arka uç kodu makinenizde yerel olarak çalışabilir. 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 aynı ağda olmaları ve cihazınızın 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. Sunucu varsayılan olarak herhangi bir rota sağlamaz. Daha sonra satın alma doğrulama sürecini yönetecek bir rota oluşturacaksınız.
Başlangıç koduna dahil olan bir bölüm, lib/iap_repository.dart
içindeki IapRepository
öğesidir. Firestore veya genel olarak veritabanlarıyla nasıl etkileşime geçeceğinizi öğrenmenin bu codelab ile alakalı olmadığı düşünüldüğünden, başlangıç kodunda Firestore'da satın alma işlemleri oluşturmanıza veya güncellemenize olanak tanıyan işlevlerin yanı sıra bu satın alma işlemlerine ait tüm sınıflar yer alır.
Firebase erişimini ayarlama
Firebase Firestore'a erişmek için bir hizmet hesabı erişim anahtarına ihtiyacınız vardır. Firebase proje ayarlarını açıp Hizmet hesapları bölümüne gidip Yeni özel anahtar oluştur'u seçerek bir anahtar oluşturun.
İ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 için Play Store'a erişmek istiyorsanız bu izinlere sahip bir hizmet hesabı oluşturmanız ve bu hesabın JSON kimlik bilgilerini indirmeniz gerekir.
- Google Play Console'a gidin ve Tüm uygulamalar sayfasından başlayın.
- Kurulum > API erişimi'ne gidin.
Google Play Console'un yeni bir proje oluşturmanızı veya mevcut bir projeye bağlantı oluşturmanızı istemesi durumunda önce bunu yapın ve ardından bu sayfaya 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 listesinde 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, isteğe bağlı olarak amacını hatırlayabilmeniz için bir açıklama da 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 geri dönün ve Hizmet hesaplarını yenile'yi tıklayın. Yeni oluşturduğunuz hesabı listede görürsünüz.
- Yeni hizmet hesabınız için Erişim izni ver'i tıklayın.
- Bir sonraki sayfayı aşağı kaydırarak 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önetme'yi seçin.
- Kullanıcı davet et'i tıklayın.
- Hesap oluşturulduğuna göre bazı kimlik bilgileri oluşturmanız yeterlidir. Cloud Console'a geri dönüp 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şturun ve indirin.
- İndirilen dosyayı
service-account-google-play.json,
olarak yeniden adlandırın veassets/
dizinine taşıyın.
Yapmamız gereken bir şey daha var. lib/constants.dart,
dosyasını açıp androidPackageId
değerini Android uygulamanız için seçtiğiniz paket kimliğiyle değiştirin.
Apple App Store erişimini ayarlama
Satın alma işlemlerini doğrulamak için App Store'a erişmek üzere paylaşılan bir gizli anahtar oluşturmanız gerekir:
- App Store Connect'i açın.
- Uygulamalarım'a gidip uygulamanızı seçin.
- Kenar çubuğu gezinme menüsünde Uygulama İçi Satın Alma İşlemleri > Yönet'e gidin.
- Listenin sağ üst kısmında Uygulama Özgün Ortak Gizli Anahtarı'nı tıklayın.
- Yeni bir gizli anahtar oluşturun ve kopyalayın.
lib/constants.dart,
dosyasını açın veappStoreSharedSecret
değerini, yeni oluşturduğunuz paylaşılan gizli anahtarla değiştirin.
Sabitler yapılandırma dosyası
Devam etmeden önce lib/constants.dart
dosyasında aşağıdaki sabitlerin yapılandırıldığından emin olun:
androidPackageId
: Android'de kullanılan paket kimliği. Ör.com.example.dashclicker
appStoreSharedSecret
: Satın alma doğrulamasını gerçekleştirmek için App Store Connect'e erişmek üzere paylaşılan gizli anahtar.bundleId
: iOS'te kullanılan paket kimliği. Ör.com.example.dashclicker
Sabitlerin geri kalanını şimdilik yoksayabilirsiniz.
10. Satın alma işlemlerini doğrulama
Satın alma işlemlerini doğrulamayla ilgili genel akış, iOS ve Android için benzerdir.
Her iki mağaza için de satın alma işlemi yapıldığında uygulamanız bir jeton alır.
Bu jeton, uygulama tarafından arka uç hizmetinize gönderilir. Arka uç hizmeti de sağlanan jetonu kullanarak satın alma işlemini ilgili mağazanın sunucularıyla doğrular.
Arka uç hizmeti daha sonra satın alma işlemini depolayabilir ve uygulamaya satın alma işleminin geçerli olup olmadığını yanıtlayabilir.
Arka uç hizmetinin, kullanıcınızın cihazında çalışan uygulama yerine mağazalarla doğrulama yapmasını sağlayarak kullanıcının sistem saatini geri sararak premium özelliklere erişmesini önleyebilirsiniz.
Flutter tarafını ayarlama
Kimlik doğrulamayı ayarlama
Satın alma işlemlerini arka uç hizmetinize göndereceğiniz için kullanıcının satın alma işlemi sırasında kimliğinin doğrulandığından emin olmak istersiniz. Kimlik doğrulama mantığının çoğu, başlangıç projesine sizin için eklenmiştir. Kullanıcı henüz oturum açmamışken PurchasePage
'ün giriş düğmesini gösterdiğinden emin olmanız yeterlidir. PurchasePage
sınıfının build 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({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
Uygulamadan arama doğrulama uç noktası
Uygulamada, Dart arka ucunuzda bir http post çağrısı kullanarak /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 durum kodunu döndürür.
Uygulama sabitlerinde sunucu IP'sini yerel makinenizin IP adresine göre yapılandırın.
lib/logic/dash_purchases.dart
FirebaseNotifier firebaseNotifier;
DashPurchases(this.counter, this.firebaseNotifier) {
// omitted
}
main.dart:
'de DashPurchases
oluşturulurken firebaseNotifier
'ü ekleme
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
context.read<FirebaseNotifier>(),
),
lazy: false,
),
Kullanıcı kimliğini satın alma işlemini doğrulama işlevine iletebilmeniz için FirebaseNotifier'a kullanıcı için bir alıcı ekleyin.
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ı belirten bir boole değeri 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) {
return true;
} else {
return false;
}
}
Satın alma işlemini uygulamadan hemen önce _handlePurchase
içinde _verifyPurchase
işlevini çağırın. Satın alma işlemini yalnızca doğrulandıktan sonra uygulamanız gerekir. Üretim uygulamasında bunu daha ayrıntılı şekilde belirtebilirsiniz. Örneğin, mağaza geçici olarak kullanılamadığında deneme aboneliği uygulayabilirsiniz. Ancak bu örnekte basitliği koruyun ve satın alma işlemini yalnızca satın alma işlemi başarıyla 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();
case storeKeyConsumable:
counter.addBoughtDashes(1000);
}
}
}
if (purchaseDetails.pendingCompletePurchase) {
await iapConnection.completePurchase(purchaseDetails);
}
}
Uygulamada satın alma işlemlerini doğrulamaya hazırsınız.
Arka uç hizmetini ayarlama
Ardından, arka uçta satın alma işlemlerini doğrulamak için Cloud Functions'ı ayarlayın.
Satın alma işleyicileri oluşturma
Her iki mağazanın doğrulama akışı neredeyse aynı 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 dışı satın alma işlemleri) doğrulamak için iki soyut yöntem içeren soyut bir PurchaseHandler
sınıfı tanımlayın.
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:
Oturum açmış kullanıcının kimliğidir. Böylece satın alma işlemlerini kullanıcıya bağlayabilirsiniz.productData:
Ürünle ilgili veriler. Bunu bir dakika içinde tanımlayacaksınız.token:
Mağaza tarafından kullanıcıya sağlanan jeton.
Ayrıca, bu satın alma işleyicilerinin kullanımını kolaylaştırmak için hem abonelikler hem de abonelik dışı ürünler 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
işlevini çağırabilirsiniz ancak ayrı uygulamalara sahip olabilirsiniz.
ProductData
sınıfı, satın alınabilen 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 veya abonelik dışı olabilir.
lib/products.dart
enum ProductType {
subscription,
nonSubscription,
}
Son olarak, ürün listesi aynı dosyada 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,
),
};
Ardından, Google Play Store ve Apple App Store için bazı yer tutucu uygulamaları tanımlayın. Google Play'den başlayın:
lib/google_play_purchase_handler.dart
oluşturun ve az önce yazdığınız PurchaseHandler
sınıfını 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;
}
}
Şu anda işleyici yöntemleri için true
döndürüyor. Bu yöntemlere daha sonra değineceğiz.
Fark etmiş olabileceğiniz gibi, kurucu IapRepository
örneği alır. Satın alma işleyicisi, daha sonra satın alma işlemleriyle ilgili bilgileri Firestore'da depolamak için bu örneği kullanır. Google Play ile iletişim kurmak için sağlanan AndroidPublisherApi
'i kullanırsınız.
Ardından, uygulama mağazası işleyicisi için de aynı işlemi yapın. lib/app_store_purchase_handler.dart
sınıfını oluşturun ve PurchaseHandler
sınıfını genişleten bir sınıf daha 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;
}
}
Mükemmel! Artık iki satın alma işleyiciniz var. Ardından, satın alma işlemi doğrulama API'si uç noktasını oluşturalım.
Satın alma işleyicilerini kullanma
bin/server.dart
dosyası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.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');
}
}
Yukarıdaki kod aşağıdaki işlemleri gerçekleştirir:
- Daha önce oluşturduğunuz uygulamadan çağrılacak bir POST uç noktası tanımlayın.
- JSON yükünün kodunu çözerek aşağıdaki bilgileri ayıklayın:
userId
: Şu anda giriş yapmış kullanıcının kimliğisource
: Kullanılan mağaza (app_store
veyagoogle_play
).productData
: Daha önce oluşturduğunuzproductDataMap
kaynağından alınır.token
: Mağazalara gönderilecek doğrulama verilerini içerir.- Kaynağa bağlı olarak
GooglePlayPurchaseHandler
veyaAppStorePurchaseHandler
içinverifyPurchase
yöntemini çağırın. - Doğrulama başarılı olursa yöntem, müşteriye 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şleyicisini 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şleyicisi 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şleyiciyi uygulama
Ardından, Google Play satın alma işleyicisini uygulamaya devam edin.
Google, satın alma işlemlerini doğrulamak için ihtiyaç duyduğunuz API'lerle etkileşime geçmek üzere Dart paketleri sağlamaktadır. Bu değişkenleri server.dart
dosyasında başlattınız ve şimdi GooglePlayPurchaseHandler
sınıfında kullanıyorsunuz.
Abonelik dışı 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 ş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şin kimliğinin 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 daha ekleyin.
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;
}
Google Play satın alma işlemleriniz doğrulanmış ve veritabanında depolanmıştır.
Ardından, iOS için App Store satın alma işlemlerine geçin.
iOS satın alma işlemlerini doğrulama: Satın alma işleyicisini uygulama
App Store'daki satın alma işlemlerini doğrulamak için süreci kolaylaştıran app_store_server_sdk
adlı bir üçüncü taraf Dart paketi vardır.
ITunesApi
örneğini oluşturarak başlayın. Hata ayıklama işlemini kolaylaştırmak için korumalı alan yapılandırmasını kullanın ve günlük kaydını etkinleştirin.
lib/app_store_purchase_handler.dart
final _iTunesAPI = ITunesApi(
ITunesHttpClient(
ITunesEnvironment.sandbox(),
loggingEnabled: true,
),
);
Artık App Store, Google Play API'lerinin aksine hem abonelikler hem de abonelik dışı uygulamalar 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ğıracak şekilde 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 {
//..
}
Ardından 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) {
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 satın alma işlemleriniz doğrulandı ve veritabanında saklandı.
Arka ucu çalıştırma
Bu noktada, /verifypurchase
uç noktasını yayınlamak için dart bin/server.dart
komutunu çalıştırabilirsiniz.
$ dart bin/server.dart
Serving at http://0.0.0.0:8080
11. Satın alma işlemlerinizi takip edin
Kullanıcılarınızın satın alma işlemlerini izlemenin önerilen yolu arka uç hizmetindedir. Bunun nedeni, arka uç sunucunuzun mağazadaki etkinliklere yanıt verebilmesi ve bu nedenle önbelleğe alma nedeniyle eski bilgilerle karşılaşma olasılığının daha düşük olması ve ayrıca sunucunun değiştirilme olasılığının daha düşük olmasıdır.
Öncelikle, oluşturmakta 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 uçunuzu bilgilendirebilir. Veritabanı satın alma işlemlerinizi güncel tutmak için bu etkinlikleri arka uçta işleyebilirsiniz. Bu bölümde, bu ayarı hem Google Play Store hem de Apple App Store için yapın.
Google Play faturalandırma etkinliklerini işleme
Google Play, cloud pub/sub konusu olarak adlandırdığı bir yöntemle faturalandırma etkinlikleri sağlar. Bunlar temel olarak, mesajların yayınlanabileceği ve tüketilebileceği mesaj sıralarıdır.
Bu işlev Google Play'e özgü olduğundan bu işlevi GooglePlayPurchaseHandler
bölümüne dahil edersiniz.
lib/google_play_purchase_handler.dart
'ü açıp PubsubApi içe aktarma işlemini 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 kurucusunu aşağıdaki gibi değiştirerek bir Timer
oluşturun:
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 on saniyede bir çağıracak şekilde yapılandırılmıştır. Süre'yi kendi tercihinize göre ayarlayabilirsiniz.
Ardından _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,
);
}
Az önce eklediğiniz kod, on saniyede bir Google Cloud'daki Pub/Sub konusuyla iletişim kurar ve yeni mesaj ister. Ardından, her iletiyi _processMessage
yönteminde işler.
Bu yöntem, gelen mesajların kodunu çözer ve gerekirse mevcut handleSubscription
veya handleNonSubscription
öğesini çağırarak her satın alma işlemi (hem abonelikler hem de abonelik dışı satın alma işlemleri) hakkında güncel bilgileri alır.
Her mesajın _askMessage
yöntemiyle onaylanması gerekir.
Ardından, gerekli bağımlılıkları server.dart
dosyasına ekleyin. PubsubApi.cloudPlatformScope kimlik bilgisi yapılandırmasına ekleyin:
bin/server.dart
final clientGooglePlay =
await auth.clientViaServiceAccount(clientCredentialsGooglePlay, [
ap.AndroidPublisherApi.androidpublisherScope,
pubsub.PubsubApi.cloudPlatformScope, // new
]);
Ardından PubsubApi örneğini oluşturun:
bin/server.dart
final pubsubApi = pubsub.PubsubApi(clientGooglePlay);
Son olarak, 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 tüketecek kodu yazdınız ancak pub/sub konusunu oluşturmadınız ve faturalandırma etkinliği yayınlamıyorsunuz. Ayarlama zamanı geldi.
Öncelikle bir pub/sub konusu oluşturun:
- Google Cloud Console'daki 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ğerle aynı bir ad verin. Bu durumda dosyayıplay_billing
olarak adlandırın. Başka bir şey seçersenizconstants.ts
değerini güncellediğinizden emin olun. Konuyu oluşturun. - Yayın/abonelik konularınızın listesinde, az önce oluşturduğunuz konunun üç dikey noktasını ve ardından İzinleri görüntüle'yi tıklayın.
- Sağdaki kenar çubuğunda Müdür ekle'yi seçin.
- Burada
google-play-developer-notifications@system.gserviceaccount.com
hesabını ekleyin ve bu hesaba Pub/Sub yayıncısı rolünü verin. - İzin değişikliklerini kaydedin.
- Az önce oluşturduğunuz konunun Konu adını kopyalayın.
- Play Console'u tekrar açıp Tüm Uygulamalar listesinden uygulamanızı seçin.
- Aşağı kaydırarak Para kazanma > Para kazanma kurulumu'na gidin.
- Konuyu eksiksiz doldurup değişikliklerinizi kaydedin.
Artık tüm Google Play faturalandırma etkinlikleri bu konuda yayınlanacak.
App Store faturalandırma etkinliklerini işleme
Ardından, App Store faturalandırma etkinlikleri için de aynı işlemi yapın. App Store'daki satın alma işlemlerinde güncellemelerin uygulanmasının 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 uygulamaktır. Bu codelab'de açıklanan ikinci yöntem ise App Store Server API'ye bağlanıp abonelik bilgilerini manuel olarak elde etmektir.
Bu kod laboratuvarının ikinci çözüme odaklanmasının nedeni, webhook'u uygulamak için sunucunuzu internete açmanız gerekmesidir.
Üretim ortamında ideal olarak her ikisine de sahip olmak istersiniz. App Store'dan etkinlik almak için webhook ve bir etkinliği kaçırmanız veya abonelik durumunu tekrar kontrol etmeniz durumunda Server API.
lib/app_store_purchase_handler.dart
dosyası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 kurucuyu 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, _pullStatus yöntemini aşağıdaki gibi 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 şu şekilde çalışır:
- IapRepository'yi kullanarak Firestore'dan etkin aboneliklerin listesini alır.
- Her sipariş için App Store Server API'den abonelik durumunu ister.
- Söz konusu abonelik satın alma işleminin son işlemini alır.
- Son kullanma tarihini kontrol eder.
- Firestore'da abonelik durumunu günceller. Süresi dolan abonelikler bu şekilde işaretlenir.
Son olarak, App Store Server API erişimini yapılandırmak için gerekli 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
Ardından App Store'u ayarlayın:
- App Store Connect'e giriş yapın ve Kullanıcılar ve Erişim'i seçin.
- Anahtar Türü > Uygulama İçi Satın Alma'ya gidin.
- Yeni bir kampanya eklemek için "artı" simgesine dokunun.
- "Codelab anahtarı" gibi bir ad verin.
- Anahtarı içeren p8 dosyasını indirin.
- Dosyayı
SubscriptionKey.p8
adıyla öğeler klasörüne kopyalayın. - Yeni oluşturulan anahtardaki anahtar kimliğini kopyalayıp
lib/constants.dart
dosyasındaappStoreKeyId
sabit değerine ayarlayın. - Anahtar listesinin en üstündeki Issuer ID'yi kopyalayın ve
lib/constants.dart
dosyasındaappStoreIssuerId
sabit değerine ayarlayın.
Cihazdaki satın almaları izleme
Satın alma işlemlerinizi izlemenin en güvenli yolu sunucu tarafındadır. Bunun nedeni, istemcinin güvenliğinin sağlanmasının zor olmasıdır. Ancak uygulamanın abonelik durumu bilgilerine göre işlem yapabilmesi için bilgileri istemciye geri göndermeniz gerekir. Satın alma işlemlerini Firestore'da depolayarak verileri istemciyle kolayca senkronize edebilir ve otomatik olarak güncel tutabilirsiniz.
List<PastPurchase> purchases
'de kullanıcının tüm satın alma verilerini içeren Firestore deposu olan IAPRepo'yu uygulamaya zaten eklemişsinizdir. Depoda ayrıca hasActiveSubscription,
bulunur. Bu değer, süresi dolmamış bir productId storeKeySubscription
ile satın alma işlemi olduğunda doğru olur. 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 uygulanacağı veya kaldırılacağı yerdir. Bu nedenle, iapRepo
değerini sınıfa bir özellik olarak ekleyin ve iapRepo
değerini oluşturucuda atayın. Ardından, doğrudan oluşturucuya bir dinleyici ekleyin ve dinleyiciyi dispose()
yönteminde kaldırın. İlk başta dinleyici boş bir işlev olabilir. IAPRepo
bir ChangeNotifier
olduğundan ve Firestore'daki satın alma işlemleri her değiştiğinde notifyListeners()
çağrısı yapıldığından, satın alınan ürünler değiştiğinde purchasesUpdate()
yöntemi her zaman ç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() {
_subscription.cancel();
iapRepo.removeListener(purchasesUpdate);
super.dispose();
}
void purchasesUpdate() {
//TODO manage updates
}
Ardından, main.dart.
içindeki kurucuya IAPRepo
değerini sağlayın. Provider
içinde zaten oluşturulduğu için context.read
kullanarak deposu 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,
'te applyPaidMultiplier
ve removePaidMultiplier
yöntemleri çarpanı sırasıyla 10 veya 1 olarak ayarlar. Bu nedenle, aboneliğin uygulanıp uygulanmadığını kontrol etmeniz gerekmez. Abonelik durumu değiştiğinde, satın alınabilir ürünün durumunu da güncelleyerek satın alma sayfasında etkin olduğunu gösterebilirsiniz. _beautifiedDashUpgrade
mülkünü, yükseltmenin satın alınıp alınmadığı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 arka uç hizmetinde abonelik ve yükseltme durumunun her zaman güncel olduğundan ve uygulamayla senkronize edildiğinden emin oldunuz. Uygulama, buna göre hareket eder ve abonelik ile yükseltme özelliklerini Dash tıklama oyununuza uygular.
12. İşlem tamamlandı
Tebrikler. Codelab'i tamamladınız. Bu codelab'in tamamlanmış kodunu tamamlandı klasöründe bulabilirsiniz.
Daha fazla bilgi edinmek için diğer Flutter codelab'lerini deneyin.