1. Giriş
Flutter uygulamasına uygulama içi satın alma işlemleri eklemek için uygulama ve Play Store'un doğru şekilde ayarlanması, satın alma işleminin doğrulanması ve abonelik avantajları gibi gerekli izinlerin verilmesi gerekir.
Bu codelab'de, size sağlanan bir uygulamaya üç tür uygulama içi satın alma ekleyecek ve bu satın alma işlemlerini Firebase ile Dart arka ucunu kullanarak doğrulayacaksınız. Sağlanan Dash Clicker uygulamasında, para birimi olarak Dash maskotunun kullanıldığı bir oyun bulunuyor. Aşağıdaki satın alma seçeneklerini eklersiniz:
- 2.000 Dash'i tek seferde satın almak için tekrarlanabilir bir satın alma seçeneği.
- Eski stil kontrol panelini modern stil kontrol paneline 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 Dash avantajı sunar. Bu ürünler doğrudan kullanıcıya sunulur ve birden çok kez satın alınabilir. Bu öğe, doğrudan tüketildiği ve birden çok kez tüketilebildiği için tüketilebilir öğe olarak adlandırılır.
İkinci seçenek, Dash'i daha güzel bir Dash'e yükseltir. Bu özellik yalnızca bir kez satın alınır ve ömür boyu kullanılabilir. Bu tür satın alma işlemlerine, uygulama tarafından tüketilemediği ancak süresiz olarak geçerli olduğu için tüketilebilir olmayan satın alma işlemi denir.
Üçüncü ve son satın alma seçeneği aboneliktir. Abonelik etkin olduğu sürece kullanıcı daha hızlı bir şekilde hazır yanıtlar alır. Ancak abonelik için ödeme yapmayı bıraktığında bu avantajlar da ortadan kalkar.
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 bunları Firestore kullanarak depolar. Firestore, süreci kolaylaştırmak için kullanılır ancak üretim uygulamanızda herhangi bir türde arka uç hizmeti kullanabilirsiniz.
Ne oluşturacaksınız?
- Uygulamayı, tüketilebilir 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 nasıl iletişim kuracağınız.
- Uygulamanızdaki satın alma işlemlerini yönetme
İhtiyacınız olanlar
- Android Studio
- Xcode (iOS geliştirme için)
- Flutter SDK'sı
2. Geliştirme ortamını kurma
Bu codelab'i başlatmak için kodu indirin ve iOS için paket tanımlayıcısını, Android için paket adını değiştirin.
Kodu indirme
GitHub deposunu komut satırından klonlamak için aşağıdaki komutu kullanın:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Alternatif olarak, GitHub'ın cli aracını yüklediyseniz aşağıdaki komutu kullanın:
gh repo clone flutter/codelabs flutter-codelabs
Örnek kod, bir dizi codelab'in kodunu içeren bir flutter-codelabs
dizinine klonlanır. Bu codelab'in kodu flutter-codelabs/in_app_purchases
içinde yer alır.
flutter-codelabs/in_app_purchases
altındaki dizin yapısında, adlandırılmış her adımın sonunda olmanız gereken yerlerin bir dizi anlık görüntüsü bulunur. Başlangıç kodu 0. adımda yer alır. Bu nedenle, aşağıdaki şekilde koda gidin:
cd flutter-codelabs/in_app_purchases/step_00
İlerlemek veya bir adımın ardından bir şeyin nasıl görüneceğini görmek istiyorsanız ilgilendiğiniz adla adlandırılmış dizine bakın. Son adımın kodu complete
klasöründedir.
Başlangıç projesini ayarlama
step_00/app
adresindeki başlangıç projesini favori IDE'nizde açın. Ekran görüntüleri için Android Studio'yu kullandık ancak Visual Studio Code da harika bir seçenektir. Her iki düzenleyicide de en son Dart ve Flutter eklentilerinin yüklü olduğundan emin olun.
Geliştireceğiniz uygulamaların, hangi ürünlerin hangi fiyata 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 adı verilir. Bu tanımlayıcılar genellikle ters alan adı notasyonu kullanılarak oluşturulur. Örneğin, flutter.dev için uygulama içi satın alma uygulaması oluştururken dev.flutter.inapppurchase
kullanırsınız. Uygulamanız için bir tanımlayıcı düşünün. Şimdi bunu proje ayarlarında belirleyeceksiniz.
Öncelikle iOS için paket tanımlayıcıyı ayarlayın. Bunu yapmak için Xcode uygulamasında Runner.xcworkspace
dosyasını açın.
Xcode'un klasör yapısında Runner projesi en üstte, Flutter, Runner ve Products hedefleri ise Runner projesinin altında yer alır. Proje ayarlarınızı düzenlemek için Runner'ı çift tıklayın ve Signing & Capabilities'i (İmzalama ve Yetenekler) tıklayın. Ekibinizi ayarlamak için Team (Ekip) alanına az önce seçtiğiniz paket tanımlayıcıyı girin.
Artık Xcode'u kapatabilir ve Android yapılandırmasını tamamlamak için Android Studio'ya geri dönebilirsiniz. Bunu yapmak için android/app,
altındaki build.gradle.kts
dosyasını açın ve applicationId
(aşağıdaki ekran görüntüsünde 24. satırda) değerini, 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ı gerekmez. Ancak aynı kimlikleri kullanmak hata olasılığını azaltır. Bu nedenle bu codelab'de 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
Projenizin bağımlılıklarına in_app_purchase
ekleyerek pubspec'e in_app_purchase
ekleyin:
$ cd app $ flutter pub add in_app_purchase dev:in_app_purchase_platform_interface Resolving dependencies... Downloading packages... characters 1.4.0 (1.4.1 available) flutter_lints 5.0.0 (6.0.0 available) + in_app_purchase 3.2.3 + in_app_purchase_android 0.4.0+3 + in_app_purchase_platform_interface 1.4.0 + in_app_purchase_storekit 0.4.4 + json_annotation 4.9.0 lints 5.1.1 (6.0.0 available) material_color_utilities 0.11.1 (0.13.0 available) meta 1.16.0 (1.17.0 available) provider 6.1.5 (6.1.5+1 available) test_api 0.7.6 (0.7.7 available) Changed 5 dependencies! 7 packages have newer versions incompatible with dependency constraints. Try `flutter pub outdated` for more information.
pubspec.yaml
hesabınızı açın ve in_app_purchase
'nin dependencies
altında, in_app_purchase_platform_interface
'ün ise dev_dependencies
altında giriş olarak listelendiğini doğrulayın.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
cloud_firestore: ^6.0.0
cupertino_icons: ^1.0.8
firebase_auth: ^6.0.1
firebase_core: ^4.0.0
google_sign_in: ^7.1.1
http: ^1.5.0
intl: ^0.20.2
provider: ^6.1.5
logging: ^1.3.0
in_app_purchase: ^3.2.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
in_app_purchase_platform_interface: ^1.4.0
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 oluşturmanız ve bu uygulamada 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 bir 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 kullanmak için App Store Connect'te ücretli uygulamalarla ilgili etkin bir sözleşmeniz de olmalıdır. https://appstoreconnect.apple.com/ adresine gidin ve Sözleşmeler, Vergi ve Bankacılık'ı tıklayın.
Ücretsiz ve ücretli uygulamalarla ilgili sözleşmeleri burada 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 deneyemeyeceğiniz için bu çok önemlidir.
Uygulama kimliğini kaydetme
Apple Developer Portal'da yeni bir tanımlayıcı oluşturun. developer.apple.com/account/resources/identifiers/list adresini ziyaret edin ve Identifiers başlığının yanındaki "artı" simgesini tıklayın.
Uygulama kimliklerini seçme
Uygulama Seçin
Bir açıklama girin ve paket kimliğini, daha önce Xcode'da ayarlanan paket kimliğiyle eşleşecek şekilde ayarlayın.
Yeni bir 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
App Store Connect'te benzersiz paket tanımlayıcınızla yeni bir uygulama oluşturun.
Yeni bir uygulama oluşturma ve sözleşmeleri yönetme hakkında daha fazla bilgi için App Store Connect Yardım Merkezi'ne bakın.
Uygulama içi satın alma işlemlerini test etmek için bir 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. Zaten bir Apple hesabı için kullanılan bir e-posta adresini kullanamazsınız. Yeni bir sandbox hesabı oluşturmak veya mevcut sandbox Apple kimliklerini yönetmek için Kullanıcılar ve Erişim bölümündeki Sandbox'a gidin.
Artık iPhone'unuzda Ayarlar > Geliştirici > Sandbox Apple Hesabı'na giderek sandbox kullanıcınızı ayarlayabilirsiniz.
Uygulama içi satın alma işlemlerinizi yapılandırma
Şimdi satın alınabilir üç öğeyi yapılandırın:
dash_consumable_2k
: Birçok kez satın alınabilen ve her satın alma işleminde kullanıcıya 2.000 Dash (uygulama içi para birimi) veren tüketilebilir bir satın alma işlemi.dash_upgrade_3d
: Yalnızca bir kez satın alınabilen ve kullanıcıya tıklaması için kozmetik olarak farklı bir Dash veren, tüketilemeyen bir "yükseltme" satın alma işlemi.dash_subscription_doubler
: Kullanıcıya abonelik süresi boyunca tıklama başına iki kat Dash hakkı veren abonelik.
Uygulama İçi Satın Alma İşlemleri'ne gidin.
Uygulama içi satın alma işlemlerinizi belirtilen kimliklerle oluşturun:
dash_consumable_2k
uygulamasını Tüketilebilir Ürün olarak ayarlayın. Ürün kimliği olarakdash_consumable_2k
kullanın. Referans adı yalnızca App Store Connect'te kullanılır. Bu adıdash consumable 2k
olarak ayarlamanız yeterlidir.Kullanılabilirliği ayarlayın. Ürün, sanal alan kullanıcısının ülkesinde kullanılabilir olmalıdır.
Fiyatlandırma ekleyin ve fiyatı
$1.99
veya diğer para birimlerindeki eşdeğeri olarak ayarlayın.Satın alma işlemi için yerelleştirilmiş sürümlerinizi ekleyin. Açıklama olarak
2000 dashes fly out
ileSpring is in the air
satın alma işlemini çağırın.Yorum ekran görüntüsü ekleyin. Ürün incelemeye gönderilmediği sürece içerik önemli değildir ancak ürünün "Gönderilmeye hazır " durumunda olması gerekir. Bu durum, uygulama App Store'dan ürünleri getirdiğinde gereklidir.
dash_upgrade_3d
uygulamasını tüketilmeyen ürün olarak ayarlayın. Ürün kimliği olarakdash_upgrade_3d
kullanın. Referans adınıdash upgrade 3d
olarak ayarlayın. Açıklama olarakBrings your dash back to the future
ile3D Dash
satın alma işlemini çağırın. Fiyatı$0.99
olarak ayarlayın. Stok durumunu yapılandırın ve inceleme ekran görüntüsünüdash_consumable_2k
ürünü için yaptığınız gibi yükleyin.dash_subscription_doubler
aboneliğini otomatik yenilenen abonelik olarak ayarlayın. Aboneliklerle ilgili akış biraz farklıdır. Öncelikle bir abonelik grubu oluşturmanız gerekir. Aynı gruba birden fazla abonelik dahil olduğunda kullanıcılar aynı anda yalnızca birine abone olabilir ancak bu abonelikler arasında üst veya alt sürüme geçebilir. Bu grubasubscriptions
adını ver.Abonelik grubu için yerelleştirme ekleyin.
Ardından aboneliği oluşturursunuz. Referans adını
dash subscription doubler
, ürün kimliğini isedash_subscription_doubler
olarak ayarlayın.Ardından, 1 haftalık abonelik süresini ve yerelleştirmeleri seçin. Bu aboneliği
Jet Engine
adıyla veDoubles your clicks
açıklamasıyla adlandır. Fiyatı$0.49
olarak ayarlayın. Stok durumunu yapılandırın ve inceleme ekran görüntüsünüdash_consumable_2k
ürünü için yaptığınız gibi yükleyin.
Artık listelerde ürünleri görebilirsiniz:
5. Play Store'u ayarlama
App Store'da olduğu gibi Play Store için de 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ş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.
- İç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ı ayarlayın bölümündeki tüm görevleri tamamlayın. Burada, uygulamanızla ilgili bazı bilgiler (ör. içerik derecelendirmeleri ve ekran görüntüleri) sağlarsınız.
Uygulamayı imzalama
Uygulama içi satın alma işlemlerini test edebilmek için Google Play'e en az bir yapı yüklemeniz gerekir.
Bunun için yayın 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. Yoksa komut satırında aşağıdaki komutu çalıştırarak bir tane 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 saklar. Dosyayı başka bir yerde depolamak istiyorsanız -keystore
parametresine ilettiğiniz bağımsız değişkeni değiştirin. Keep
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'da imzalamayı yapılandırma
<your app dir>/android/app/build.gradle.kts
dosyasını düzenleyerek uygulamanız için imzalamayı yapılandırın.
android
bloğundan önce özellikler dosyanızdaki anahtar deposu bilgilerini ekleyin:
import java.util.Properties
import java.io.FileInputStream
plugins {
// omitted
}
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
// omitted
}
key.properties
dosyasını keystoreProperties
nesnesine yükleyin.
buildTypes
bloğunu şu şekilde güncelleyin:
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
Modülünüzün signingConfigs
dosyasındaki signingConfigs
bloğunu imzalama yapılandırma bilgileriyle yapılandırın:build.gradle.kts
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
Uygulamanızın yayın derlemeleri artık otomatik olarak imzalanacak.
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ğıdaki komutu çalıştırarak uygulamanızı oluşturabilirsiniz:
flutter build appbundle
Bu komut varsayılan olarak bir yayın derlemesi oluşturur ve çıkış <your app dir>/build/app/outputs/bundle/release/
konumunda bulunabilir.
Google Play Console'daki kontrol panelinden Test etme ve yayınlama > Test etme > Kapalı test'e gidip yeni bir kapalı test sürümü oluşturun.
Ardından, derleme komutuyla oluşturulan app-release.aab
uygulama paketini yükleyin.
Kaydet'i ve ardından Sürümü incele'yi tıklayın.
Son olarak, kapalı test sürümünü etkinleştirmek için Kapalı teste sunmayı başlat'ı 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ı, Google Play Console'da iki yere eklenmelidir:
- Belirli test kanalına (dahili test)
- Lisans test kullanıcısı olarak
Öncelikle test kullanıcısını dahili test kanalına ekleyin. Test edin ve yayınlayın > Test > Dahili test'e geri dönüp 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 test amaçlı uygulama içi satın alma işlemlerine 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
Şimdi 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
: Birçok kez satın alınabilen ve her satın alma işleminde kullanıcıya 2.000 Dash (uygulama içi para birimi) veren tüketilebilir bir satın alma işlemi.dash_upgrade_3d
: Yalnızca bir kez satın alınabilen ve kullanıcıya tıklayabileceği, görünüm olarak farklı bir Dash veren, tüketilemeyen bir "yükseltme" satın alma işlemi.dash_subscription_doubler
: Kullanıcıya abonelik süresi boyunca tıklama başına iki kat Dash hakkı veren abonelik.
Öncelikle tüketilebilir ve tüketilemez ürünleri 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 olarak gerekli tüm bilgileri girin. Ürün kimliğinin, kullanmayı düşündüğünüz kimlikle tam olarak eşleştiğinden emin olun.
- Kaydet'i tıklayın.
- Etkinleştir'i tıklayın.
- Tüketilmeyen "yükseltme" satın alma işlemi için bu süreci 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, kullanmayı düşündüğünüz kimlikle tam olarak eşleştiğinden emin olun.
- Kaydet'i tıklayın
Satın alma işlemleriniz artık Play Console'da ayarlanmış olmalıdır.
6. Firebase'i ayarlama
Bu codelab'de, kullanıcıların satın alma işlemlerini doğrulayıp izlemek için bir arka uç hizmeti kullanacaksınız.
Arka uç hizmeti kullanmanın çeşitli avantajları vardır:
- İşlemleri güvenli bir şekilde doğrulayabilirsiniz.
- Uygulama mağazalarındaki faturalandırma etkinliklerine yanıt verebilirsiniz.
- Satın alma işlemlerini bir veritabanında takip edebilirsiniz.
- Kullanıcılar, sistem saatini geri sararak uygulamanızı kandırıp premium özellikler elde edemez.
Arka uç hizmeti oluşturmanın birçok yolu olsa da bu işlem için Google'ın kendi Firebase'ini kullanarak Cloud Functions ve Firestore'u kullanacaksınız.
Arka uç yazma işlemi bu codelab'in kapsamı dışında kabul edilir. Bu nedenle, başlangıç kodu, temel satın alma işlemlerini gerçekleştiren bir Firebase projesi içerir.
Başlangıç uygulamasında Firebase eklentileri de bulunur.
Geriye kendi Firebase projenizi oluşturmak, hem uygulamayı hem de Firebase için arka ucu yapılandırmak ve son olarak arka ucu dağıtmak kalıyor.
Firebase projesi oluşturma
Firebase konsoluna gidip yeni bir Firebase projesi oluşturun. Bu örnekte projeye Dash Clicker adını verin.
Arka uç uygulamasında, satın alma işlemlerini belirli bir kullanıcıya bağladığınız için kimlik doğrulama yapmanız gerekir. Bunun için Firebase'in kimlik doğrulama modülünü Google ile oturum açma özelliğiyle birlikte kullanın.
- Firebase kontrol panelinde Authentication'a 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.
Cloud Firestore kurallarını şu şekilde 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
Flutter uygulamasına Firebase'i yüklemenin önerilen yolu FlutterFire CLI'yı kullanmaktır. Kurulum sayfasında açıklanan talimatları uygulayın.
flutterfire configure komutunu ç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 konusunda 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: Diğer adımlar
Firebase kontrol panelinde Proje genel bakışı'na gidin, Ayarlar'ı seçin ve Genel sekmesini tıklayın.
Uygulamalarınız bölümüne gidip 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 imzalama sertifikanızın karmasını alma
Flutter uygulama projenizin kök dizininde, dizini android/
klasörü olarak değiştirin ve imzalama raporu oluşturun.
cd android ./gradlew :app:signingReport
İmzalama anahtarlarının uzun bir listesi gösterilir. Hata ayıklama sertifikasının karma değerini aradığınız için Variant
ve Config
özellikleri debug
olarak ayarlanmış sertifikayı bulun. 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 hash değerini kopyalayın ve uygulama gönderme modal iletişim kutusundaki son alanı doldurun.
Son olarak, imzalama yapılandırmasını içerecek şekilde uygulamayı güncellemek için flutterfire configure
komutunu tekrar çalıştırın.
$ flutterfire configure ? You have an existing `firebase.json` file and possibly already configured your project for Firebase. Would you prefer to reuse the valus in your existing `firebase.json` file to configure your project? (y/n) › yes ✔ You have an existing `firebase.json` file and possibly already configured your project for Firebase. Would you prefer to reuse the values in your existing `firebase.json` file to configure your project? · yes
iOS için Firebase'i ayarlama: Diğer adımlar
ios/Runner.xcworkspace
dosyasını Xcode
ile açın. Dilerseniz tercih ettiğiniz IDE'yi de kullanabilirsiniz.
VSCode'da ios/
klasörünü sağ tıklayın ve ardından open in xcode
simgesini tıklayın.
Android Studio'da ios/
klasörünü sağ tıklayın, ardından flutter
ve open iOS module in Xcode
seçeneğini tıklayın.
iOS'te Google ile Oturum Açma'ya izin vermek için CFBundleURLTypes
yapılandırma seçeneğini derleme plist
dosyalarınıza ekleyin. (Daha fazla bilgi için google_sign_in
paketi dokümanlarına bakın.) Bu durumda dosya ios/Runner/Info.plist
.
Anahtar/değer çifti zaten eklenmiş ancak değerleri değiştirilmelidir:
REVERSED_CLIENT_ID
değerini, etrafındaki<string>..</string>
öğesi olmadanGoogleService-Info.plist
dosyasından alın.ios/Runner/Info.plist
dosyanızdaki değeriCFBundleURLTypes
anahtarı altında 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 güncellemelerini dinleme
Codelab'in bu bölümünde, uygulamayı ürün satın alma işlemine hazırlayacaksınız. 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,
bölümünde, iki sayfa içeren BottomNavigationBar
ile birlikte Scaffold
simgesine sahip MyHomePage
widget'ını bulun. Bu sayfada ayrıca DashCounter
, DashUpgrades,
ve DashPurchases
için üç Provider
oluşturulur. DashCounter
, mevcut Dash 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ının nesnesi ilk kez istendiğinde tanımlanır. Bu nesne, uygulama başladığında satın alma güncellemelerini doğrudan 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, // Add this line
),
Ayrıca InAppPurchaseConnection
öğesinin bir örneğine de ihtiyacınız vardır. Ancak uygulamanın test edilebilirliğini korumak için bağlantıyı taklit etmenin bir yolunu bulmanız gerekir. Bunu yapmak için testte geçersiz kılınabilecek bir örnek yöntemi oluşturun ve main.dart
'ya 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!;
}
}
Testi aşağıdaki şekilde güncelleyin:
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
içinde DashPurchasesChangeNotifier
koduna gidin. Şu anda, satın aldığınız hazır metinlere yalnızca DashCounter
ekleyebilirsiniz.
_subscription
(StreamSubscription<List<PurchaseDetails>> _subscription;
türünde) adlı bir yayın aboneliği özelliği, IAPConnection.instance,
ve içe aktarmalar ekleyin. Bu işlem sonucunda 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();
}
}
late
anahtar kelimesi, oluşturucuda _subscription
başlatıldığı için _subscription
öğesine eklenir. Bu proje, varsayılan olarak boş değer atanamayacak şekilde (NNBD) ayarlanmıştır. Bu nedenle, boş değer atanabilir olarak tanımlanmayan özelliklerin boş olmayan bir değeri olmalıdır. late
niteleyicisi, bu değeri tanımlamayı geciktirmenize olanak tanır.
Oluşturucuda purchaseUpdated
akışını alın ve akışı dinlemeye başlayın. dispose()
yönteminde yayın aboneliğini iptal edin.
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';
import '../main.dart';
import '../model/purchasable_product.dart';
import '../model/store_state.dart';
import 'dash_counter.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.
}
Uygulama artık satın alma güncellemelerini alıyor. Bu nedenle, 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 testleri çalıştırın.
$ flutter test 00:01 +1: All tests passed!
8. Satın alma işlemleri gerçekleştirme
Bu codelab'in bu bölümünde, mevcut sahte ürünleri satın alınabilir gerçek ürünlerle değiştireceksiniz. Bu ürünler mağazalardan yüklenir, listede gösterilir ve ürüne dokunulduğunda satın alınır.
Adapt PurchasableProduct
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,
içinde sahte satın alma işlemlerini kaldırıp boş bir listeyle değiştirin List<PurchasableProduct> products = [];
.
Kullanılabilir satın alma işlemlerini yükleme
Kullanıcının satın alma işlemi yapabilmesi için mağazadaki satın alma işlemlerini yükleyin. Öncelikle mağazanın kullanılabilir olup olmadığını kontrol edin. Mağaza kullanılamadığında storeState
ayarını notAvailable
olarak belirlemek kullanıcıya bir hata mesajı gösterir.
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, satın alınabilecek ürünleri yükleyin. Önceki Google Play ve App Store kurulumu göz önüne alındığında storeKeyConsumable
, storeKeySubscription,
ve storeKeyUpgrade
simgelerini görmeniz beklenir. Beklenen bir satın alma işlemi kullanılamadığında bu bilgileri konsola yazdırın. Bu bilgileri arka uç hizmetine de gönderebilirsiniz.
await iapConnection.queryProductDetails(ids)
yöntemi, bulunamayan kimlikleri ve 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
öğ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(); // Add this line
}
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ınabilir ürünleri gösterme
purchase_page.dart
dosyasını göz önünde bulundurun. PurchasePage
widget'ında, StoreState
değerine bağlı olarak _PurchasesLoading
, _PurchaseList,
veya _PurchasesNotAvailable,
gösterilir. Widget'ta, sonraki adımda kullanılacak olan 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 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 kullanılabilir ürünleri görebilirsiniz. Satın alma işlemlerinin ilgili konsollara girildikten sonra kullanılabilir hale gelmesi biraz zaman alabilir.
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 olmayan ürünlerden ayırmanız gerekir. Yükseltme ve abonelik ürünleri, tüketilmeyen ürünlerdir.
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
getter'ı güncelleyerek bu değişkene referans verin.
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şlemi tamamlandıktan sonra completePurchase
işlevini çağırmak önemlidir. Böylece mağaza, satın alma işleminin doğru şekilde tamamlandığı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 izleme ve doğrulama işlemine geçmeden önce bunu desteklemek için bir Dart arka ucu 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 proje genel bakışı
Bu projenin bazı bölümleri bu codelab'in kapsamı dışında kabul edildiğinden başlangıç koduna dahil edilmiştir. Başlamadan önce, işleri nasıl yapılandıracağınıza dair bir fikir edinmek için başlangıç kodunda bulunanları incelemeniz iyi bir fikir olabilir.
Bu arka uç kodu, makinenizde yerel olarak çalıştırılabilir. 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 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ına hizmet vermek için shelf
ve shelf_router
kullanır. Varsayılan olarak sunucu 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 zaten dahil olan bir bölüm, lib/iap_repository.dart
içindeki IapRepository
'dır. Firestore veya genel olarak veritabanlarıyla nasıl etkileşim kurulacağını öğrenmek bu codelab ile alakalı olmadığından başlangıç kodu, Firestore'da satın alma işlemleri oluşturmanız veya güncellemeniz için işlevlerin yanı sıra bu satın alma işlemlerine yönelik tüm sınıfları içerir.
Firebase erişimini ayarlama
Firebase Firestore'a erişmek için hizmet hesabı erişim anahtarına ihtiyacınız vardır. Firebase proje ayarlarını açıp Hizmet hesapları bölümüne giderek ve ardından Yeni özel anahtar oluştur'u seçerek oluşturabilirsiniz.
İndirilen JSON dosyasını assets/
klasörüne kopyalayın ve dosyanın adını service-account-firebase.json
olarak değiştirin.
Google Play erişimini ayarlama
Satın alma işlemlerini doğrulamak için Play Store'a erişmek üzere bu izinlere sahip bir hizmet hesabı oluşturmanız ve bu hesabın JSON kimlik bilgilerini indirmeniz gerekir.
- Google Cloud Console'da Google Play Android Developer API sayfasına gidin.
Google Play Console'un proje oluşturmanızı veya mevcut bir projeye bağlantı oluşturmanızı istemesi durumunda önce bu işlemi yapın, ardından bu sayfaya geri dönün.
- Ardından, Hizmet hesapları sayfasına gidip + Hizmet hesabı oluştur'u tıklayın.
- Hizmet hesabı adını girip Oluştur ve devam et'i tıklayın.
- Pub/Sub Abonesi rolünü seçin ve Bitti'yi tıklayın.
- Hesap oluşturulduktan sonra Anahtarları yönet'e gidin.
- Anahtar ekle > Yeni anahtar oluştur'u seçin.
- JSON anahtarı oluşturup indirin.
- İndirilen dosyayı
service-account-google-play.json,
olarak yeniden adlandırın veassets/
dizinine taşıyın. - Ardından, Play Console'da Kullanıcılar ve izinler sayfasına gidin.
- Yeni kullanıcıları davet et'i tıklayın ve daha önce oluşturulan hizmet hesabının e-posta adresini girin. E-postayı Hizmet hesapları sayfasındaki tabloda bulabilirsiniz.
- Uygulama için Finansal verileri görüntüleme ve Siparişleri ve abonelikleri yönetme izinlerini verin.
- Kullanıcı davet et'i tıklayın.
Yapmamız gereken bir işlem 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 bölümünde Genel > Uygulama bilgileri'ne gidin.
- Uygulamaya Özgü Paylaşılan Gizli Anahtar başlığı altında Yönet'i 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 aşağıdaki sabitlerin lib/constants.dart
dosyasında yapılandırıldığından emin olun:
androidPackageId
: Android'de kullanılan paket kimliği (ör.com.example.dashclicker
)appStoreSharedSecret
: Satın alma işlemini doğrulamak için App Store Connect'e erişmek üzere kullanılan paylaşılan gizli anahtar.bundleId
: iOS'te kullanılan paket kimliği (ör.com.example.dashclicker
)
Şimdilik diğer sabitleri yoksayabilirsiniz.
10. Satın alma işlemlerini doğrulama
Satın alma işlemlerini doğrulama için genel akış, iOS ve Android'de benzerdir.
Her iki mağazada da 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ç hizmetiniz 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 saklamayı seçebilir ve satın alma işleminin geçerli olup olmadığına dair uygulamaya yanıt verebilir.
Doğrulamayı kullanıcının cihazında çalışan uygulama yerine arka uç hizmetiyle mağazalarda yaparak kullanıcının, örneğin sistem saatini geri sararak premium özelliklere erişmesini engelleyebilirsiniz.
Flutter tarafını ayarlama
Kimlik doğrulama 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. Başlangıç projesinde kimlik doğrulama mantığının çoğu sizin için eklenmiştir. Yalnızca kullanıcının henüz oturum açmadığı durumlarda PurchasePage
öğesinin giriş düğmesini gösterdiğinden emin olmanız gerekir. Aşağıdaki kodu PurchasePage
öğesinin derleme yönteminin başına ekleyin:
lib/pages/purchase_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../logic/dash_purchases.dart';
import '../logic/firebase_notifier.dart'; // Add this import
import '../model/firebase_state.dart'; // And this import
import '../model/purchasable_product.dart';
import '../model/store_state.dart';
import '../repo/iap_repo.dart';
import 'login_page.dart'; // And this one as well
class PurchasePage extends StatelessWidget {
const PurchasePage({super.key});
@override
Widget build(BuildContext context) { // Update from here
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();
} // To here.
// ...
Uygulamadan uç nokta doğrulamasını çağırma
Uygulamada, bir http post çağrısı kullanarak Dart arka ucunuzdaki /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
simgesini 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
import 'dart:async';
import 'dart:convert'; // Add this import
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; // And this import
import 'package:in_app_purchase/in_app_purchase.dart';
import '../constants.dart';
import '../main.dart';
import '../model/purchasable_product.dart';
import '../model/store_state.dart';
import 'dash_counter.dart';
import 'firebase_notifier.dart'; // And this one
class DashPurchases extends ChangeNotifier {
DashCounter counter;
FirebaseNotifier firebaseNotifier; // Add this line
StoreState storeState = StoreState.loading;
late StreamSubscription<List<PurchaseDetails>> _subscription;
List<PurchasableProduct> products = [];
bool get beautifiedDash => _beautifiedDashUpgrade;
bool _beautifiedDashUpgrade = false;
final iapConnection = IAPConnection.instance;
DashPurchases(this.counter, this.firebaseNotifier) { // Update this line
final purchaseUpdated = iapConnection.purchaseStream;
_subscription = purchaseUpdated.listen(
_onPurchaseUpdate,
onDone: _updateStreamOnDone,
onError: _updateStreamOnError,
);
loadPurchases();
}
main.dart:
içinde DashPurchases
oluşturulurken firebaseNotifier
ekleyin
lib/main.dart
ChangeNotifierProvider<DashPurchases>(
create: (context) => DashPurchases(
context.read<DashCounter>(),
context.read<FirebaseNotifier>(),
),
lazy: false,
),
FirebaseNotifier'da User için bir alıcı ekleyin. Böylece, kullanıcı kimliğini satın alma doğrulama işlevine iletebilirsiniz.
lib/logic/firebase_notifier.dart
Future<FirebaseFirestore> get firestore async {
var isInitialized = await _isInitialized.future;
if (!isInitialized) {
throw Exception('Firebase is not initialized');
}
return FirebaseFirestore.instance;
}
User? get user => FirebaseAuth.instance.currentUser; // Add this line
Future<void> load() async {
// ...
_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, örneğin mağaza geçici olarak kullanılamadığında deneme aboneliği uygulamak için bunu daha ayrıntılı olarak belirtebilirsiniz. Ancak bu örnekte, satın alma işlemi başarıyla doğrulandığında satın alma işlemini 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(2000);
case storeKeyUpgrade:
_beautifiedDashUpgrade = true;
}
}
}
if (purchaseDetails.pendingCompletePurchase) {
await iapConnection.completePurchase(purchaseDetails);
}
}
Uygulamada artık 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ğrulama için arka ucu 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.
İşe, purchase_handler.dart
dosyasını lib/
klasörüne 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ı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. 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şleyicilerin 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ğırabilir ancak yine de ayrı uygulamalara sahip olabilirsiniz.
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 veya abonelik dışı olabilir.
lib/products.dart
enum ProductType { subscription, nonSubscription }
Son olarak, ürün listesi aynı dosyada 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 ile başlayın:
lib/google_play_purchase_handler.dart
oluşturun ve yazdığınız PurchaseHandler
'yi 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
döndürülür. Bunlara daha sonra değineceğiz.
Fark etmiş olabileceğiniz gibi, oluşturucu IapRepository
örneğini alır. Satın alma işleyici, satın alma işlemleriyle ilgili bilgileri daha sonra Firestore'da depolamak için bu örneği kullanır. Google Play ile iletişim kurmak için sağlanan AndroidPublisherApi
kullanılır.
Ardından, uygulama mağazası işleyicisi için de aynı işlemi yapın. lib/app_store_purchase_handler.dart
oluşturun ve PurchaseHandler
öğesini genişleten bir sınıfı tekrar 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,
}) async {
return true;
}
@override
Future<bool> handleSubscription({
required String userId,
required ProductData productData,
required String token,
}) async {
return true;
}
}
Mükemmel! Artık iki satın alma işleyiciniz var. Ardından, satın alma doğrulama API uç noktasını oluşturun.
Satın alma işleyicilerini kullanma
bin/server.dart
öğesini açın ve shelf_route
kullanarak bir API uç noktası oluşturun:
bin/server.dart
import 'dart:convert';
import 'package:firebase_backend_dart/helpers.dart';
import 'package:firebase_backend_dart/products.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.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');
}
}
Kod şu işlemleri 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 ayıklayın:
userId
: Giriş yapmış kullanıcının kimliğisource
: Kullanılan mağaza,app_store
veyagoogle_play
.productData
: Daha önce oluşturduğunuzproductDataMap
öğesinden 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, istemciye
Response.ok
döndürür. - Doğrulama başarısız olursa yöntem istemciye
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 aldığınız hizmet hesabı anahtarlarını yüklemeniz ve Android Publisher API ile Firebase Firestore API dahil olmak üzere farklı hizmetlere erişimi yapılandırmanız gerekir. Ardından, farklı bağımlılıklarla iki satın alma işleyicisi oluşturun:
bin/server.dart
import 'dart:convert';
import 'dart:io'; // new
import 'package:firebase_backend_dart/app_store_purchase_handler.dart'; // new
import 'package:firebase_backend_dart/google_play_purchase_handler.dart'; // new
import 'package:firebase_backend_dart/helpers.dart';
import 'package:firebase_backend_dart/iap_repository.dart'; // new
import 'package:firebase_backend_dart/products.dart';
import 'package:firebase_backend_dart/purchase_handler.dart'; // new
import 'package:googleapis/androidpublisher/v3.dart' as ap; // new
import 'package:googleapis/firestore/v1.dart' as fs; // new
import 'package:googleapis_auth/auth_io.dart' as auth; // new
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.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
Ardından, Google Play satın alma işleyicisini uygulamaya devam edin.
Google, satın alma işlemlerini doğrulamanız gereken API'lerle etkileşim kurmak için Dart paketleri sunar. Bu değişkenleri 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
/// Handle non-subscription purchases (one time purchases).
///
/// Retrieves the purchase status from Google Play and updates
/// the Firestore Database accordingly.
@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 don't 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 de 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 don't 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
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 artık doğrulanmış ve veritabanında saklanmış olmalıdı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'da satın alma işlemlerini doğrulamak için app_store_server_sdk
adlı üçüncü taraf Dart paketi, süreci kolaylaştırır.
ITunesApi
örneğini oluşturarak başlayın. Hata ayıklamayı 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),
);
Google Play API'lerinin aksine, App Store artık hem abonelikler hem de abonelik dışı işlemler için aynı API uç noktalarını kullanıyor. Bu nedenle, her iki işleyici için de aynı mantığı kullanabilirsiniz. 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 {
// See next step
}
Ş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 satın alma işlemleriniz artık doğrulanmalı ve veritabanında saklanmalıdır.
Arka ucu çalıştırma
Bu noktada, /verifypurchase
uç noktasını sunmak 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şlemlerini takip etme
Kullanıcılarınızın satın alma işlemlerini izlemenin önerilen yolu arka uç hizmetidir. Bunun nedeni, arka ucunuzun mağazadaki etkinliklere yanıt verebilmesi ve bu nedenle önbelleğe alma nedeniyle güncel olmayan bilgilerle karşılaşma olasılığının daha düşük olmasının yanı sıra kurcalanmaya karşı daha az duyarlı olmasıdır.
Öncelikle, oluşturduğunuz Dart arka ucuyla mağaza etkinliklerinin arka uçta 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ç sisteminizi bilgilendirebilir. Satın alma işlemlerinin veritabanınızda güncel kalması için bu etkinlikleri arka ucunuzda 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, Cloud Pub/Sub konusu olarak adlandırdığı bir yöntemle faturalandırma etkinlikleri sağlar. Bunlar, iletilerin yayınlanabileceği ve tüketilebileceği ileti kuyruklarıdır.
Bu işlev Google Play'e özgü olduğundan, işlevi GooglePlayPurchaseHandler
bölümüne dahil edersiniz.
lib/google_play_purchase_handler.dart
dosyasını 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 oluşturucusunu 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
, her on saniyede bir _pullMessageFromPubSub
yöntemini çağıracak şekilde yapılandırılır. Süreyi kendi tercihinize göre ayarlayabilirsiniz.
Ardından, _pullMessageFromPubSub
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/$googleCloudProjectId/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/$googleCloudProjectId/subscriptions/$googlePlayPubsubBillingTopic-sub';
await pubsubApi.projects.subscriptions.acknowledge(
request,
subscriptionName,
);
}
Yeni eklediğiniz kod, Google Cloud'daki Pub/Sub konusuyla her on saniyede bir iletişim kurar ve yeni mesajlar ister. Ardından, her iletiyi _processMessage
yöntemiyle işler.
Bu yöntem, gelen mesajların kodunu çözer ve her satın alma işlemiyle (hem abonelikler hem de abonelik dışı işlemler) ilgili güncellenmiş bilgileri alır. Gerekirse mevcut handleSubscription
veya handleNonSubscription
çağrılı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 bilgileri yapılandırmasına ekleyin:
bin/server.dart
import 'package:googleapis/pubsub/v1.dart' as pubsub; // Add this import
final clientGooglePlay = await auth
.clientViaServiceAccount(clientCredentialsGooglePlay, [
ap.AndroidPublisherApi.androidpublisherScope,
pubsub.PubsubApi.cloudPlatformScope, // Add this line
]);
Ardından PubsubApi örneğini oluşturun:
bin/server.dart
final pubsubApi = pubsub.PubsubApi(clientGooglePlay);
Son olarak, GooglePlayPurchaseHandler
oluşturucusuna iletin:
bin/server.dart
return {
'google_play': GooglePlayPurchaseHandler(
androidPublisher,
iapRepository,
pubsubApi, // Add this line
),
'app_store': AppStorePurchaseHandler(
iapRepository,
),
};
Google Play kurulumu
Faturalandırma etkinliklerini pub/sub konusundan kullanmak için kodu yazdınız ancak pub/sub konusunu oluşturmadınız ve herhangi bir faturalandırma etkinliği yayınlamıyorsunuz. Bu özelliği ayarlamanın zamanı geldi.
Öncelikle bir Pub/Sub konusu oluşturun:
constants.dart
içindekigoogleCloudProjectId
değerini Google Cloud projenizin kimliği olarak ayarlayın.- 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.dart
içindegooglePlayPubsubBillingTopic
için ayarlanan değerle aynı adı verin. Bu durumda,play_billing
olarak adlandırın. Başka bir seçenek belirlersenizconstants.dart
bölümünü güncellediğinizden emin olun. Konuyu oluşturun. - Yayın/abone konuları listenizde, az önce oluşturduğunuz konunun üç dikey noktasını ve İzinleri görüntüle'yi tıklayın.
- Sağdaki kenar çubuğunda Add principal'ı (Asıl ekle) seçin.
- Burada
google-play-developer-notifications@system.gserviceaccount.com
hesabını ekleyin ve Pub/Sub Yayıncısı rolünü 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.
- Aşağı kaydırıp Para kazanma > Para kazanma kurulumu'na gidin.
- Konuyu eksiksiz bir şekilde doldurun ve değişikliklerinizi kaydedin.
Tüm Google Play faturalandırma etkinlikleri artık 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üncelleme işlemeyi uygulamanı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'u uygulamaktır. Bu codelab'de bulacağınız 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ç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çırdıysanız veya abonelik durumunu iki kez kontrol etmeniz gerekiyorsa 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; // Add this member
AppStorePurchaseHandler(
this.iapRepository,
this.appStoreServerAPI, // And this parameter
);
Oluşturucuyu, _pullStatus
yöntemini çağıracak bir zamanlayıcı ekleyecek şekilde değiştirin. Bu zamanlayıcı, _pullStatus
yöntemini her 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
/// Request the App Store for the latest subscription status.
/// Updates all App Store subscriptions in the database.
/// NOTE: This code only handles when a subscription expires as example.
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 durumu istenir.
- İlgili abonelik satın alma işleminin son işlemini alır.
- Son kullanma tarihini kontrol eder.
- Abonelik durumu Firestore'da güncellenir. Süresi dolmuş 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
import 'package:app_store_server_sdk/app_store_server_sdk.dart'; // Add this import
import 'package:firebase_backend_dart/constants.dart'; // And this one.
// 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, // Add this argument
),
};
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.
- Entegrasyonlar > Anahtarlar > Uygulama İçi Satın Alma'ya gidin.
- Yeni bir tane eklemek için "artı" simgesine dokunun.
- Anahtara "Codelab anahtarı" gibi bir ad verin.
- Anahtarı içeren p8 dosyasını indirin.
- Bu dosyayı,
SubscriptionKey.p8
adıyla öğeler klasörüne kopyalayın. - Yeni oluşturulan anahtardan anahtar kimliğini kopyalayın ve
lib/constants.dart
dosyasındaappStoreKeyId
sabiti olarak ayarlayın. - Anahtar listesinin en üstündeki veren kimliğini kopyalayın ve
lib/constants.dart
dosyasındaappStoreIssuerId
sabit olarak ayarlayın.
Cihazdaki satın alma işlemlerini izleme
Satın alma işlemlerinizi izlemenin en güvenli yolu sunucu tarafıdır. Bunun nedeni, istemcinin güvenliğinin sağlanmasının zor olmasıdır. Ancak bilgileri istemciye geri gönderecek bir yolunuz olmalıdır. Böylece uygulama, abonelik durumu bilgileri üzerinde işlem yapabilir. Satın alma işlemlerini Firestore'da depolayarak verileri istemciyle senkronize edebilir ve otomatik olarak güncel tutabilirsiniz.
Uygulamaya, kullanıcının List<PastPurchase> purchases
içindeki tüm satın alma verilerini içeren Firestore deposu olan IAPRepo'yu zaten eklediniz. Depoda ayrıca, süresi dolmamış bir durumla productId storeKeySubscription
satın alımı olduğunda doğru olan hasActiveSubscription,
da bulunur. Kullanıcı giriş yapmadığında liste boştur.
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((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. Bu nedenle, iapRepo
öğesini sınıfa özellik olarak ekleyin ve oluşturucuda iapRepo
öğesini atayın. Ardından, oluşturucuda doğrudan bir dinleyici ekleyin ve dispose()
yönteminde dinleyiciyi kaldırın. Başlangıçta, işleyici yalnızca boş bir işlev olabilir. IAPRepo
bir ChangeNotifier
olduğu ve Firestore'daki satın alma işlemleri her değiştiğinde notifyListeners()
işlevini çağırdığınız için satın alınan ürünler değiştiğinde purchasesUpdate()
yöntemi her zaman çağrılır.
lib/logic/dash_purchases.dart
import '../repo/iap_repo.dart'; // Add this import
class DashPurchases extends ChangeNotifier {
DashCounter counter;
FirebaseNotifier firebaseNotifier;
StoreState storeState = StoreState.loading;
late StreamSubscription<List<PurchaseDetails>> _subscription;
List<PurchasableProduct> products = [];
IAPRepo iapRepo; // Add this line
bool get beautifiedDash => _beautifiedDashUpgrade;
bool _beautifiedDashUpgrade = false;
final iapConnection = IAPConnection.instance;
// Add this.iapRepo as a parameter
DashPurchases(this.counter, this.firebaseNotifier, this.iapRepo) {
final purchaseUpdated = iapConnection.purchaseStream;
_subscription = purchaseUpdated.listen(
_onPurchaseUpdate,
onDone: _updateStreamOnDone,
onError: _updateStreamOnError,
);
iapRepo.addListener(purchasesUpdate);
loadPurchases();
}
Future<void> loadPurchases() async {
// Elided.
}
@override
void dispose() {
_subscription.cancel();
iapRepo.removeListener(purchasesUpdate); // Add this line
super.dispose();
}
void purchasesUpdate() {
//TODO manage updates
}
Ardından, IAPRepo
öğesini main.dart.
içindeki oluşturucuya sağlayın. context.read
öğesi Provider
içinde zaten 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>(), // Add this line
),
lazy: false,
),
Ardından, purchaseUpdate()
işlevinin kodunu yazın. dash_counter.dart,
, applyPaidMultiplier
ve removePaidMultiplier
yöntemlerinde çarpan sırasıyla 10 veya 1 olarak ayarlanır. 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 ürünün zaten etkin olduğunu gösterebilirsiniz. Yükseltme satın alınıp alınmadığına bağlı olarak _beautifiedDashUpgrade
özelliğini 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 olmasını ve uygulamayla senkronize edilmesini sağladınız. Uygulama buna göre hareket eder ve abonelik ile yükseltme özelliklerini Dash tıklama oyununuza uygular.
12. İşlem tamamlandı
Tebrikler!!! Codelab'i tamamlamış olmanız gerekir. Bu codelab'in tamamlanmış kodunu complete klasöründe bulabilirsiniz.
Daha fazla bilgi edinmek için diğer Flutter codelab'lerini deneyin.