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

1. Giriş

Son Güncelleme: 11.07.2023

Flutter uygulamasına uygulama içi satın alma özelliği eklemek için Uygulama ve Play mağazalarının doğru şekilde ayarlanması, satın alma işleminin doğrulanması ve abonelik ayrıcalıkları gibi gerekli izinlerin verilmesi gerekir.

Bu codelab'de, bir uygulamaya (sizin için sağlanır) üç tür uygulama içi satın alma işlemi ekleyecek ve bu satın alma işlemlerini Firebase için Dart arka ucu kullanarak doğrulayacaksınız. Sağlanan uygulama olan Dash Clicker, para birimi olarak Dash maskotunun kullanıldığı bir oyun içeriyor. Aşağıdaki satın alma seçeneklerini eklersiniz:

  1. Tek seferde 2.000 kısa çizgi karşılığında tekrarlanabilir satın alma seçeneği.
  2. Eski tarz Dash'i modern tarzda bir Dash'e 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 kısa çizgi sunar. Bunlar doğrudan kullanıcı tarafından kullanılabilir ve birçok kez satın alınabilir. Doğrudan tüketildiği ve birden fazla kez tüketildiği için buna tüketim öğesi denir.

İkinci seçenek, Kısa çizgiyi daha güzel bir Kısa çizgiye yükseltir. Yalnızca bir kez satın alınması gerekir ve süresiz olarak kullanılabilir. Bu tür bir satın alma işlemi, uygulama tarafından kullanılamayan ancak sonsuza kadar geçerli olduğu için tüketilemez olarak adlandırılır.

Üçüncü ve son satın alma seçeneği ise aboneliktir. Abonelik etkin durumdayken kullanıcı kısa çizgileri daha hızlı alır, ancak abonelik için ödeme yapmayı bıraktığında avantajlar da ortadan kalkar.

Sizin için de sağlanan arka uç hizmeti bir Dart uygulaması olarak çalışır, satın alma işlemlerinin yapıldığını doğrular ve bunları Firestore kullanarak depolar. Firestore, işlemi kolaylaştırmak için kullanılır ancak üretim uygulamanızda herhangi bir arka uç hizmetini kullanabilirsiniz.

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

Neler oluşturacaksınız?

  • Uygulamayı, tüketilebilir satın alma işlemlerini ve abonelikleri destekleyecek şekilde genişleteceksiniz.
  • Satın alınan öğeleri doğrulamak ve depolamak için bir Dart arka uç uygulaması da genişleteceksiniz.

Öğrenecekleriniz

  • App Store ve Play Store'u satın alınabilir ürünlerle yapılandırma.
  • Satın alma işlemlerini doğrulamak ve bunları Firestore'da depolamak için mağazalarla nasıl iletişim kuracağınız.
  • Uygulamanızda satın alma işlemlerini yönetme.

Gerekenler

  • Android Studio 4.1 veya sonraki sürümler
  • Xcode 12 veya üzeri (iOS geliştirme için)
  • Flutter SDK'sı

2. Geliştirme ortamını ayarlama

Bu codelab'i başlatmak için kodu indirip iOS için paket tanımlayıcısını ve Android için paket adını değiştirin.

Kodu indirme

GitHub deposunu komut satırından klonlamak için şu komutu kullanın:

git clone https://github.com/flutter/codelabs.git flutter-codelabs

Alternatif olarak, GitHub'ın cli aracı yüklüyse şu komutu kullanın:

gh repo clone flutter/codelabs flutter-codelabs

Örnek kod, codelab'ler koleksiyonunun kodunu içeren flutter-codelabs dizinine klonlanır. Bu codelab'in kodu flutter-codelabs/in_app_purchases dilindedir.

flutter-codelabs/in_app_purchases altındaki dizin yapısı, adlandırılmış her adımın sonunda olmanız gereken bilgilerin bir anlık görüntüsünü içerir. Başlangıç kodu 0. adımdadır, bu nedenle eşleşen dosyaları bulmak çok kolay:

cd flutter-codelabs/in_app_purchases/step_00

İleri gitmek veya bir adımdan sonra bir öğenin nasıl görünmesi gerektiğini görmek isterseniz, ilgilendiğiniz adımın adını taşıyan dizine bakın. Son adımın kodu complete klasörünün altında.

Başlangıç projesini oluşturma

En sevdiğiniz IDE'de step_00 cihazındaki başlangıç projesini açın. Ekran görüntüleri için Android Studio'yu kullandık. Ancak Visual Studio Code da mükemmel bir seçenek. Her iki düzenleyicide de en son Dart ve Flutter eklentilerinin yüklendiğinden emin olun.

Üreteceğiniz uygulamaların, hangi ürünlerin hangi fiyattan mevcut olduğunu öğrenmek için App Store ve Play Store ile iletişim kurması gerekir. Her uygulama benzersiz bir kimlikle tanımlanır. iOS App Store'da bu, paket kimliği, Android Play Store'da ise uygulama kimliğidir. Bu tanımlayıcılar genellikle ters alan adı gösterimi kullanılarak yapılır. Örneğin, flutter.dev için bir uygulama içi satın alma uygulaması yaparken dev.flutter.inapppurchase kullanırız. Uygulamanız için bir tanımlayıcı düşünün. Şimdi bu tanımlayıcıyı proje ayarlarında belirleyeceksiniz.

İlk olarak iOS için paket tanımlayıcısını ayarlayın.

Proje Android Studio'da açıkken iOS klasörünü sağ tıklayın, Flutter'ı tıklayın ve Xcode uygulamasında modülü açın.

942772eb9a73bfaa.png

Xcode'un klasör yapısında Runner projesi en üstte, Flutter, Runner ve Products hedefleri ise Runner projesinin altındadır. Proje ayarlarınızı düzenlemek için Çalıştırıcı'yı çift tıklayın ve ardından İmzalama ve Özellikler. Ekibinizi ayarlamak için Ekip alanına az önce seçtiğiniz paket kimliğini girin.

812f919d965c649a.jpeg

Artık Xcode'u kapatabilir ve Android yapılandırmasını tamamlamak üzere Android Studio'ya geri dönebilirsiniz. Bunu yapmak için android/app, altındaki build.gradle dosyasını açın ve applicationId öğenizi (aşağıdaki ekran görüntüsünde 37. satırda bulunan) iOS paket tanımlayıcısıyla aynı olan uygulama kimliğiyle değiştirin. iOS ve Android mağazalarının kimliklerinin aynı olması gerekmediğini unutmayın. Ancak kimlikleri aynı tutmak hataya daha az açık olduğundan bu codelab'de aynı tanımlayıcıları da kullanacağız.

5c4733ac560ae8c2.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

pubspec'inizdeki bağımlılıklara in_app_purchase ekleyerek pubspec'e in_app_purchase ekleyin:

$ cd app
$ flutter pub add in_app_purchase

pubspec.yaml

dependencies:
  ..
  cloud_firestore: ^4.0.3
  firebase_auth: ^4.2.2
  firebase_core: ^2.5.0
  google_sign_in: ^6.0.1
  http: ^0.13.4
  in_app_purchase: ^3.0.1
  intl: ^0.18.0
  provider: ^6.0.2
  ..

Paketi indirmek için pub get'i tıklayın veya komut satırında flutter pub get komutunu çalıştırın.

4. App Store'u kurma

Uygulama içi satın alma işlemlerini ayarlamak ve iOS'te test etmek için App Store'da yeni bir uygulama oluşturmanız ve orada satın alınabilir ürünler oluşturmanız gerekir. Herhangi bir içerik yayınlamanız veya uygulamayı incelenmesi için Apple'a göndermeniz gerekmez. Bu işlem için geliştirici hesabınızın olması gerekir. Hesabınız yoksa Apple geliştirici programına kaydolun.

Uygulama içi satın alma işlemlerini kullanabilmeniz için App Store Connect'te ücretli uygulamalarla ilgili etkin bir sözleşmeniz de olması gerekir. https://appstoreconnect.apple.com/ adresine gidip Sözleşmeler, Vergi ve Bankacılık'ı tıklayın.

6e373780e5e24a6f.png

Ücretsiz ve ücretli uygulamalar için sözleşmeler burada gösterilir. Ücretsiz uygulamaların durumu etkin olmalı, ücretli uygulamaların durumu ise yeni olmalıdır. Şartları görüntülediğinizden, kabul ettiğinizden ve gerekli tüm bilgileri girdiğinizden emin olun.

74c73197472c9aec.png

Her şey doğru şekilde ayarlandığında ücretli uygulamalar için durum etkinleştirilir. Etkin bir sözleşme olmadan uygulama içi satın alma işlemlerini deneyemeyeceğiniz için bu çok önemlidir.

4a100bbb8cafdbbf.jpeg

Uygulama kimliğini kaydet

Apple geliştirici portalında yeni bir tanımlayıcı oluşturun.

55d7e592d9a3fc7b.png

Uygulama kimliklerini seçin

13f125598b72ca77.png

Uygulama Seçin

41ac4c13404e2526.png

Bir açıklama girin ve paket kimliğini, paket kimliği ile XCode'da daha önce ayarlanan değerle eşleşecek şekilde ayarlayın.

9d2c940ad80deeef.png

Yeni bir uygulama kimliğinin nasıl oluşturulacağı hakkında daha fazla bilgi için Geliştirici Hesabı Yardımı'na bakın .

Yeni uygulama oluşturma

Benzersiz paket tanımlayıcınızı kullanarak App Store Connect'te yeni bir uygulama oluşturun.

10509b17fbf031bd.png

5b7c0bb684ef52c7.png

Yeni bir uygulama oluşturma ve sözleşmeleri yönetme hakkında daha fazla yardım için App Store Connect yardımına bakın.

Uygulama içi satın alma işlemlerini test etmek için bir korumalı alan test kullanıcısı gerekir. Bu test kullanıcısı iTunes'a bağlı olmamalıdır. Yalnızca uygulama içi satın alma işlemlerini test etmek için kullanılır. Halihazırda bir Apple hesabı için kullanılan bir e-posta adresini kullanamazsınız. Kullanıcılar ve Erişim bölümünde, yeni bir korumalı alan hesabı oluşturmak veya mevcut korumalı alan Apple kimliklerini yönetmek için Korumalı alan altındaki Test kullanıcıları'na gidin.

3ca2b26d4e391a4c.jpeg

Artık iPhone'unuzda Ayarlar > Uygulama Mağazası > Sandbox-account.

c7dadad2c1d448fa.jpeg 5363f87efcddaa4.jpeg

Uygulama içi satın alma işlemlerinizi yapılandırma

Şimdi, satın alınabilir üç öğeyi yapılandıracaksınız:

  • dash_consumable_2k: Kullanıcılara satın alma işlemi başına 2.000 kısa çizgi (uygulama içi para birimi) veren, birden fazla kez satın alınabilen bir tüketim ürünü satın alma işlemidir.
  • dash_upgrade_3d: Kullanılamayan bir "yükseltme" kullanıcıya kozmetik olarak farklı bir Dash'i tıklamasını sağlayan bir satın alma işlemi sunar.
  • dash_subscription_doubler: Abonelik süresince kullanıcıya tıklama başına iki kat daha fazla Kısa çizgi veren bir abonelik.

d156b2f5bac43ca8.png

Uygulama İçi Satın Alma İşlemleri > Yönet'e dokunun.

Belirtilen kimliklerle uygulama içi satın alma işlemlerinizi oluşturun:

  1. dash_consumable_2k uygulamasını Tüketilebilir olarak ayarlayın.

Ürün Kimliği olarak dash_consumable_2k kullanın. Referans adı yalnızca App Store Connect'te kullanılır. Bu adı dash consumable 2k olarak ayarlayıp satın alma işlemi için yerelleştirmelerinizi eklemeniz yeterlidir. Açıklama olarak 2000 dashes fly out ile birlikte Spring is in the air satın alma işlemini çağırın.

