Uygulama içi satın alma işlemlerini Flutter uygulamanıza ekleme

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:

  1. 2.000 Dash'i tek seferde satın almak için tekrarlanabilir bir satın alma seçeneği.
  2. Eski stil kontrol panelini modern stil kontrol paneline dönüştürmek için tek seferlik yükseltme satın alma işlemi.
  3. 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.

300123416ebc8dc1.png 7145d0fffe6ea741.png 646317a79be08214.png

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

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.

a9fbac80a31e28e0.png

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.

812f919d965c649a.jpeg

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.

e320a49ff2068ac2.png

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.

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.

11db9fca823e7608.png

Ü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.

74c73197472c9aec.png

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.

4a100bbb8cafdbbf.jpeg

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.

55d7e592d9a3fc7b.png

Uygulama kimliklerini seçme

13f125598b72ca77.png

Uygulama Seçin

41ac4c13404e2526.png

Bir açıklama girin ve paket kimliğini, daha önce Xcode'da ayarlanan paket kimliğiyle eşleşecek şekilde ayarlayın.

9d2c940ad80deeef.png

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.

10509b17fbf031bd.png

5b7c0bb684ef52c7.png

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.

2ba0f599bcac9b36.png

Artık iPhone'unuzda Ayarlar > Geliştirici > Sandbox Apple Hesabı'na giderek sandbox kullanıcınızı ayarlayabilirsiniz.

74a545210b282ad8.png eaa67752f2350f74.png

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.

a118161fac83815a.png

Uygulama İçi Satın Alma İşlemleri'ne gidin.

Uygulama içi satın alma işlemlerinizi belirtilen kimliklerle oluşturun:

  1. dash_consumable_2k uygulamasını Tüketilebilir Ürün olarak ayarlayın. Ürün kimliği olarak dash_consumable_2k kullanın. Referans adı yalnızca App Store Connect'te kullanılır. Bu adı dash consumable 2k olarak ayarlamanız yeterlidir. 1f8527fc03902099.png Kullanılabilirliği ayarlayın. Ürün, sanal alan kullanıcısının ülkesinde kullanılabilir olmalıdır. bd6b2ce2d9314e6e.png Fiyatlandırma ekleyin ve fiyatı $1.99 veya diğer para birimlerindeki eşdeğeri olarak ayarlayın. 926b03544ae044c4.png Satın alma işlemi için yerelleştirilmiş sürümlerinizi ekleyin. Açıklama olarak 2000 dashes fly out ile Spring is in the air satın alma işlemini çağırın. e26dd4f966dcfece.png 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. 25171bfd6f3a033a.png
  2. dash_upgrade_3d uygulamasını tüketilmeyen ürün olarak ayarlayın. Ürün kimliği olarak dash_upgrade_3d kullanın. Referans adını dash upgrade 3d olarak ayarlayın. Açıklama olarak Brings your dash back to the future ile 3D 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. 83878759f32a7d4a.png
  3. 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 gruba subscriptions adını ver. 393a44b09f3cd8bf.png Abonelik grubu için yerelleştirme ekleyin. 595aa910776349bd.png Ardından aboneliği oluşturursunuz. Referans adını dash subscription doubler, ürün kimliğini ise dash_subscription_doubler olarak ayarlayın. 7bfff7bbe11c8eec.png Ardından, 1 haftalık abonelik süresini ve yerelleştirmeleri seçin. Bu aboneliği Jet Engine adıyla ve Doubles 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. 44d18e02b926a334.png

Artık listelerde ürünleri görebilirsiniz:

17f242b5c1426b79.png d71da951f595054a.png

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:

  1. Play Console'u açın.
  2. Tüm uygulamalar > Uygulama oluştur'u seçin.
  3. 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.
  4. Uygulamanızın oyun olduğunu belirtin. Bunu daha sonra değiştirebilirsiniz.
  5. Uygulamanızın ücretsiz mi yoksa ücretli mi olduğunu belirtin.
  6. İçerik kuralları ve ABD ihracat yasaları beyanlarını doldurun.
  7. 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. 13845badcf9bc1db.png

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:

  1. Belirli test kanalına (dahili test)
  2. 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.