ec1701834fd8527.png

  1. dash_upgrade_3d öğesini Tüketilebilir olmayan öğe olarak ayarlayın.

Ürün Kimliği olarak dash_upgrade_3d kullanın. Referans adını dash upgrade 3d olarak ayarlayın ve satın alma işlemi için yerelleştirmelerinizi ekleyin. Açıklama olarak Brings your dash back to the future ile birlikte 3D Dash satın alma işlemini çağırın.

6765d4b711764c30.png

  1. dash_subscription_doubler hizmetini Otomatik yenilenen abonelik olarak ayarlayın.

Aboneliklerin akışı biraz farklıdır. Önce Referans Adını ve Ürün Kimliğini ayarlamanız gerekir:

6d29e08dae26a0c4.png

Sonra, bir abonelik grubu oluşturmanız gerekir. Aynı grubun parçası olan birden çok abonelik olduğunda, bir kullanıcı bu aboneliklerden yalnızca birine aynı anda abone olabilir, ancak bu abonelikler arasında kolayca üst veya alt sürüme geçebilir. Bu grubu subscriptions olarak adlandırmanız yeterli.

5bd0da17a85ac076.png

Ardından abonelik süresini ve yerelleştirmeleri girin. Bu aboneliğe Jet Engine adını Doubles your clicks açıklamasıyla ekleyin. Save'i (Kaydet) tıklayın.

bd1b1d82eeee4cb3.png

Kaydet düğmesini tıkladıktan sonra bir abonelik fiyatı ekleyin. İstediğiniz fiyatı seçin.

d0bf39680ef0aa2e.png

Artık satın alma işlemleri listesinde bu üç satın alma işlemini görmeniz gerekir:

99d5c4b446e8fecf.png

5. Play Store'u kurma

App Store'da olduğu gibi, Play Store için de bir geliştirici hesabınızın olması gerekir. Henüz bir hesabınız yoksa hesap oluşturun.

Yeni uygulama oluşturma

Google Play Console'da yeni bir uygulama oluşturun:

  1. Play Console'u açın.
  2. Tüm uygulamalar > Uygulama oluşturun.
  3. Varsayılan dili seçin ve uygulamanız için bir başlık ekleyin. Uygulamanızın adını Google Play'de görünmesini istediğiniz şekilde yazın. Adı daha sonra değiştirebilirsiniz.
  4. Uygulamanızın bir oyun olduğunu belirtin. Bunu daha sonra değiştirebilirsiniz.
  5. Uygulamanızın ücretsiz mi yoksa ücretli mi olduğunu belirtin.
  6. Play Store kullanıcılarının bu uygulamayla ilgili olarak sizinle iletişim kurmak için kullanabileceği bir e-posta adresi ekleyin.
  7. İçerik kuralları ve ABD ihracat yasaları beyanlarını doldurun.
  8. Uygulama oluştur'u seçin.

Uygulamanız oluşturulduktan sonra kontrol paneline gidin ve Uygulamanızı ayarlama bölümündeki tüm görevleri tamamlayın. Bu bölümde uygulamanızla ilgili içerik derecelendirmeleri ve ekran görüntüleri gibi bazı bilgiler sağlarsınız. 13845badcf9bc1db.png.

Başvuruyu imzalayın

Uygulama içi satın alma işlemlerini test edebilmek için Google Play'e en az bir derleme yüklemiş olmanız gerekir.

Bunun için sürüm derlemenizin hata ayıklama anahtarları dışında bir şeyle imzalanması gerekir.

Anahtar deposu oluşturma

Mevcut bir anahtar deponuz varsa sonraki adıma geçin. Bu kod yoksa komut satırında aşağıdaki komutu çalıştırarak oluşturun.

Mac/Linux'ta, aşağıdaki komutu kullanın:

keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

Windows'da aşağıdaki komutu kullanın:

keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias key

Bu komut, key.jks dosyasını ana dizininizde depolar. Dosyayı başka bir yerde depolamak istiyorsanız -keystore parametresine ilettiğiniz bağımsız değişkeni değiştirin. Acele etmeyin

keystore

file private; lütfen herkese açık kaynak kontrolüne girmeyin.

Uygulamadaki anahtar deposuna başvuruda bulunma

Anahtar deponuza referans içeren <your app dir>/android/key.properties adlı bir dosya oluşturun:

storePassword=<password from previous step>
keyPassword=<password from previous step>
keyAlias=key
storeFile=<location of the key store file, such as /Users/<user name>/key.jks>

Gradle'da oturum açmayı yapılandırma

<your app dir>/android/app/build.gradle dosyasını düzenleyerek uygulamanız için imzalamayı yapılandırın.

Özellikler dosyanızdaki anahtar deposu bilgilerini android bloğundan önce ekleyin:

   def keystoreProperties = new Properties()
   def keystorePropertiesFile = rootProject.file('key.properties')
   if (keystorePropertiesFile.exists()) {
       keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
   }

   android {
         // omitted
   }

key.properties dosyasını keystoreProperties nesnesine yükleyin.

Aşağıdaki kodu buildTypes bloğunun önüne ekleyin:

   buildTypes {
       release {
           // TODO: Add your own signing config for the release build.
           // Signing with the debug keys for now,
           // so `flutter run --release` works.
           signingConfig signingConfigs.debug
       }
   }

Modülünüzün build.gradle dosyasındaki signingConfigs bloğunu imzalama yapılandırma bilgileriyle yapılandırın:

   signingConfigs {
       release {
           keyAlias keystoreProperties['keyAlias']
           keyPassword keystoreProperties['keyPassword']
           storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
           storePassword keystoreProperties['storePassword']
       }
   }
   buildTypes {
       release {
           signingConfig signingConfigs.release
       }
   }

Uygulamanızın sürüm derlemeleri artık otomatik olarak imzalanacak.

Uygulamanızı imzalama hakkında daha fazla bilgi için developer.android.com adresinde Uygulamanızı imzalama konusuna bakın.

İlk derlemenizi yükleyin

Uygulamanız imzalama için yapılandırıldıktan sonra aşağıdaki komutu çalıştırarak uygulamanızı geliştirebilmeniz gerekir:

flutter build appbundle

Bu komut, varsayılan olarak bir sürüm derlemesi oluşturur. Çıkış, <your app dir>/build/app/outputs/bundle/release/ adresinde bulunabilir

Google Play Console'daki kontrol panelinden Sürüm > Test ediliyor > Kapalı test ve yeni, kapalı test sürümü oluşturun.

Bu codelab'de yalnızca Google'ın uygulamayı imzalamasına devam edeceksiniz. Etkinleştirmek için Play Uygulama İmzalama'nın altındaki Devam düğmesine basın.

ba98446d9c5c40e0.png

Ardından, derleme komutu tarafından oluşturulan app-release.aab uygulama paketini yükleyin.

Kaydet'i, ardından Sürümü incele'yi tıklayın.

Son olarak, dahili test sürümünü etkinleştirmek için Dahili test kanalına sunumu başlat'ı tıklayın.

Test kullanıcılarını ayarlama

Uygulama içi satın alma işlemlerini test edebilmek için test kullanıcılarınızın Google Hesapları, Google Play Console'a iki konuma eklenmelidir:

  1. Belirli test kanalına (Dahili test)
  2. Lisans test kullanıcısı olarak

İlk olarak, test kullanıcısını dahili test kanalına ekleyerek başlayın. Sürüm > Test ediliyor > Dahili test'i tıklayın ve Test kullanıcıları sekmesini tıklayın.

a0d0394e85128f84.png

E-posta listesi oluştur'u tıklayarak yeni bir e-posta listesi oluşturun. Listeye bir ad verin ve uygulama içi satın almaları test etme erişimi gereken Google hesaplarının e-posta adreslerini ekleyin.

Ardından, listenin onay kutusunu işaretleyin ve Değişiklikleri kaydet'i tıklayın.

Ardından lisans test kullanıcılarını ekleyin:

  1. Google Play Console'un Tüm uygulamalar görünümüne geri dönün.
  2. Ayarlar > Lisans testi.
  3. Uygulama içi satın alma işlemlerini test edebilmeleri gereken test kullanıcılarıyla aynı 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.

Tıpkı App Store'da olduğu gibi, üç farklı satın alma işlemi tanımlamanız gerekir:

  • dash_consumable_2k: Kullanıcılara satın alma işlemi başına 2.000 kısa çizgi (uygulama içi para birimi) veren, birden fazla kez satın alınabilen bir tüketim ürünü satın alma işlemidir.
  • dash_upgrade_3d: Kullanılamayan bir "yükseltme" Kullanıcıya kozmetik olarak farklı bir Dash'i tıklama fırsatı veren, yalnızca bir kez satın alınabilen satın alma işlemi.
  • dash_subscription_doubler: Abonelik süresince kullanıcıya tıklama başına iki kat daha fazla Kısa çizgi veren bir abonelik.

Öncelikle tüketilebilir olan ve olmayan öğeleri ekleyin.

  1. Google Play Console'a gidip uygulamanızı seçin.
  2. Para kazanın > Ürünler > Uygulama içi ürünler.
  3. Ürün oluştur'u tıklayın.c8d66e32f57dee21.png
  4. Ürününüz için gerekli tüm bilgileri girin. Ürün kimliğinin, kullanmak istediğiniz kimlikle tam olarak eşleştiğinden emin olun.
  5. Kaydet'i tıklayın.
  6. Etkinleştir'i tıklayın.
  7. Kullanılamayan "yükseltme" işlemini, değeri için teklif verirsiniz.

Sonra, aboneliği ekleyin:

  1. Google Play Console'a gidip uygulamanızı seçin.
  2. Para kazanın > Ürünler > Abonelikler.
  3. Abonelik oluştur'u tıklayın.32a6a9eefdb71dd0.png
  4. Aboneliğiniz için gerekli tüm bilgileri girin. Ürün kimliğinin, kullanmak istediğiniz kimlikle tam olarak eşleştiğinden emin olun.
  5. Kaydet'i tıklayın

Satın alma işlemlerinizin artık Play Console'da ayarlanmış olması gerekir.

6. Firebase'i ayarlayın

Bu codelab'de kullanıcıların geri bildirimlerini doğrulamak ve izlemek için bir arka uç hizmeti kullanacaksınız. değeri için teklif verirsiniz.

Bir arka uç hizmeti kullanmanın çeşitli avantajları vardır:

  • İşlemleri güvenli bir şekilde doğrulayabilirsiniz.
  • Uygulama mağazalarından faturalandırma etkinliklerine tepki verebilirsiniz.
  • Satın alma işlemlerini bir veritabanında takip edebilirsiniz.
  • Kullanıcılar sistem saatlerini geri sararak uygulamanızın premium özellikler sunmasını sağlayamazlar.

Arka uç hizmeti oluşturmanın birçok yolu olsa da bunu Cloud Functions ve Firestore'u ve Google'ın kendi Firebase'ini kullanarak yapabilirsiniz.

Arka ucu yazmak bu codelab'de kapsam dışındadır. Bu nedenle başlangıç kodu, başlamanıza yardımcı olacak temel satın alma işlemlerini gerçekleştiren bir Firebase projesi içerir.

Firebase eklentileri başlangıç uygulamasına da dahildir.

Şimdi yapmanız gereken kendi Firebase projenizi oluşturmak, Firebase için hem uygulamayı hem de arka ucu yapılandırmak ve son olarak arka ucu dağıtmaktır.

Firebase projesi oluşturma

Firebase konsoluna gidin ve yeni bir Firebase projesi oluşturun. Bu örnek için Dash Clicker projesini çağırın.

Arka uç uygulamasında satın alma işlemlerini belirli bir kullanıcıya bağlarsınız, bu nedenle kimlik doğrulama gerekir. Bunun için Google ile Oturum Açma ile Firebase'in kimlik doğrulama modülünden yararlanın.

  1. Firebase kontrol panelinde Kimlik Doğrulama'ya 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.

7babb48832fbef29.png

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

e20553e0de5ac331.png

Aşağıdaki gibi Cloud Firestore kuralları ayarlayın:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /purchases/{purchaseId} {
      allow read: if request.auth != null && request.auth.uid == resource.data.userId
    }
  }
}

Flutter için Firebase'i kurma

Flutter uygulamasına Firebase'i yüklemek için önerilen yöntem FlutterFire CLI'yı kullanmaktır. Kurulum sayfasında açıklanan talimatları uygulayın.

Flutterfire yapılandırmasını çalıştırırken, bir önceki adımda oluşturduğunuz projeyi seçin.

$ flutterfire configure

i Found 5 Firebase projects.                                                                                                  
? Select a Firebase project to configure your Flutter application with ›                                                      
❯ in-app-purchases-1234 (in-app-purchases-1234)                                                                         
  other-flutter-codelab-1 (other-flutter-codelab-1)                                                                           
  other-flutter-codelab-2 (other-flutter-codelab-2)                                                                      
  other-flutter-codelab-3 (other-flutter-codelab-3)                                                                           
  other-flutter-codelab-4 (other-flutter-codelab-4)                                                                                                                                                               
  <create a new project>  

Daha sonra, iki platformu seçerek iOS ve Android'i etkinleştirin.

? Which platforms should your configuration support (use arrow keys & space to select)? ›                                     
✔ android                                                                                                                     
✔ ios                                                                                                                         
  macos                                                                                                                       
  web                                                                                                                          

firebase_options.dart'ı geçersiz kılmayla ilgili sorulduğunda Evet'i seçin.

? Generated FirebaseOptions file lib/firebase_options.dart already exists, do you want to override it? (y/n) › yes                                                                                                                         

Android için Firebase kurulumu: Diğer adımlar

Firebase kontrol panelinden Projeye Genel Bakış'a gidin ve Ayarlar'ı, ardından Genel sekmesini seçin.

Uygulamalarınız bölümüne gidin ve dashclicker (android) uygulamasını seçin.

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 imza sertifikanızın karmasını alın

Flutter uygulama projenizin kök dizininde, dizini android/ klasörü olarak değiştirin ve ardından bir imzalama raporu oluşturun.

cd android
./gradlew :app:signingReport

İmzalama anahtarlarından oluşan kapsamlı bir liste gösterilir. Hata ayıklama sertifikasının karmasını aradığınız için Variant ve Config özellikleri debug olarak ayarlanmış sertifikayı arayın. Anahtar deposu büyük olasılıkla .android/debug.keystore altındaki ana klasörünüzdedir.

> Task :app:signingReport
Variant: debug
Config: debug
Store: /<USER_HOME_FOLDER>/.android/debug.keystore
Alias: AndroidDebugKey
MD5: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
SHA1: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
SHA-256: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
Valid until: Tuesday, January 19, 2038

SHA-1 karmasını kopyalayın ve uygulama gönderim kalıcı iletişim kutusundaki son alanı doldurun.

iOS için Firebase'i kurma: Diğer adımlar

ios/Runnder.xcworkspace uygulamasını Xcode ile açın. Dilerseniz istediğiniz IDE ile.

VSCode'da ios/ klasörünü sağ tıklayın, ardından open in xcode öğesini tıklayın.

Android Studio'da ios/ klasörünü sağ tıklayın, ardından flutter seçeneğini ve ardından open iOS module in Xcode seçeneğini tıklayın.

iOS'te Google ile oturum açılmasına izin vermek için derleme plist dosyalarınıza CFBundleURLTypes yapılandırma seçeneğini ekleyin. (Daha fazla bilgi için google_sign_in paketi dokümanlarına göz atın.) Bu durumda, dosyalar ios/Runner/Info-Debug.plist ve ios/Runner/Info-Release.plist olur.

Anahtar/değer çifti zaten eklenmiş ancak değerlerinin değiştirilmesi gerekiyor:

  1. Etrafında <string>..</string> öğesi olmadan, GoogleService-Info.plist dosyasından REVERSED_CLIENT_ID değerini alın.
  2. ios/Runner/Info-Debug.plist ve ios/Runner/Info-Release.plist dosyalarınızda CFBundleURLTypes anahtarının altında bulunan değeri değiştirin.
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <!-- TODO Replace this value: -->
            <!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
            <string>com.googleusercontent.apps.REDACTED</string>
        </array>
    </dict>
</array>

Firebase kurulumunu tamamladınız.

7. Satın alma işlemleriyle ilgili güncellemeleri dinleme

Codelab'in bu bölümünde ürünleri satın almak için uygulamayı hazırlayacaksınız. Uygulama başlatıldıktan sonra satın alma güncellemelerini ve hataları dinlemek de bu sürece dahildir.

Satın alma işlemleriyle ilgili güncellemeleri dinleme

main.dart, içinde, iki sayfa içeren BottomNavigationBar içeren Scaffold widget'ını bulun.MyHomePage Bu sayfa ayrıca DashCounter, DashUpgrades, ve DashPurchases için üç Provider oluşturur. DashCounter, mevcut kısa çizgi sayısını izler ve otomatik olarak artırır. DashUpgrades, Dashes ile satın alabileceğiniz yükseltmeleri yönetir. Bu codelab, DashPurchases konusuna odaklanmaktadır.

Varsayılan olarak, bir sağlayıcıya ait nesne ilk kez istendiğinde tanımlanır. Bu nesne, uygulama başlatıldığında doğrudan satın alma güncellemelerini dinler. Bu nedenle, lazy: false ile bu nesnede geç yüklemeyi devre dışı bırakın:

lib/main.dart

ChangeNotifierProvider<DashPurchases>(
  create: (context) => DashPurchases(
    context.read<DashCounter>(),
  ),
  lazy: false,
),

Ayrıca InAppPurchaseConnection öğesinin bir örneğine de ihtiyacınız var. Ancak uygulamanın test edilebilir olmasını sağlamak için bağlantıyla alay edecek bir yönteme ihtiyacınız vardır. Bunu yapmak için testte geçersiz kılınabilecek bir örnek yöntemi oluşturun ve bunu main.dart öğesine ekleyin.

lib/main.dart

// Gives the option to override in tests.
class IAPConnection {
  static InAppPurchase? _instance;
  static set instance(InAppPurchase value) {
    _instance = value;
  }

  static InAppPurchase get instance {
    _instance ??= InAppPurchase.instance;
    return _instance!;
  }
}

Testin çalışmaya devam etmesini istiyorsanız testi biraz güncellemeniz gerekir. TestIAPConnection ile ilgili kodun tamamını görmek için GitHub'da widget_test.dart etiketine bakın.

test/widget_test.dart

void main() {
  testWidgets('App starts', (WidgetTester tester) async {
    IAPConnection.instance = TestIAPConnection();
    await tester.pumpWidget(const MyApp());
    expect(find.text('Tim Sneath'), findsOneWidget);
  });
}

lib/logic/dash_purchases.dart içinde DashPurchases ChangeNotifier koduna gidin. Şu anda satın aldığınız kısa çizgilere ekleyebileceğiniz yalnızca bir DashCounter var.

Bir akış abonelik mülkü, _subscription (StreamSubscription<List<PurchaseDetails>> _subscription; türünde), IAPConnection.instance, ve içe aktarma işlemleri ekleyin. Bu işlem sonucunda elde edilen kod aşağıdaki gibi görünmelidir:

lib/logic/dash_purchases.dart

import 'package:in_app_purchase/in_app_purchase.dart';

class DashPurchases extends ChangeNotifier {
  late StreamSubscription<List<PurchaseDetails>> _subscription;
  final iapConnection = IAPConnection.instance;

  DashPurchases(this.counter);
}

_subscription oluşturucuda başlatıldığı için late anahtar kelimesi _subscription kampanyasına eklendi. Bu proje varsayılan olarak null olamaz (NNBD) şekilde ayarlanmıştır. Bu, boş değer olarak bildirilmeyen özelliklerin null olmayan bir değere sahip olması gerektiği anlamına gelir. late niteleyicisi, bu değeri tanımlamayı ertelemenize olanak tanır.

Oluşturucuda purchaseUpdatedStream öğesini alın ve akışı dinlemeye başlayın. dispose() yönteminde yayın aboneliğini iptal edin.

lib/logic/dash_purchases.dart

class DashPurchases extends ChangeNotifier {
  DashCounter counter;
  late StreamSubscription<List<PurchaseDetails>> _subscription;
  final iapConnection = IAPConnection.instance;

  DashPurchases(this.counter) {
    final purchaseUpdated =
        iapConnection.purchaseStream;
    _subscription = purchaseUpdated.listen(
      _onPurchaseUpdate,
      onDone: _updateStreamOnDone,
      onError: _updateStreamOnError,
    );
  }

  @override
  void dispose() {
    _subscription.cancel();
    super.dispose();
  }

  Future<void> buy(PurchasableProduct product) async {
    // omitted
  }

  void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
    // Handle purchases here
  }

  void _updateStreamOnDone() {
    _subscription.cancel();
  }

  void _updateStreamOnError(dynamic error) {
    //Handle error here
  }
}

Artık uygulama satın alma güncellemelerini aldığından bir sonraki bölümde satın alma işlemi yapacaksınız.

Devam etmeden önce her şeyin doğru şekilde ayarlandığını doğrulamak için "flutter test"" ile test yapın.

$ flutter test

00:01 +1: All tests passed!                                                                                   

8. Satın alma işlemleri gerçekleştirme

Codelab'in bu bölümünde mevcut sahte ürünleri gerçek satın alınabilir ürünlerle değiştireceksiniz. Bu ürünler mağazalardan yüklenir, liste halinde gösterilir ve ürüne dokunulduğunda satın alınır.

PrchasableProduct

PurchasableProduct, model bir ürün gösteriyor. purchasable_product.dart öğesindeki PurchasableProduct sınıfını aşağıdaki kodla değiştirerek gerçek içeriği gösterecek şekilde güncelleyin:

lib/model/purchasable_product.dart

import 'package:in_app_purchase/in_app_purchase.dart';

enum ProductStatus {
  purchasable,
  purchased,
  pending,
}

class PurchasableProduct {
  String get id => productDetails.id;
  String get title => productDetails.title;
  String get description => productDetails.description;
  String get price => productDetails.price;
  ProductStatus status;
  ProductDetails productDetails;

  PurchasableProduct(this.productDetails) : status = ProductStatus.purchasable;
}

dash_purchases.dart, içinde, satın alınan model satın alımları kaldırın ve bunları boş bir listeyle (List<PurchasableProduct> products = [];) değiştirin

Mevcut satın alma işlemlerini yükleme

Kullanıcıya satın alma olanağı sunmak için mağazadan satın alınan öğeleri yükleyin. İlk olarak mağazanın kullanılabilir olup olmadığını kontrol edin. Mağaza kullanılamadığında storeState, notAvailable olarak ayarlandığında kullanıcıya bir hata mesajı gösterilir.

lib/logic/dash_purchases.dart

  Future<void> loadPurchases() async {
    final available = await iapConnection.isAvailable();
    if (!available) {
      storeState = StoreState.notAvailable;
      notifyListeners();
      return;
    }
  }

Mağaza kullanılabilir olduğunda, satın alınan mevcut öğeleri yükleyin. Önceki Firebase kurulumu göz önüne alındığında storeKeyConsumable, storeKeySubscription, ve storeKeyUpgrade değerleri görülebilir. Beklenen bir satın alma işlemi mevcut olmadığında, bu bilgileri konsola yazdırın; bu bilgiyi arka uç hizmetine göndermeniz faydalı olabilir.