a0d0394e85128f84.png

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:

  1. Google Play Console'un Tüm uygulamalar görünümüne geri dönün.
  2. Ayarlar > Lisans testi'ne gidin.
  3. Uygulama içi satın alma işlemlerini test edebilmesi gereken test kullanıcılarının e-posta adreslerini ekleyin.
  4. Lisans yanıtı'nı RESPOND_NORMALLY olarak ayarlayın.
  5. Değişiklikleri Kaydet'i tıklayın.

a1a0f9d3e55ea8da.png

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.

  1. Google Play Console'a gidip uygulamanızı seçin.
  2. Para kazanma > Ürünler > Uygulama içi ürünler'e gidin.
  3. Ürün oluştur'u tıklayın.c8d66e32f57dee21.png
  4. Ü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.
  5. Kaydet'i tıklayın.
  6. Etkinleştir'i tıklayın.
  7. Tüketilmeyen "yükseltme" satın alma işlemi için bu süreci tekrarlayın.

Ardından aboneliği ekleyin:

  1. Google Play Console'a gidip uygulamanızı seçin.
  2. Para kazanma > Ürünler > Abonelikler'e gidin.
  3. Abonelik oluştur'u tıklayın.32a6a9eefdb71dd0.png
  4. 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.
  5. 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.

  1. Firebase kontrol panelinde Authentication'a gidin ve gerekirse etkinleştirin.
  2. Oturum açma yöntemi sekmesine gidin ve Google oturum açma sağlayıcısını etkinleştirin.

fe2e0933d6810888.png

Firebase'in Firestore veritabanını da kullanacağınız için bunu da etkinleştirin.

d02d641821c71e2c.png

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.

b22d46a759c0c834.png

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:

  1. REVERSED_CLIENT_ID değerini, etrafındaki <string>..</string> öğesi olmadan GoogleService-Info.plist dosyasından alın.
  2. ios/Runner/Info.plist dosyanızdaki değeri CFBundleURLTypes 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.

ca1a9f97c21e552d.png

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:

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.

27590fc77ae94ad4.png

İ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.

  1. Google Cloud Console'da Google Play Android Developer API sayfasına gidin. 629f0bd8e6b50be8.png 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.
  2. Ardından, Hizmet hesapları sayfasına gidip + Hizmet hesabı oluştur'u tıklayın. 8dc97e3b1262328a.png
  3. Hizmet hesabı adını girip Oluştur ve devam et'i tıklayın. 4fe8106af85ce75f.png
  4. Pub/Sub Abonesi rolünü seçin ve Bitti'yi tıklayın. a5b6fa6ea8ee22d.png
  5. Hesap oluşturulduktan sonra Anahtarları yönet'e gidin. eb36da2c1ad6dd06.png
  6. Anahtar ekle > Yeni anahtar oluştur'u seçin. e92db9557a28a479.png
  7. JSON anahtarı oluşturup indirin. 711d04f2f4176333.png
  8. İndirilen dosyayı service-account-google-play.json, olarak yeniden adlandırın ve assets/ dizinine taşıyın.
  9. Ardından, Play Console'da Kullanıcılar ve izinler sayfasına gidin.28fffbfc35b45f97.png
  10. 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.e3310cc077f397d.png
  11. Uygulama için Finansal verileri görüntüleme ve Siparişleri ve abonelikleri yönetme izinlerini verin. a3b8cf2b660d1900.png
  12. 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:

  1. App Store Connect'i açın.
  2. Uygulamalarım'a gidip uygulamanızı seçin.
  3. Kenar çubuğu gezinme bölümünde Genel > Uygulama bilgileri'ne gidin.
  4. Uygulamaya Özgü Paylaşılan Gizli Anahtar başlığı altında Yönet'i tıklayın. ad419782c5fbacb2.png
  5. Yeni bir gizli anahtar oluşturun ve kopyalayın. b5b72a357459b0e5.png
  6. lib/constants.dart, dosyasını açın ve appStoreSharedSecret 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.