await iapConnection.queryProductDetails(ids) yöntemi, hem bulunan kimlikleri hem de bulunan satın alınabilir ürünleri döndürür. Kullanıcı arayüzünü güncellemek için yanıttaki productDetails öğesini kullanın ve StoreState değerini available olarak ayarlayın.

lib/logic/dash_purchases.dart

import '../constants.dart';

  Future<void> loadPurchases() async {
    final available = await iapConnection.isAvailable();
    if (!available) {
      storeState = StoreState.notAvailable;
      notifyListeners();
      return;
    }
    const ids = <String>{
      storeKeyConsumable,
      storeKeySubscription,
      storeKeyUpgrade,
    };
    final response = await iapConnection.queryProductDetails(ids);
    for (var element in response.notFoundIDs) {
      debugPrint('Purchase $element not found');
    }
    products = response.productDetails.map((e) => PurchasableProduct(e)).toList();
    storeState = StoreState.available;
    notifyListeners();
  }

Oluşturucuda loadPurchases() işlevini çağırın:

lib/logic/dash_purchases.dart

  DashPurchases(this.counter) {
    final purchaseUpdated = iapConnection.purchaseStream;
    _subscription = purchaseUpdated.listen(
      _onPurchaseUpdate,
      onDone: _updateStreamOnDone,
      onError: _updateStreamOnError,
    );
    loadPurchases();
  }

Son olarak, storeState alanının StoreState.available olan değerini StoreState.loading: olarak değiştirin

lib/logic/dash_purchases.dart

StoreState storeState = StoreState.loading;

Satın alınabilir ürünleri gösterme

purchase_page.dart dosyasını kullanabilirsiniz. PurchasePage widget'ı, StoreState bağlı olarak _PurchasesLoading, _PurchaseList, veya _PurchasesNotAvailable, gösterir. Widget ayrıca kullanıcının geçmiş satın alma işlemlerini de gösterir ve bunlar sonraki adımda kullanılır.

_PurchaseList widget'ı, satın alınabilir ürünlerin listesini gösterir ve DashPurchases nesnesine bir satın alma isteği gönderir.

lib/pages/purchase_page.dart

class _PurchaseList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var purchases = context.watch<DashPurchases>();
    var products = purchases.products;
    return Column(
      children: products
          .map((product) => _PurchaseWidget(
              product: product,
              onPressed: () {
                purchases.buy(product);
              }))
          .toList(),
    );
  }
}

Doğru şekilde yapılandırılmışlarsa, mevcut ürünleri Android ve iOS mağazalarında görebilirsiniz. İlgili konsollara girildiğinde, satın alınan öğelerin kullanılabilir hale gelmesinin biraz zaman alabileceğini unutmayın.

ca1a9f97c21e552d.png

dash_purchases.dart işlevine dönün ve ürün satın almak için işlevi uygulayın. Sadece sarf malzemelerini sarf malzemelerinden ayırmanız gerekir. Yükseltme ve abonelik ürünleri tüketilebilir değildir.

lib/logic/dash_purchases.dart

  Future<void> buy(PurchasableProduct product) async {
    final purchaseParam = PurchaseParam(productDetails: product.productDetails);
    switch (product.id) {
      case storeKeyConsumable:
        await iapConnection.buyConsumable(purchaseParam: purchaseParam);
        break;
      case storeKeySubscription:
      case storeKeyUpgrade:
        await iapConnection.buyNonConsumable(purchaseParam: purchaseParam);
        break;
      default:
        throw ArgumentError.value(
            product.productDetails, '${product.id} is not a known product');
    }
  }

Devam etmeden önce _beautifiedDashUpgrade değişkenini oluşturun ve beautifiedDash alıcısını bu değişkene referans verecek şekilde güncelleyin.

lib/logic/dash_purchases.dart

  bool get beautifiedDash => _beautifiedDashUpgrade;
  bool _beautifiedDashUpgrade = false;

_onPurchaseUpdate yöntemi satın alma güncellemelerini alır, satın alma sayfasında gösterilen ürünün durumunu günceller ve satın alma işlemini sayaç mantığına uygular. Mağazanın satın alma işleminin doğru şekilde yapıldığını bilmesi için, satın alma işlemini gerçekleştirdikten sonra completePurchase numaralı telefonu aramanız önemlidir.

lib/logic/dash_purchases.dart

  Future<void> _onPurchaseUpdate(
      List<PurchaseDetails> purchaseDetailsList) async {
    for (var purchaseDetails in purchaseDetailsList) {
      await _handlePurchase(purchaseDetails);
    }
    notifyListeners();
  }

  Future<void> _handlePurchase(PurchaseDetails purchaseDetails) async {
    if (purchaseDetails.status == PurchaseStatus.purchased) {
      switch (purchaseDetails.productID) {
        case storeKeySubscription:
          counter.applyPaidMultiplier();
          break;
        case storeKeyConsumable:
          counter.addBoughtDashes(2000);
          break;
        case storeKeyUpgrade:
          _beautifiedDashUpgrade = true;
          break;
      }
    }

    if (purchaseDetails.pendingCompletePurchase) {
      await iapConnection.completePurchase(purchaseDetails);
    }
  }

9. Arka ucu ayarlama

Satın alma işlemlerini izlemeye ve doğrulamaya geçmeden önce, bunu destekleyecek bir Dart arka ucu oluşturun.

Bu bölümde, kök olarak dart-backend/ klasörü üzerinde çalışın.

Aşağıdaki araçların yüklü olduğundan emin olun:

Temel projeye genel bakış

Bu projenin bazı bölümleri, codelab için kapsam dışında kabul edildiğinden başlangıç koduna dahil edilir. Başlamadan önce öğeleri nasıl yapılandıracağınıza dair bir fikir edinmek için başlangıç kodunda zaten bulunan şeylerin üzerinden geçmek iyi bir fikirdir.

Bu arka uç kodu makinenizde yerel olarak çalışabilir. Bu kodu kullanmak için dağıtmanız gerekmez. Ancak, geliştirme cihazınızdan (Android veya iPhone) sunucunun çalışacağı makineye bağlanabilmeniz gerekir. Bunun için bunların aynı ağda olmaları ve makinenizin IP adresini bilmeniz gerekir.

Aşağıdaki komutu kullanarak sunucuyu çalıştırmayı deneyin:

$ dart ./bin/server.dart

Serving at http://0.0.0.0:8080

Dart arka ucu, API uç noktalarını sunmak için shelf ve shelf_router kullanır. Varsayılan olarak sunucu herhangi bir rota sağlamaz. Daha sonra, satın alma işlemlerini doğrulama işlemini gerçekleştirmek için bir rota oluşturacaksınız.

lib/iap_repository.dart dilindeki IapRepository, başlangıç kodunda halihazırda bulunan bir parçadır. Firestore veya genel olarak veritabanları ile nasıl etkileşimde bulunulacağını öğrenmenin bu codelab ile alakalı olmadığı düşünüldüğünden, başlangıç kodu, Firestore'da satın alma işlemleri oluşturmanız veya bunları güncellemenizin yanı sıra bu satın alımlara yönelik tüm sınıfları da içerir.

Firebase erişimini ayarlayın

Firebase Firestore'a erişmek için hizmet hesabı erişim anahtarı gerekir. Firebase proje ayarlarını açıp Hizmet hesapları bölümüne gidin ve Yeni özel anahtar oluştur'u seçin.

27590fc77ae94ad4.png

İndirilen JSON dosyasını assets/ klasörüne kopyalayın ve service-account-firebase.json olarak yeniden adlandırın.

Google Play erişimini ayarlama

Satın alma işlemlerini doğrulamak üzere Play Store'a erişmek için bu izinlere sahip bir hizmet hesabı oluşturmanız ve bu hesaba ait JSON kimlik bilgilerini indirmeniz gerekir.

  1. Google Play Console'a gidin ve Tüm uygulamalar sayfasından başlayın.
  2. Kurulum > API erişimi. 317fdfb54921f50e.png Google Play Console, bir proje oluşturmanızı veya mevcut bir projeye bağlantı vermenizi isterse bunu önce yapın, ardından bu sayfaya geri dönün.
  3. Hizmet hesaplarını tanımlayabileceğiniz bölümü bulun ve Yeni hizmet hesabı oluştur'u tıklayın.1e70d3f8d794bebb.png
  4. Açılan iletişim kutusunda Google Cloud Platform bağlantısını tıklayın. 7c9536336dd9e9b4.png
  5. Projenizi seçin. Bu seçeneği görmüyorsanız sağ üstteki Hesap açılır listesinden doğru Google Hesabı'nda oturum açtığınızdan emin olun. 3fb3a25bad803063.png
  6. Projenizi seçtikten sonra üst menü çubuğunda + Hizmet Hesabı Oluştur'u tıklayın. 62fe4c3f8644acd8.png
  7. Hizmet hesabı için bir ad girin, neyle ilgili olduğunu hatırlamanız için isteğe bağlı olarak bir açıklama girin ve sonraki adıma geçin. 8a92d5d6a3dff48c.png.
  8. Hizmet hesabına Düzenleyici rolünü atayın. 6052b7753667ed1a.png
  9. Sihirbazı tamamlayın, geliştirici konsolundaki API Erişimi sayfasına dönün ve Hizmet hesaplarını yenile'yi tıklayın. Yeni oluşturulan hesabınızı listede göreceksiniz. 5895a7db8b4c7659.png.
  10. Yeni hizmet hesabınız için Erişim izni ver'i tıklayın.
  11. Bir sonraki sayfada Finansal veriler bloğuna gidin. Hem Finansal verileri, siparişleri ve iptal anketine verilen yanıtları görüntüleme hem de Siparişleri ve abonelikleri yönet'i seçin. 75b22d0201cf67e.png
  12. Kullanıcı davet et'i tıklayın. 70ea0b1288c62a59.png
  13. Hesap kurulumunu yaptığınıza göre yalnızca bazı kimlik bilgileri oluşturmanız gerekir. Cloud konsoluna dönün, hizmet hesapları listesinde hizmet hesabınızı bulun, üç dikey noktayı tıklayın ve Anahtarları yönet'i seçin. 853ee186b0e9954e.png
  14. Yeni bir JSON anahtarı oluşturup indirin. 2a33a55803f5299c.png cb4bf48ebac0364e.png
  15. İndirilen dosyanın adını service-account-google-play.json, olarak değiştirin ve assets/ dizinine taşıyın.

Yapmamız gereken diğer bir işlem de lib/constants.dart, uygulamasını açıp androidPackageId değerini Android uygulamanız için seçtiğiniz paket kimliğiyle değiştirmektir.

Apple App Store erişimini ayarlama

Satın alma işlemlerini doğrulamak üzere App Store'a erişmek için, paylaşılan bir gizli anahtar oluşturmanız gerekir:

  1. App Store Connect'i açın.
  2. Uygulamalarım'a gidin ve uygulamanızı seçin.
  3. Kenar çubuğu gezinme menüsünde, Uygulama İçi Satın Alma İşlemleri > Yönet'e dokunun.
  4. Listenin sağ üst tarafında App-Specific Shared Secret (Uygulamaya Özel Paylaşılan Gizli Anahtar) seçeneğini tıklayın.
  5. Yeni gizli anahtar oluşturun ve kopyalayın.
  6. lib/constants.dart, dosyasını açın ve appStoreSharedSecret değerini, az önce oluşturduğunuz paylaşılan gizli anahtarla değiştirin.

d8b8042470aaeff.png

b72f4565750e2f40.png

Sabit değerler yapılandırma dosyası

Devam etmeden önce, lib/constants.dart dosyasında aşağıdaki sabit değerlerin yapılandırıldığından emin olun:

  • androidPackageId: Android'de kullanılan paket kimliği. ör. com.example.dashclicker
  • appStoreSharedSecret: Satın alma işlemlerini doğrulamak üzere App Store Connect'e erişmek için paylaşılan gizli anahtar.
  • bundleId: iOS'te kullanılan paket kimliği. ör. com.example.dashclicker

Diğer sabit değerleri şimdilik göz ardı edebilirsiniz.

10. Satın alma işlemlerini doğrulayın

Satın alma işlemlerini doğrulamaya ilişkin genel akış iOS ve Android'de benzerdir.

Uygulamanız, her iki mağaza için de satın alma işlemi yapıldığında bir jeton alır.

Bu jeton, uygulama tarafından arka uç hizmetinize gönderilir ve ardından, sağlanan jetonu kullanarak satın alma işlemini ilgili mağazanın sunucularıyla doğrular.

Daha sonra arka uç hizmeti satın alma işlemini saklamayı seçebilir ve satın alma işleminin geçerli olup olmadığını uygulamaya yanıt verebilir.

Arka uç hizmetinin, doğrulamayı kullanıcınızın cihazında çalışan uygulama yerine mağazalar ile yapmasını sağlayarak (örneğin, sistem saatini geri sararak) kullanıcının premium özelliklere erişmesini önleyebilirsiniz.

Flutter tarafını ayarlama

Kimlik doğrulamasını ayarlayın

Satın alma işlemlerini arka uç hizmetinize göndereceğinizden, satın alma işlemi sırasında kullanıcının kimliğinin doğrulandığından emin olmak istersiniz. Kimlik doğrulama mantığının çoğu başlangıç projesine sizin için zaten eklenmiştir. Kullanıcı henüz giriş yapmadığında PurchasePage öğesinin giriş düğmesini gösterdiğinden emin olmanız gerekir. PurchasePage derleme yönteminin başına aşağıdaki kodu ekleyin:

lib/pages/purchase_page.dart

import '../logic/firebase_notifier.dart';
import '../model/firebase_state.dart';
import 'login_page.dart';

class PurchasePage extends StatelessWidget {  
  const PurchasePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var firebaseNotifier = context.watch<FirebaseNotifier>();
    if (firebaseNotifier.state == FirebaseState.loading) {
      return _PurchasesLoading();
    } else if (firebaseNotifier.state == FirebaseState.notAvailable) {
      return _PurchasesNotAvailable();
    }

    if (!firebaseNotifier.loggedIn) {
      return const LoginPage();
    }
    // omitted

Uygulamadan arama doğrulama uç noktası

Uygulamada, bir http post çağrısı kullanarak Dart arka ucunuzda /verifypurchase uç noktasını çağıran _verifyPurchase(PurchaseDetails purchaseDetails) işlevini oluşturun.

Seçilen mağazayı (Play Store için google_play veya App Store için app_store), serverVerificationData ve productID öğelerini gönderin. Sunucu, satın alma işleminin doğrulanıp doğrulanmadığını belirten bir durum kodu döndürür.

Uygulama sabit değerlerinde, sunucu IP'sini yerel makine IP adresinize göre yapılandırın.

lib/logic/dash_purchases.dart

  FirebaseNotifier firebaseNotifier;

  DashPurchases(this.counter, this.firebaseNotifier) {
    // omitted
  }

main.dart: içinde, DashPurchases oluşturarak firebaseNotifier özelliğini ekleyin

lib/main.dart

        ChangeNotifierProvider<DashPurchases>(
          create: (context) => DashPurchases(
            context.read<DashCounter>(),
            context.read<FirebaseNotifier>(),
          ),
          lazy: false,
        ),

FirebaseNotifier'da kullanıcı için bir alıcı ekleyerek kullanıcı kimliğini satın alma doğrulama işlevine iletebilirsiniz.

lib/logic/firebase_notifier.dart

  User? get user => FirebaseAuth.instance.currentUser;

_verifyPurchase işlevini DashPurchases sınıfına ekleyin. Bu async işlevi, satın alma işleminin doğrulanıp doğrulanmadığını gösteren bir boole döndürür.

lib/logic/dash_purchases.dart

  Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) async {
    final url = Uri.parse('http://$serverIp:8080/verifypurchase');
    const headers = {
      'Content-type': 'application/json',
      'Accept': 'application/json',
    };
    final response = await http.post(
      url,
      body: jsonEncode({
        'source': purchaseDetails.verificationData.source,
        'productId': purchaseDetails.productID,
        'verificationData':
            purchaseDetails.verificationData.serverVerificationData,
        'userId': firebaseNotifier.user?.uid,
      }),
      headers: headers,
    );
    if (response.statusCode == 200) {
      print('Successfully verified purchase');
      return true;
    } else {
      print('failed request: ${response.statusCode} - ${response.body}');
      return false;
    }
  }

Satın alma işlemini uygulamadan hemen önce _handlePurchase işlevinde _verifyPurchase işlevini çağırın. Satın alma işlemini yalnızca doğrulandığında uygulamanız gerekir. Bir üretim uygulamasında bunu daha ayrıntılı şekilde belirtebilirsiniz (örneğin, mağaza geçici olarak kullanılamadığında deneme aboneliği uygulamak için). Ancak bu örnekte, basitliği koruyun ve satın alma işlemini yalnızca satın alma işlemi başarılı bir şekilde doğrulandığında uygulayın.

lib/logic/dash_purchases.dart

  Future<void> _onPurchaseUpdate(
      List<PurchaseDetails> purchaseDetailsList) async {
    for (var purchaseDetails in purchaseDetailsList) {
      await _handlePurchase(purchaseDetails);
    }
    notifyListeners();
  }

  Future<void> _handlePurchase(PurchaseDetails purchaseDetails) async {
    if (purchaseDetails.status == PurchaseStatus.purchased) {
      // Send to server
      var validPurchase = await _verifyPurchase(purchaseDetails);

      if (validPurchase) {
        // Apply changes locally
        switch (purchaseDetails.productID) {
          case storeKeySubscription:
            counter.applyPaidMultiplier();
            break;
          case storeKeyConsumable:
            counter.addBoughtDashes(1000);
            break;
        }
      }
    }

    if (purchaseDetails.pendingCompletePurchase) {
      await iapConnection.completePurchase(purchaseDetails);
    }
  }

Uygulamada artık satın alma işlemlerini doğrulamak için her şey hazır.

Arka uç hizmetini ayarlama

Ardından, arka uçta satın alma işlemlerini doğrulamak için Cloud Functions işlevini ayarlayın.

Satın alma işleyiciler oluşturma

Her iki mağazanın doğrulama akışı birbirine yakın olduğundan, her mağaza için ayrı uygulamalar içeren soyut bir PurchaseHandler sınıfı oluşturun.

be50c207c5a2a519.png

lib/ klasörüne bir purchase_handler.dart dosyası ekleyerek başlayın. Bu dosyada, iki farklı satın alma türünü (abonelikler ve abonelik olmayanlar) doğrulamak için iki soyut yöntem içeren soyut bir PurchaseHandler sınıfı tanımlarsınız.

lib/purchase_handler.dart

import 'products.dart';

/// Generic purchase handler,
/// must be implemented for Google Play and Apple Store
abstract class PurchaseHandler {

  /// Verify if non-subscription purchase (aka consumable) is valid
  /// and update the database
  Future<bool> handleNonSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  });

  /// Verify if subscription purchase (aka non-consumable) is valid
  /// and update the database
  Future<bool> handleSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  });
}

Gördüğünüz gibi her yöntem için üç parametre gerekir:

  • userId: Satın alma işlemlerini kullanıcıyla ilişkilendirebilmeniz için giriş yapmış kullanıcının kimliği.
  • productData: Ürünle ilgili veriler. Bir dakika içinde tanımlayacaksınız.
  • token: Kullanıcıya mağaza tarafından sağlanan jeton.

Ayrıca bu satın alma işleyicilerin kullanımını kolaylaştırmak için hem abonelikler hem de abonelik olmayan öğeler için kullanılabilecek bir verifyPurchase() yöntemi ekleyin:

lib/purchase_handler.dart

  /// Verify if purchase is valid and update the database
  Future<bool> verifyPurchase({
    required String userId,
    required ProductData productData,
    required String token,
  }) async {
    switch (productData.type) {
      case ProductType.subscription:
        return handleSubscription(
          userId: userId,
          productData: productData,
          token: token,
        );
      case ProductType.nonSubscription:
        return handleNonSubscription(
          userId: userId,
          productData: productData,
          token: token,
        );
    }
  }

Artık her iki durum için de verifyPurchase öğesini çağırabilirsiniz, ancak yine de ayrı uygulamaları kullanabilirsiniz.

ProductData sınıfı, satın alınabilir farklı ürünlerle ilgili temel bilgileri içerir. Bu bilgiler arasında, ürün kimliği (bazen SKU olarak da adlandırılır) ve ProductType yer alır.

lib/products.dart

class ProductData {
  final String productId;
  final ProductType type;

  const ProductData(this.productId, this.type);
}

ProductType, abonelik olabilir ya da olmayabilir.

lib/products.dart

enum ProductType {
  subscription,
  nonSubscription,
}

Son olarak, ürün listesi aynı dosyadaki bir harita olarak tanımlanır.

lib/products.dart

const productDataMap = {
  'dash_consumable_2k': ProductData(
    'dash_consumable_2k',
    ProductType.nonSubscription,
  ),
  'dash_upgrade_3d': ProductData(
    'dash_upgrade_3d',
    ProductType.nonSubscription,
  ),
  'dash_subscription_doubler': ProductData(
    'dash_subscription_doubler',
    ProductType.subscription,
  ),
};

Daha sonra, Google Play Store ve Apple App Store için bazı yer tutucu uygulamaları tanımlayın. Google Play ile başlayın:

lib/google_play_purchase_handler.dart oluşturun ve az önce yazdığınız PurchaseHandler öğesini genişleten bir sınıf ekleyin:

lib/google_play_purchase_handler.dart

import 'dart:async';

import 'package:googleapis/androidpublisher/v3.dart' as ap;

import 'constants.dart';
import 'iap_repository.dart';
import 'products.dart';
import 'purchase_handler.dart';

class GooglePlayPurchaseHandler extends PurchaseHandler {
  final ap.AndroidPublisherApi androidPublisher;
  final IapRepository iapRepository;

  GooglePlayPurchaseHandler(
    this.androidPublisher,
    this.iapRepository,
  );

  @override
  Future<bool> handleNonSubscription({
    required String? userId,
    required ProductData productData,
    required String token,
  }) async {
    return true;
  }

  @override
  Future<bool> handleSubscription({
    required String? userId,
    required ProductData productData,
    required String token,
  }) async {
    return true;
  }
}

Şimdilik işleyici yöntemleri için true değerini döndürmektedir; bu adımları daha sonra ele alacağız.

Fark etmiş olabileceğiniz gibi, oluşturucu IapRepository öğesinin bir örneğini alır. Satın alma işleyici, daha sonra Firestore'da yapılan satın alma işlemleriyle ilgili bilgileri depolamak için bu örneği kullanır. Google Play ile iletişim kurmak için, sağlanan AndroidPublisherApi kullanılır.

Sonra, aynı işlemi uygulama mağazası işleyicisi için yapın. lib/app_store_purchase_handler.dart oluşturun ve PurchaseHandler öğesini tekrar genişleten bir sınıf ekleyin:

lib/app_store_purchase_handler.dart

import 'dart:async';

import 'package:app_store_server_sdk/app_store_server_sdk.dart';

import 'constants.dart';
import 'iap_repository.dart';
import 'products.dart';
import 'purchase_handler.dart';