be50c207c5a2a519.png

İş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:

  1. Daha önce oluşturduğunuz uygulamadan çağrılacak bir POST uç noktası tanımlayın.
  2. JSON yükünün kodunu çözün ve aşağıdaki bilgileri ayıklayın:
    1. userId: Giriş yapmış kullanıcının kimliği
    2. source: Kullanılan mağaza, app_store veya google_play.
    3. productData: Daha önce oluşturduğunuz productDataMap öğesinden alınır.
    4. token: Mağazalara gönderilecek doğrulama verilerini içerir.
  3. Kaynağa bağlı olarak GooglePlayPurchaseHandler veya AppStorePurchaseHandler için verifyPurchase yöntemini çağırın.
  4. Doğrulama başarılı olursa yöntem, istemciye Response.ok döndürür.
  5. 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:

  1. constants.dart içindeki googleCloudProjectId değerini Google Cloud projenizin kimliği olarak ayarlayın.
  2. Google Cloud Console'da Cloud Pub/Sub sayfasını ziyaret edin.
  3. Firebase projenizde olduğunuzdan emin olun ve + Konu Oluştur'u tıklayın. d5ebf6897a0a8bf5.png
  4. Yeni konuya, constants.dart içinde googlePlayPubsubBillingTopic için ayarlanan değerle aynı adı verin. Bu durumda, play_billing olarak adlandırın. Başka bir seçenek belirlerseniz constants.dart bölümünü güncellediğinizden emin olun. Konuyu oluşturun. 20d690fc543c4212.png
  5. Yayın/abone konuları listenizde, az önce oluşturduğunuz konunun üç dikey noktasını ve İzinleri görüntüle'yi tıklayın. ea03308190609fb.png
  6. Sağdaki kenar çubuğunda Add principal'ı (Asıl ekle) seçin.
  7. Burada google-play-developer-notifications@system.gserviceaccount.com hesabını ekleyin ve Pub/Sub Yayıncısı rolünü verin. 55631ec0549215bc.png
  8. İzin değişikliklerini kaydedin.
  9. Yeni oluşturduğunuz konunun Konu adını kopyalayın.
  10. Play Console'u tekrar açın ve Tüm Uygulamalar listesinden uygulamanızı seçin.
  11. Aşağı kaydırıp Para kazanma > Para kazanma kurulumu'na gidin.
  12. Konuyu eksiksiz bir şekilde doldurun ve değişikliklerinizi kaydedin. 7e5e875dc6ce5d54.png

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:

  1. IapRepository'yi kullanarak Firestore'dan etkin aboneliklerin listesini alır.
  2. Her sipariş için App Store Server API'den abonelik durumu istenir.
  3. İlgili abonelik satın alma işleminin son işlemini alır.
  4. Son kullanma tarihini kontrol eder.
  5. 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:

  1. App Store Connect'e giriş yapın ve Kullanıcılar ve Erişim'i seçin.
  2. Entegrasyonlar > Anahtarlar > Uygulama İçi Satın Alma'ya gidin.
  3. Yeni bir tane eklemek için "artı" simgesine dokunun.
  4. Anahtara "Codelab anahtarı" gibi bir ad verin.
  5. Anahtarı içeren p8 dosyasını indirin.
  6. Bu dosyayı, SubscriptionKey.p8 adıyla öğeler klasörüne kopyalayın.
  7. Yeni oluşturulan anahtardan anahtar kimliğini kopyalayın ve lib/constants.dart dosyasında appStoreKeyId sabiti olarak ayarlayın.
  8. Anahtar listesinin en üstündeki veren kimliğini kopyalayın ve lib/constants.dart dosyasında appStoreIssuerId sabit olarak ayarlayın.

9540ea9ada3da151.png

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 android_studio_folder.png complete klasöründe bulabilirsiniz.

Daha fazla bilgi edinmek için diğer Flutter codelab'lerini deneyin.