class AppStorePurchaseHandler extends PurchaseHandler {
  final IapRepository iapRepository;

  AppStorePurchaseHandler(
    this.iapRepository,
  );

  @override
  Future<bool> handleNonSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  }) {
    return true;
  }

  @override
  Future<bool> handleSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  }) {
    return true;
  }
}

Çok güzel! Artık iki satın alma işleyiciniz vardır. Şimdi satın alma doğrulama API'si uç noktası oluşturalım.

Satın alma işleyicileri kullanma

bin/server.dart uygulamasını açın ve shelf_route kullanarak bir API uç noktası oluşturun:

bin/server.dart

Future<void> main() async {
  final router = Router();

  final purchaseHandlers = await _createPurchaseHandlers();

  router.post('/verifypurchase', (Request request) async {
    final dynamic payload = json.decode(await request.readAsString());

    final (:userId, :source, :productData, :token) = getPurchaseData(payload);

    final result = await purchaseHandlers[source]!.verifyPurchase(
      userId: userId,
      productData: productData,
      token: token,
    );

    if (result) {
      return Response.ok('all good!');
    } else {
      return Response.internalServerError();
    }
  });

  await serveHandler(router);
}

({
  String userId,
  String source,
  ProductData productData,
  String token,
}) getPurchaseData(dynamic payload) {
  if (payload
      case {
        'userId': String userId,
        'source': String source,
        'productId': String productId,
        'verificationData': String token,
      }) {
    return (
      userId: userId,
      source: source,
      productData: productDataMap[productId]!,
      token: token,
    );
  } else {
    throw const FormatException('Unexpected JSON');
  }
}

Yukarıdaki kod aşağıdakileri yapıyor:

  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 çıkarın:
  3. userId: Şu anda giriş yapmış kullanıcı kimliği
  4. source: Kullanılan mağaza, app_store veya google_play.
  5. productData: Daha önce oluşturduğunuz productDataMap verisinden elde edilir.
  6. token: Mağazalara gönderilecek doğrulama verilerini içerir.
  7. Kaynağa bağlı olarak GooglePlayPurchaseHandler veya AppStorePurchaseHandler için verifyPurchase yöntemine çağrı.
  8. Doğrulama başarılı olursa yöntem istemciye bir Response.ok döndürür.
  9. Doğrulama başarısız olursa yöntem, istemciye bir Response.internalServerError döndürür.

API uç noktasını oluşturduktan sonra iki satın alma işleyiciyi yapılandırmanız gerekir. Bunun için önceki adımda edindiğiniz hizmet hesabı anahtarlarını yüklemeniz ve Android Publisher API ile Firebase Firestore API gibi farklı hizmetlere erişimi yapılandırmanız gerekir. Ardından, farklı bağımlılıklara sahip iki satın alma işleyici oluşturun:

bin/server.dart

Future<Map<String, PurchaseHandler>> _createPurchaseHandlers() async {
  // Configure Android Publisher API access
  final serviceAccountGooglePlay =
      File('assets/service-account-google-play.json').readAsStringSync();
  final clientCredentialsGooglePlay =
      auth.ServiceAccountCredentials.fromJson(serviceAccountGooglePlay);
  final clientGooglePlay =
      await auth.clientViaServiceAccount(clientCredentialsGooglePlay, [
    ap.AndroidPublisherApi.androidpublisherScope,
  ]);
  final androidPublisher = ap.AndroidPublisherApi(clientGooglePlay);

  // Configure Firestore API access
  final serviceAccountFirebase =
      File('assets/service-account-firebase.json').readAsStringSync();
  final clientCredentialsFirebase =
      auth.ServiceAccountCredentials.fromJson(serviceAccountFirebase);
  final clientFirebase =
      await auth.clientViaServiceAccount(clientCredentialsFirebase, [
    fs.FirestoreApi.cloudPlatformScope,
  ]);
  final firestoreApi = fs.FirestoreApi(clientFirebase);
  final dynamic json = jsonDecode(serviceAccountFirebase);
  final projectId = json['project_id'] as String;
  final iapRepository = IapRepository(firestoreApi, projectId);

  return {
    'google_play': GooglePlayPurchaseHandler(
      androidPublisher,
      iapRepository,
    ),
    'app_store': AppStorePurchaseHandler(
      iapRepository,
    ),
  };
}

Android satın alma işlemlerini doğrulama: Satın alma işleyicisini uygulama

Sonra, Google Play satın alma işleyiciyi uygulamaya devam edin.

Google, satın alma işlemlerini doğrulamanız gereken API'lerle etkileşimde bulunmanız için Dart paketleri sağlamaktadır. Bu anahtar kelimeleri server.dart dosyasında başlattınız ve şimdi GooglePlayPurchaseHandler sınıfında kullanıyorsunuz.

Abonelik türü olmayan satın alma işlemleri için işleyiciyi uygulayın:

lib/google_play_purchase_handler.dart

  @override
  Future<bool> handleNonSubscription({
    required String? userId,
    required ProductData productData,
    required String token,
  }) async {
    print(
      'GooglePlayPurchaseHandler.handleNonSubscription'
      '($userId, ${productData.productId}, ${token.substring(0, 5)}...)',
    );

    try {
      // Verify purchase with Google
      final response = await androidPublisher.purchases.products.get(
        androidPackageId,
        productData.productId,
        token,
      );

      print('Purchases response: ${response.toJson()}');

      // Make sure an order id exists
      if (response.orderId == null) {
        print('Could not handle purchase without order id');
        return false;
      }
      final orderId = response.orderId!;

      final purchaseData = NonSubscriptionPurchase(
        purchaseDate: DateTime.fromMillisecondsSinceEpoch(
          int.parse(response.purchaseTimeMillis ?? '0'),
        ),
        orderId: orderId,
        productId: productData.productId,
        status: _nonSubscriptionStatusFrom(response.purchaseState),
        userId: userId,
        iapSource: IAPSource.googleplay,
      );

      // Update the database
      if (userId != null) {
        // If we know the userId,
        // update the existing purchase or create it if it does not exist.
        await iapRepository.createOrUpdatePurchase(purchaseData);
      } else {
        // If we do not know the user id, a previous entry must already
        // exist, and thus we'll only update it.
        await iapRepository.updatePurchase(purchaseData);
      }
      return true;
    } on ap.DetailedApiRequestError catch (e) {
      print(
        'Error on handle NonSubscription: $e\n'
        'JSON: ${e.jsonResponse}',
      );
    } catch (e) {
      print('Error on handle NonSubscription: $e\n');
    }
    return false;
  }

Abonelik satın alma işleyicisini benzer bir şekilde güncelleyebilirsiniz:

lib/google_play_purchase_handler.dart

  /// Handle subscription purchases.
  ///
  /// Retrieves the purchase status from Google Play and updates
  /// the Firestore Database accordingly.
  @override
  Future<bool> handleSubscription({
    required String? userId,
    required ProductData productData,
    required String token,
  }) async {
    print(
      'GooglePlayPurchaseHandler.handleSubscription'
      '($userId, ${productData.productId}, ${token.substring(0, 5)}...)',
    );

    try {
      // Verify purchase with Google
      final response = await androidPublisher.purchases.subscriptions.get(
        androidPackageId,
        productData.productId,
        token,
      );

      print('Subscription response: ${response.toJson()}');

      // Make sure an order id exists
      if (response.orderId == null) {
        print('Could not handle purchase without order id');
        return false;
      }
      final orderId = extractOrderId(response.orderId!);

      final purchaseData = SubscriptionPurchase(
        purchaseDate: DateTime.fromMillisecondsSinceEpoch(
          int.parse(response.startTimeMillis ?? '0'),
        ),
        orderId: orderId,
        productId: productData.productId,
        status: _subscriptionStatusFrom(response.paymentState),
        userId: userId,
        iapSource: IAPSource.googleplay,
        expiryDate: DateTime.fromMillisecondsSinceEpoch(
          int.parse(response.expiryTimeMillis ?? '0'),
        ),
      );

      // Update the database
      if (userId != null) {
        // If we know the userId,
        // update the existing purchase or create it if it does not exist.
        await iapRepository.createOrUpdatePurchase(purchaseData);
      } else {
        // If we do not know the user id, a previous entry must already
        // exist, and thus we'll only update it.
        await iapRepository.updatePurchase(purchaseData);
      }
      return true;
    } on ap.DetailedApiRequestError catch (e) {
      print(
        'Error on handle Subscription: $e\n'
        'JSON: ${e.jsonResponse}',
      );
    } catch (e) {
      print('Error on handle Subscription: $e\n');
    }
    return false;
  }
}

Sipariş kimliklerinin ayrıştırılmasını kolaylaştırmak için aşağıdaki yöntemi ve satın alma durumunu ayrıştırmak için iki yöntem ekleyin.

lib/google_play_purchase_handler.dart

/// If a subscription suffix is present (..#) extract the orderId.
String extractOrderId(String orderId) {
  final orderIdSplit = orderId.split('..');
  if (orderIdSplit.isNotEmpty) {
    orderId = orderIdSplit[0];
  }
  return orderId;
}

NonSubscriptionStatus _nonSubscriptionStatusFrom(int? state) {
  return switch (state) {
    0 => NonSubscriptionStatus.completed,
    2 => NonSubscriptionStatus.pending,
    _ => NonSubscriptionStatus.cancelled,
  };
}

SubscriptionStatus _subscriptionStatusFrom(int? state) {
  return switch (state) {
    // Payment pending
    0 => SubscriptionStatus.pending,
    // Payment received
    1 => SubscriptionStatus.active,
    // Free trial
    2 => SubscriptionStatus.active,
    // Pending deferred upgrade/downgrade
    3 => SubscriptionStatus.pending,
    // Expired or cancelled
    _ => SubscriptionStatus.expired,
  };
}

Google Play satın alma işlemleriniz artık doğrulanmalı ve veritabanında depolanmalıdır.

Ardından, iOS için App Store'dan satın alma işlemlerine geçin.

iOS satın alma işlemlerini doğrulama: Satın alma işleyiciyi uygulayın

App Store ile satın alma işlemlerini doğrulamak için işlemi kolaylaştıran app_store_server_sdk adlı bir üçüncü taraf Dart paketi sunulmaktadır.

ITunesApi örneğini oluşturarak başlayın. Korumalı alan yapılandırmasını kullanın ve hata ayıklamayı kolaylaştırmak için günlük kaydını etkinleştirin.

lib/app_store_purchase_handler.dart

  final _iTunesAPI = ITunesApi(
    ITunesHttpClient(
      ITunesEnvironment.sandbox(),
      loggingEnabled: true,
    ),
  );

Artık Google Play API'lerinin aksine App Store, hem abonelikler hem de abonelik olmayan öğeler için aynı API uç noktalarını kullanıyor. Bu, her iki işleyici için de aynı mantığı kullanabileceğiniz anlamına gelir. Aynı uygulamayı çağırmaları için bunları birleştirin:

lib/app_store_purchase_handler.dart

  @override
  Future<bool> handleNonSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  }) {
    return handleValidation(userId: userId, token: token);
  }

  @override
  Future<bool> handleSubscription({
    required String userId,
    required ProductData productData,
    required String token,
  }) {
    return handleValidation(userId: userId, token: token);
  }

  /// Handle purchase validation.
  Future<bool> handleValidation({
    required String userId,
    required String token,
  }) async {
   //..
  }

Şimdi handleValidation uygulayın:

lib/app_store_purchase_handler.dart

  /// Handle purchase validation.
  Future<bool> handleValidation({
    required String userId,
    required String token,
  }) async {
    print('AppStorePurchaseHandler.handleValidation');
    final response = await _iTunesAPI.verifyReceipt(
      password: appStoreSharedSecret,
      receiptData: token,
    );
    print('response: $response');
    if (response.status == 0) {
      print('Successfully verified purchase');
      final receipts = response.latestReceiptInfo ?? [];
      for (final receipt in receipts) {
        final product = productDataMap[receipt.productId];
        if (product == null) {
          print('Error: Unknown product: ${receipt.productId}');
          continue;
        }
        switch (product.type) {
          case ProductType.nonSubscription:
            await iapRepository.createOrUpdatePurchase(NonSubscriptionPurchase(
              userId: userId,
              productId: receipt.productId ?? '',
              iapSource: IAPSource.appstore,
              orderId: receipt.originalTransactionId ?? '',
              purchaseDate: DateTime.fromMillisecondsSinceEpoch(
                  int.parse(receipt.originalPurchaseDateMs ?? '0')),
              type: product.type,
              status: NonSubscriptionStatus.completed,
            ));
            break;
          case ProductType.subscription:
            await iapRepository.createOrUpdatePurchase(SubscriptionPurchase(
              userId: userId,
              productId: receipt.productId ?? '',
              iapSource: IAPSource.appstore,
              orderId: receipt.originalTransactionId ?? '',
              purchaseDate: DateTime.fromMillisecondsSinceEpoch(
                  int.parse(receipt.originalPurchaseDateMs ?? '0')),
              type: product.type,
              expiryDate: DateTime.fromMillisecondsSinceEpoch(
                  int.parse(receipt.expiresDateMs ?? '0')),
              status: SubscriptionStatus.active,
            ));
            break;
        }
      }
      return true;
    } else {
      print('Error: Status: ${response.status}');
      return false;
    }
  }

App Store'dan satın aldığınız öğeler artık doğrulanıp veritabanında depolanıyor olmalıdır.

Arka ucu çalıştırma

Bu noktada, /verifypurchase uç noktasını sunmak için dart bin/server.dart çalıştırabilirsiniz.

$ dart bin/server.dart 
Serving at http://0.0.0.0:8080

11. Satın alma işlemlerini takip edin

Kullanıcılarınızın düzenli olarak arka uç hizmetinde olduğunu varsayalım. Bunun nedeni, arka ucunuzun mağazadaki etkinliklere yanıt verebilmesi ve böylece, önbelleğe alma nedeniyle eski bilgilerle karşılaşma ihtimalinin daha düşük olması ve ayrıca oynanabilirliğe daha açık olmasıdır.

İlk olarak, geliştirmekte olduğunuz Dart arka ucuyla arka uçta mağaza etkinliklerinin işlenmesini ayarlayın.

Mağaza etkinliklerini arka uçta işleme

Mağazalar, aboneliklerin yenilenmesi gibi gerçekleşen faturalandırma etkinlikleri hakkında arka ucunuzu bilgilendirebilir. Veritabanınızdaki satın alma işlemlerini güncel tutmak için arka ucunuzda bu etkinlikleri işleyebilirsiniz. Bu bölümde hem Google Play Store hem de Apple App Store için kurulum yapın.

Google Play faturalandırma etkinliklerini işleme

Google Play, faturalandırma etkinlikleri bulut pub/sub konusu olarak adlandırdığı etkinlikler sunar. Bunlar temel olarak iletilerin yayınlanabileceği ve gönderilebileceği ileti sıralarıdır.

Bu işlev Google Play'e özgü olduğundan, söz konusu işlev GooglePlayPurchaseHandler içinde yer almaktadır.

lib/google_play_purchase_handler.dart uygulamasını açıp PubsubApi içe aktarımını ekleyerek başlayın:

lib/google_play_purchase_handler.dart

import 'package:googleapis/pubsub/v1.dart' as pubsub;

Ardından, PubsubApi öğesini GooglePlayPurchaseHandler öğesine iletin ve sınıf oluşturucuyu aşağıdaki gibi bir Timer oluşturacak şekilde değiştirin:

lib/google_play_purchase_handler.dart

class GooglePlayPurchaseHandler extends PurchaseHandler {
  final ap.AndroidPublisherApi androidPublisher;
  final IapRepository iapRepository;
  final pubsub.PubsubApi pubsubApi; // new

  GooglePlayPurchaseHandler(
    this.androidPublisher,
    this.iapRepository,
    this.pubsubApi, // new
  ) {
    // Poll messages from Pub/Sub every 10 seconds
    Timer.periodic(Duration(seconds: 10), (_) {
      _pullMessageFromPubSub();
    });
  }

Timer, _pullMessageFromSubSub yöntemini her on saniyede bir çağıracak şekilde yapılandırılmıştır. Süre'yi kendi tercihinize göre ayarlayabilirsiniz.

Ardından, _pullMessageFromSubSub oluşturun

lib/google_play_purchase_handler.dart

  /// Process messages from Google Play
  /// Called every 10 seconds
  Future<void> _pullMessageFromPubSub() async {
    print('Polling Google Play messages');
    final request = pubsub.PullRequest(
      maxMessages: 1000,
    );
    final topicName =
        'projects/$googlePlayProjectName/subscriptions/$googlePlayPubsubBillingTopic-sub';
    final pullResponse = await pubsubApi.projects.subscriptions.pull(
      request,
      topicName,
    );
    final messages = pullResponse.receivedMessages ?? [];
    for (final message in messages) {
      final data64 = message.message?.data;
      if (data64 != null) {
        await _processMessage(data64, message.ackId);
      }
    }
  }

  Future<void> _processMessage(String data64, String? ackId) async {
    final dataRaw = utf8.decode(base64Decode(data64));
    print('Received data: $dataRaw');
    final dynamic data = jsonDecode(dataRaw);
    if (data['testNotification'] != null) {
      print('Skip test messages');
      if (ackId != null) {
        await _ackMessage(ackId);
      }
      return;
    }
    final dynamic subscriptionNotification = data['subscriptionNotification'];
    final dynamic oneTimeProductNotification =
        data['oneTimeProductNotification'];
    if (subscriptionNotification != null) {
      print('Processing Subscription');
      final subscriptionId =
          subscriptionNotification['subscriptionId'] as String;
      final purchaseToken = subscriptionNotification['purchaseToken'] as String;
      final productData = productDataMap[subscriptionId]!;
      final result = await handleSubscription(
        userId: null,
        productData: productData,
        token: purchaseToken,
      );
      if (result && ackId != null) {
        await _ackMessage(ackId);
      }
    } else if (oneTimeProductNotification != null) {
      print('Processing NonSubscription');
      final sku = oneTimeProductNotification['sku'] as String;
      final purchaseToken =
          oneTimeProductNotification['purchaseToken'] as String;
      final productData = productDataMap[sku]!;
      final result = await handleNonSubscription(
        userId: null,
        productData: productData,
        token: purchaseToken,
      );
      if (result && ackId != null) {
        await _ackMessage(ackId);
      }
    } else {
      print('invalid data');
    }
  }

  /// ACK Messages from Pub/Sub
  Future<void> _ackMessage(String id) async {
    print('ACK Message');
    final request = pubsub.AcknowledgeRequest(
      ackIds: [id],
    );
    final subscriptionName =
        'projects/$googlePlayProjectName/subscriptions/$googlePlayPubsubBillingTopic-sub';
    await pubsubApi.projects.subscriptions.acknowledge(
      request,
      subscriptionName,
    );
  }

Az önce eklediğiniz kod, her on saniyede bir Google Cloud'dan Pub/Sub konusu ile iletişim kurar ve yeni mesajlar ister. Ardından, her iletiyi _processMessage yönteminde işler.

Bu yöntem gelen mesajların kodunu çözer ve hem abonelik hem de abonelik olmayan her bir satın alma işlemiyle ilgili güncel bilgileri alır ve gerekirse mevcut handleSubscription veya handleNonSubscription çağırır.

Her mesajın _askMessage yöntemiyle onaylanması gerekir.

Ardından, gerekli bağımlılıkları server.dart dosyasına ekleyin. PubsubApi.cloudPlatformScope'u kimlik bilgisi yapılandırmasına ekleyin:

bin/server.dart

 final clientGooglePlay =
      await auth.clientViaServiceAccount(clientCredentialsGooglePlay, [
    ap.AndroidPublisherApi.androidpublisherScope,
    pubsub.PubsubApi.cloudPlatformScope, // new
  ]);

Daha sonra, PubsubApi örneğini oluşturun:

bin/server.dart

  final pubsubApi = pubsub.PubsubApi(clientGooglePlay);

Ve son olarak, bunu GooglePlayPurchaseHandler oluşturucuya iletin:

bin/server.dart

  return {
    'google_play': GooglePlayPurchaseHandler(
      androidPublisher,
      iapRepository,
      pubsubApi, // new
    ),
    'app_store': AppStorePurchaseHandler(
      iapRepository,
    ),
  };

Google Play kurulumu

Pub/sub konusundan faturalandırma etkinliklerini kullanmak için kodu yazdınız ancak pub/sub konusunu oluşturmadınız veya herhangi bir faturalandırma etkinliği yayınlamadınız. Bunu ayarlamanın zamanı geldi.

Öncelikle bir pub/sub konusu oluşturun:

  1. Google Cloud Console'da Cloud Pub/Sub sayfasını ziyaret edin.
  2. Firebase projenizde olduğunuzdan emin olun ve + Konu Oluştur'u tıklayın. d5ebf6897a0a8bf5.png
  3. Yeni konuya, constants.ts içinde GOOGLE_PLAY_PUBSUB_BILLING_TOPIC için ayarlanan değere benzer bir ad verin. Bu örnekte, etiketi play_billing olarak adlandırın. Başka bir seçenek seçerseniz constants.ts uygulamasını güncellemeyi unutmayın. Konuyu oluşturun. 20d690fc543c4212.png.
  4. Pub/Sub konularınızın listesinde, az önce oluşturduğunuz konuyla ilgili üç dikey noktayı ve ardından İzinleri görüntüle'yi tıklayın. ea03308190609fb.png
  5. Sağdaki kenar çubuğunda Ana hesap ekle'yi seçin.
  6. Buraya google-play-developer-notifications@system.gserviceaccount.com adlı kullanıcıyı ekleyin ve Pub/Sub Yayıncısı rolü verin. 55631ec0549215bc.png
  7. İzin değişikliklerini kaydedin.
  8. Yeni oluşturduğunuz konunun Konu adı'nı kopyalayın.
  9. Play Console'u tekrar açın ve Tüm Uygulamalar listesinden uygulamanızı seçin.
  10. Ekranı aşağı kaydırın ve Para kazanın > Para Kazanma Ayarları.
  11. Konunun tamamını doldurun ve değişikliklerinizi kaydedin. 7e5e875dc6ce5d54.png.

Tüm Google Play faturalandırma etkinlikleri artık konu hakkında yayınlanacak.

App Store faturalandırma etkinliklerini işleme

Sonra, aynı işlemi App Store faturalandırma etkinlikleri için de yapın. App Store'daki satın alma işlemlerinde güncellemeleri işlemenin iki etkili yolu vardır. Bunlardan biri, Apple'a sağladığınız ve Apple'ın sunucunuzla iletişim kurmak için kullandığı bir webhook'u uygulamaktır. Bu codelab'de yer alan ikinci yöntem ise App Store Server API'ye bağlanıp abonelik bilgilerini manuel olarak almaktır.

Bu codelab'in ikinci çözüme odaklanmasının nedeni, webhook'u uygulamak için sunucunuzu internete açık hale getirmenizin gerekmesidir.

Bir üretim ortamında, ideal olarak her ikisine de sahip olmanız gerekir. Bir etkinliği kaçırdıysanız veya abonelik durumunu tekrar kontrol etmeniz gerekirse App Store'dan ve Server API'den etkinlikleri almak için kullanılan webhook.

lib/app_store_purchase_handler.dart uygulamasını açıp AppStoreServerAPI bağımlılığını ekleyerek başlayın:

lib/app_store_purchase_handler.dart

final AppStoreServerAPI appStoreServerAPI;

AppStorePurchaseHandler(
  this.iapRepository,
  this.appStoreServerAPI, // new
)

_pullStatus yöntemini çağıracak bir zamanlayıcı eklemek için oluşturucuyu değiştirin. Bu zamanlayıcı, _pullStatus yöntemini 10 saniyede bir çağırır. Bu zamanlayıcı süresini ihtiyaçlarınıza göre ayarlayabilirsiniz.

lib/app_store_purchase_handler.dart

  AppStorePurchaseHandler(
    this.iapRepository,
    this.appStoreServerAPI,
  ) {
    // Poll Subscription status every 10 seconds.
    Timer.periodic(Duration(seconds: 10), (_) {
      _pullStatus();
    });
  }

Ardından, aşağıdaki gibi _pullStatus yöntemini oluşturun:

lib/app_store_purchase_handler.dart

  Future<void> _pullStatus() async {
    print('Polling App Store');
    final purchases = await iapRepository.getPurchases();
    // filter for App Store subscriptions
    final appStoreSubscriptions = purchases.where((element) =>
        element.type == ProductType.subscription &&
        element.iapSource == IAPSource.appstore);
    for (final purchase in appStoreSubscriptions) {
      final status =
          await appStoreServerAPI.getAllSubscriptionStatuses(purchase.orderId);
      // Obtain all subscriptions for the order id.
      for (final subscription in status.data) {
        // Last transaction contains the subscription status.
        for (final transaction in subscription.lastTransactions) {
          final expirationDate = DateTime.fromMillisecondsSinceEpoch(
              transaction.transactionInfo.expiresDate ?? 0);
          // Check if subscription has expired.
          final isExpired = expirationDate.isBefore(DateTime.now());
          print('Expiration Date: $expirationDate - isExpired: $isExpired');
          // Update the subscription status with the new expiration date and status.
          await iapRepository.updatePurchase(SubscriptionPurchase(
            userId: null,
            productId: transaction.transactionInfo.productId,
            iapSource: IAPSource.appstore,
            orderId: transaction.originalTransactionId,
            purchaseDate: DateTime.fromMillisecondsSinceEpoch(
                transaction.transactionInfo.originalPurchaseDate),
            type: ProductType.subscription,
            expiryDate: expirationDate,
            status: isExpired
                ? SubscriptionStatus.expired
                : SubscriptionStatus.active,
          ));
        }
      }
    }
  }

Bu yöntem aşağıdaki gibi çalışır:

  1. IapRepository'i kullanarak Firestore'dan etkin aboneliklerin listesini alır.
  2. Her sipariş için App Store Server API'ye abonelik durumu istenir.
  3. Söz konusu abonelik satın alma işlemi için son işlemi sağlar.
  4. Son kullanma tarihini kontrol eder.
  5. Firestore'daki abonelik durumunu günceller. Süresi dolmuşsa abonelik bu şekilde işaretlenir.

Son olarak, App Store Server API erişimini yapılandırmak için gereken tüm kodu ekleyin:

bin/server.dart

  // add from here
  final subscriptionKeyAppStore =
      File('assets/SubscriptionKey.p8').readAsStringSync();

  // Configure Apple Store API access
  var appStoreEnvironment = AppStoreEnvironment.sandbox(
    bundleId: bundleId,
    issuerId: appStoreIssuerId,
    keyId: appStoreKeyId,
    privateKey: subscriptionKeyAppStore,
  );

  // Stored token for Apple Store API access, if available
  final file = File('assets/appstore.token');
  String? appStoreToken;
  if (file.existsSync() && file.lengthSync() > 0) {
    appStoreToken = file.readAsStringSync();
  }

  final appStoreServerAPI = AppStoreServerAPI(
    AppStoreServerHttpClient(
      appStoreEnvironment,
      jwt: appStoreToken,
      jwtTokenUpdatedCallback: (token) {
        file.writeAsStringSync(token);
      },
    ),
  );
  // to here


  return {
    'google_play': GooglePlayPurchaseHandler(
      androidPublisher,
      iapRepository,
      pubsubApi,
    ),
    'app_store': AppStorePurchaseHandler(
      iapRepository,
      appStoreServerAPI, // new
    ),
  };

App Store kurulumu

Sonra, App Store'u kurun:

  1. App Store Connect'e giriş yapın, ardından Kullanıcılar ve Erişim'i seçin.
  2. Key Type > (Anahtar Türü) > Uygulama içi satın alma.
  3. "Artı"ya dokunun. simgesine dokunun.
  4. Bir ad verin, ör. "Codelab anahtarı".
  5. Anahtarı içeren p8 dosyasını indirin.
  6. Dosyayı SubscriptionKey.p8 adlı öğeler klasörüne kopyalayın.
  7. Yeni oluşturulan anahtarın anahtar kimliğini kopyalayın ve lib/constants.dart dosyasında appStoreKeyId sabit değeri olarak ayarlayın.
  8. Anahtar listesinin sağ üst tarafındaki Düzenleyen Kimliği'ni kopyalayın ve lib/constants.dart dosyasında appStoreIssuerId sabit değeri olarak ayarlayın.

9540ea9ada3da151.png

Cihazdaki satın alma işlemlerini izleme

Satın alma işlemlerinizi izlemenin en güvenli yolu sunucu tarafındadır. Çünkü istemcinin güvenliğini sağlamak zordur ancak uygulamanın abonelik durumu bilgilerine göre işlem yapabilmesi için bilgileri istemciye geri ulaştırmanın bir yolunu bulmanız gerekir. Satın alınan öğeleri Firestore'da saklayarak verileri istemciyle kolayca senkronize edebilir ve otomatik olarak güncellenmesini sağlayabilirsiniz.

IAPRepo'yu uygulamaya zaten eklediniz. Bu, List<PastPurchase> purchases uygulamasındaki kullanıcının tüm satın alma verilerini içeren Firestore deposudur. Depo ayrıca, süresi dolmamış productId storeKeySubscription değerine sahip bir satın alma olduğunda doğru olan hasActiveSubscription, değerini de içerir. Kullanıcı giriş yapmadığında liste boş olur.

lib/repo/iap_repo.dart

  void updatePurchases() {
    _purchaseSubscription?.cancel();
    var user = _user;
    if (user == null) {
      purchases = [];
      hasActiveSubscription = false;
      hasUpgrade = false;
      return;
    }
    var purchaseStream = _firestore
        .collection('purchases')
        .where('userId', isEqualTo: user.uid)
        .snapshots();
    _purchaseSubscription = purchaseStream.listen((snapshot) {
      purchases = snapshot.docs.map((DocumentSnapshot document) {
        var data = document.data();
        return PastPurchase.fromJson(data);
      }).toList();

      hasActiveSubscription = purchases.any((element) =>
          element.productId == storeKeySubscription &&
          element.status != Status.expired);

      hasUpgrade = purchases.any(
        (element) => element.productId == storeKeyUpgrade,
      );

      notifyListeners();
    });
  }

Tüm satın alma mantığı DashPurchases sınıfındadır ve aboneliklerin uygulanması veya kaldırılması gereken yerdir. Dolayısıyla, iapRepo öğesini sınıfa bir özellik olarak ekleyin ve oluşturucuda iapRepo öğesini atayın. Daha sonra, oluşturucuya doğrudan bir işleyici ekleyin ve dispose() yönteminde işleyiciyi kaldırın. Dinleyici, başlangıçta boş bir işlev olabilir. IAPRepo bir ChangeNotifier olduğundan ve Firestore'da her satın alma işlemi değiştiğinde notifyListeners() yöntemini çağırdığınızdan, satın alınan ürünler değiştiğinde her zaman purchasesUpdate() yöntemi çağrılır.

lib/logic/dash_purchases.dart

  IAPRepo iapRepo;

  DashPurchases(this.counter, this.firebaseNotifier, this.iapRepo) {
    final purchaseUpdated =
        iapConnection.purchaseStream;
    _subscription = purchaseUpdated.listen(
      _onPurchaseUpdate,
      onDone: _updateStreamOnDone,
      onError: _updateStreamOnError,
    );
    iapRepo.addListener(purchasesUpdate);
    loadPurchases();
  }

  @override
  void dispose() {
    iapRepo.removeListener(purchasesUpdate);
    _subscription.cancel();
    super.dispose();
  }

  void purchasesUpdate() {
    //TODO manage updates
  }

Ardından, IAPRepo öğesini main.dart. öğesinde oluşturucuya sağlayın. Zaten bir Provider içinde oluşturulduğu için context.read kullanarak depoyu alabilirsiniz.

lib/main.dart

        ChangeNotifierProvider<DashPurchases>(
          create: (context) => DashPurchases(
            context.read<DashCounter>(),
            context.read<FirebaseNotifier>(),
            context.read<IAPRepo>(),
          ),
          lazy: false,
        ),

Ardından, purchaseUpdate() işlevinin kodunu yazın. dash_counter.dart, ürününde applyPaidMultiplier ve removePaidMultiplier yöntemleri, çarpanı sırasıyla 10 veya 1 olarak ayarlar. Böylece aboneliğin önceden uygulanıp uygulanmadığını kontrol etmeniz gerekmez. Abonelik durumu değiştiğinde, satın alınabilir ürünün durumunu da güncellersiniz. Böylece, satın alma sayfasında ürünün zaten etkin olduğunu gösterebilirsiniz. _beautifiedDashUpgrade özelliğini, yükseltmenin satın alıp almadığına göre ayarlayın.

lib/logic/dash_purchases.dart

void purchasesUpdate() {
    var subscriptions = <PurchasableProduct>[];
    var upgrades = <PurchasableProduct>[];
    // Get a list of purchasable products for the subscription and upgrade.
    // This should be 1 per type.
    if (products.isNotEmpty) {
      subscriptions = products
          .where((element) => element.productDetails.id == storeKeySubscription)
          .toList();
      upgrades = products
          .where((element) => element.productDetails.id == storeKeyUpgrade)
          .toList();
    }

    // Set the subscription in the counter logic and show/hide purchased on the
    // purchases page.
    if (iapRepo.hasActiveSubscription) {
      counter.applyPaidMultiplier();
      for (var element in subscriptions) {
        _updateStatus(element, ProductStatus.purchased);
      }
    } else {
      counter.removePaidMultiplier();
      for (var element in subscriptions) {
        _updateStatus(element, ProductStatus.purchasable);
      }
    }

    // Set the Dash beautifier and show/hide purchased on
    // the purchases page.
    if (iapRepo.hasUpgrade != _beautifiedDashUpgrade) {
      _beautifiedDashUpgrade = iapRepo.hasUpgrade;
      for (var element in upgrades) {
        _updateStatus(
          element,
          _beautifiedDashUpgrade
              ? ProductStatus.purchased
              : ProductStatus.purchasable);
      }
      notifyListeners();
    }
  }

  void _updateStatus(PurchasableProduct product, ProductStatus status) {
    if (product.status != ProductStatus.purchased) {
      product.status = ProductStatus.purchased;
      notifyListeners();
    }
  }

Artık abonelik ve yükseltme durumunun arka uç hizmetinde her zaman güncel olduğundan ve uygulamayla senkronize edildiğinden emin oldunuz. Uygulama buna uygun şekilde hareket ederek Dash tıklama oyununa abonelik ve yükseltme özelliklerini uygular.

12. Hepsi bu kadar.

Tebrikler!!! Codelab'i tamamladınız. Bu codelab için tamamlanmış kodu android_studio_folder.pngeksiksiz klasörde bulabilirsiniz.

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