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 App ve Play mağazalarını doğru şekilde ayarlamanız, satın alma işlemini doğrulamanız ve abonelik avantajları gibi gerekli izinleri vermeniz gerekir.

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

  1. Tek seferde 2.000 Dash satın alabileceğiniz, tekrarlanabilir bir satın alma seçeneği.
  2. Eski tarz Dash'i modern tarz Dash'e dönüştürmek için tek seferlik bir yükseltme satın alma işlemi.
  3. Otomatik olarak oluşturulan tıklamaları iki katına çıkaran abonelik.

İlk satın alma seçeneği, kullanıcıya doğrudan 2.000 Dash avantajı sağlar. Bu öğeler doğrudan kullanıcı tarafından kullanılabilir ve birden çok kez satın alınabilir. Doğrudan tüketilen ve birden çok kez tüketilebilen bu öğeye tüketim öğesi denir.

İkinci seçenek, Dash\'i daha güzel bir Dash\'e yükseltir. Bu özellik yalnızca bir kez satın alınmalıdır ve sonsuza kadar kullanılabilir. Uygulama tarafından kullanılamamasına rağmen sonsuza kadar geçerli olduğu için bu tür satın alma işlemlerine "tükenebilir olmayan" denir.

Üçüncü ve son satın alma seçeneği aboneliktir. Abonelik etkinken kullanıcı daha hızlı Dash kazanır ancak abonelik için ödemeyi durdurduğunda avantajlar da sona erer.

Arka uç hizmeti (sizin için de sağlanır) bir Dart uygulaması olarak çalışır, satın alma işlemlerinin yapıldığını doğrular ve Firestore'u kullanarak bunları depolar. Firestore, süreci kolaylaştırmak için kullanılır ancak üretim uygulamanızda herhangi bir arka uç hizmeti türünü kullanabilirsiniz.

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

Ne oluşturacaksınız?

  • Bir uygulamayı, tüketim amaçlı satın alma işlemlerini ve abonelikleri destekleyecek şekilde genişletirsiniz.
  • Ayrıca, satın alınan öğeleri doğrulamak ve depolamak için bir Dart arka uç uygulaması da genişleteceksiniz.

Öğrenecekleriniz

  • App Store ve Play Store'u satın alınabilir ürünlerle yapılandırma.
  • Satın alma işlemlerini doğrulamak ve Firestore'da depolamak için mağazalarla iletişime geçme.
  • Uygulamanızdaki satın alma işlemlerini yönetme

İhtiyacınız olanlar

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

2. Geliştirme ortamını ayarlama

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

Kodu indirme

GitHub deposunu komut satırından kopyalamak için aşağıdaki komutu kullanın:

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

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

gh repo clone flutter/codelabs flutter-codelabs

Örnek kod, bir kod deneme koleksiyonu için kodu içeren flutter-codelabs dizine klonlanır. Bu codelab'in kodu flutter-codelabs/in_app_purchases'tedir.

flutter-codelabs/in_app_purchases altındaki dizin yapısı, adlandırılmış her adımın sonunda nerede olmanız gerektiğini gösteren bir dizi anlık görüntü içerir. Başlangıç kodu 0. adımdadır. Bu nedenle, eşleşen dosyaları bulmak için:

cd flutter-codelabs/in_app_purchases/step_00

İleri atlamak veya bir adımdan sonra bir şeyin nasıl görünmesi gerektiğini görmek istiyorsanız ilgilendiğiniz adlandırmaya sahip dizinde bakın. Son adımın kodu complete klasöründedir.

Başlangıç projesini oluşturma

Favori IDE'nizde step_00'ten başlangıç projesini açın. Ekran görüntüleri için Android Studio'yu kullandık ancak Visual Studio Code de iyi bir seçenektir. Her iki düzenleyicide de en son Dart ve Flutter eklentilerinin yüklü olduğundan emin olun.

Hazırlayacağınız uygulamaların, hangi ürünlerin hangi fiyattan satıldığını öğrenmek için App Store ve Play Store ile iletişim kurması gerekir. Her uygulama benzersiz bir kimlikle tanımlanır. iOS App Store'da buna paket tanımlayıcısı, Android Play Store'da ise uygulama kimliği denir. Bu tanımlayıcılar genellikle ters alan adı gösterimi kullanılarak oluşturulur. Örneğin, flutter.dev için bir uygulama içi satın alma uygulaması oluştururken dev.flutter.inapppurchase değerini kullanırsınız. Uygulamanız için bir tanımlayıcı düşünün. Bunu proje ayarlarında ayarlayacaksınız.

Öncelikle iOS için paket tanımlayıcısını ayarlayın.

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

942772eb9a73bfaa.png

Xcode'un klasör yapısında Runner projesi en üstte yer alır. Flutter, Runner ve Ürünler hedefleri ise Runner projesinin altındadır. Proje ayarlarınızı düzenlemek için Runner'ı çift tıklayın ve İmza ve Yetenekler'i tıklayın. Ekibinizi ayarlamak için Ekip alanının altına yeni seçtiğiniz paket tanımlayıcısını girin.

812f919d965c649a.jpeg

Artık Xcode'u kapatabilir ve Android yapılandırmasını tamamlamak için Android Studio'ya dönebilirsiniz. Bunu yapmak için android/app, altındaki build.gradle dosyasını açın ve applicationId değerinizi (aşağıdaki ekran görüntüsünde 37. satırda) iOS paket tanımlayıcısı ile aynı olan uygulama kimliğiyle değiştirin. iOS ve Android mağazalarının kimliklerinin aynı olması gerekmez ancak aynı tutulması hata olasılığını azaltır. Bu nedenle bu kod laboratuvarında da aynı tanımlayıcıları kullanacağız.

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 in_app_purchase'ü pubspec'e ekleyin:

$ cd app
$ flutter pub add in_app_purchase dev:in_app_purchase_platform_interface

pubspec.yaml dosyanızı açın ve dependencies altında in_app_purchase, dev_dependencies altında ise in_app_purchase_platform_interface girişinin listelendiğini onaylayın.

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cloud_firestore: ^5.5.1
  cupertino_icons: ^1.0.8
  firebase_auth: ^5.3.4
  firebase_core: ^3.8.1
  google_sign_in: ^6.2.2
  http: ^1.2.2
  intl: ^0.20.1
  provider: ^6.1.2
  in_app_purchase: ^3.2.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^4.0.0
  in_app_purchase_platform_interface: ^1.4.0

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

4. App Store'u ayarlama

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

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

11db9fca823e7608.png

Burada ücretsiz ve ücretli uygulamalarla ilgili sözleşmeleri görürsünüz. Ücretsiz uygulamaların durumu etkin, ücretli uygulamaların durumu ise yeni olmalıdır. Şartları görüntülediğinizden, kabul ettiğinizden ve gerekli tüm bilgileri girdiğinizden emin olun.

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 deneyemezsiniz. Bu nedenle, bu işlem çok önemlidir.

4a100bbb8cafdbbf.jpeg

Uygulama kimliğini kaydetme

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

55d7e592d9a3fc7b.png

Uygulama kimliklerini seçme

13f125598b72ca77.png

Uygulama Seçin

41ac4c13404e2526.png

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

9d2c940ad80deeef.png

Yeni uygulama kimliği oluşturma hakkında daha fazla bilgi için Geliştirici Hesabı Yardımı başlıklı makaleyi inceleyin .

Yeni uygulama oluşturma

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

10509b17fbf031bd.png

5b7c0bb684ef52c7.png

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

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

3ca2b26d4e391a4c.jpeg

Artık iPhone'unuzda Ayarlar > App Store > Korumalı alan hesabı'na giderek korumalı alan kullanıcınızı ayarlayabilirsiniz.

d99e0b89673867cd.jpeg e1621bcaeb33d3c5.jpeg

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

Ardından, satın alınabilecek üç öğeyi yapılandırın:

  • dash_consumable_2k: Çok sayıda satın alınabilen ve kullanıcıya her satın alma işleminde 2.000 Dash (uygulama içi para birimi) kazandıran, tüketilebilecek bir satın alma işlemi.
  • dash_upgrade_3d: Kullanıcıya tıklaması için görsel açıdan farklı bir Dash sunan, yalnızca bir kez satın alınabilen, tüketilemeyen bir "yükseltme" satın alma işlemi.
  • dash_subscription_doubler: Abonelik süresi boyunca kullanıcıya tıklama başına iki kat daha fazla kısa çizgi veren bir abonelik.

d156b2f5bac43ca8.png

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

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

  1. dash_consumable_2k öğesini tükenebilir olarak ayarlayın.

Ürün kimliği olarak dash_consumable_2k kullanın. Referans adı yalnızca App Store Connect'te kullanılır. Adı dash consumable 2k olarak ayarlayın ve satın alma işlemi için yerelleştirmelerinizi ekleyin. Satın alma işlemini Spring is in the air olarak adlandırın ve açıklama olarak 2000 dashes fly out kullanın.

ec1701834fd8527.png

  1. dash_upgrade_3d öğesini tükenmeyen olarak ayarlayın.

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

6765d4b711764c30.png

  1. dash_subscription_doublerotomatik yenilenen abonelik olarak ayarlayın.

Abonelikler için akış biraz farklıdır. Öncelikle referans adını ve ürün kimliğini ayarlamanız gerekir:

6d29e08dae26a0c4.png

Ardından, bir abonelik grubu oluşturmanız gerekir. Aynı grupta birden fazla abonelik olduğunda kullanıcılar aynı anda bunlardan yalnızca birine abone olabilir ancak bu abonelikler arasında kolayca geçiş yapabilir. Bu grubu subscriptions olarak adlandırmanız yeterlidir.

5bd0da17a85ac076.png

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

bd1b1d82eeee4cb3.png

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

d0bf39680ef0aa2e.png

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

99d5c4b446e8fecf.png

5. Play Store'u ayarlama

App Store'da olduğu gibi Play Store için de geliştirici hesabınız olmalıdır. Henüz bir hesabınız yoksa hesap kaydedin.

Yeni uygulama oluşturma

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

  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. Play Store kullanıcılarının bu uygulamayla ilgili olarak sizinle iletişim kurabileceğ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 gidip Uygulamanızı ayarlayın bölümündeki tüm görevleri tamamlayın. Burada, uygulamanızla ilgili içerik derecelendirmeleri ve ekran görüntüleri gibi bazı bilgileri sağlarsınız. 13845badcf9bc1db.png

Uygulamayı imzalama

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

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

Anahtar deposu oluşturma

Mevcut bir anahtar deponuz varsa sonraki adıma geçin. Aksi takdirde, komut satırında aşağıdaki komutu çalıştırarak bir tane oluşturun.

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

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

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

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

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

keystore

Dosyayı gizli tutun; herkese açık kaynak kontrolüne eklemeyin.

Uygulamadan anahtar deposuna referans verme

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

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

Gradle'de imzalamayı yapılandırma

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

android bloğundan önce properties dosyanızdaki anahtar mağazası bilgilerini ekleyin:

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

   android {
         // omitted
   }

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

Aşağıdaki kodu buildTypes bloğundan önce ekleyin:

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

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

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

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

Uygulamanızı imzalama hakkında daha fazla bilgi için developer.android.com adresindeki Uygulamanızı imzalama başlıklı makaleyi inceleyin.

İlk derlemenizi yükleme

Uygulamanız imzalama için yapılandırıldıktan sonra aşağıdakileri çalıştırarak uygulamanızı derleyebilirsiniz:

flutter build appbundle

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

Google Play Console'daki kontrol panelinde Sürüm > Test > Kapalı test'e gidin ve yeni bir kapalı test sürümü oluşturun.

Bu kod laboratuvarında uygulamayı Google'ın imzalamasını tercih edeceğiz. Bu nedenle, Play Uygulama İmzalama bölümünde Devam'a basarak bu özelliği etkinleştirin.

ba98446d9c5c40e0.png

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

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

Son olarak, dahili test sürümünü etkinleştirmek için Dahili test için yayınlamaya başla'yı tıklayın.

Test kullanıcıları oluşturma

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

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

Öncelikle test kullanıcısını dahili test kanalına ekleyin. Sürüm > Test > Dahili test'e geri dönün ve Test kullanıcıları sekmesini tıklayın.

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 alma işlemlerini test etmeye erişmesi gereken Google Hesaplarının e-posta adreslerini ekleyin.

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

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

  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

Artık uygulama içinde satın alınabilecek öğeleri yapılandıracaksınız.

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

  • dash_consumable_2k: Çok sayıda satın alınabilen ve kullanıcıya her satın alma işleminde 2.000 Dash (uygulama içi para birimi) kazandıran, tüketilebilecek bir satın alma işlemi.
  • dash_upgrade_3d: Kullanıcıya tıklaması için görsel olarak farklı bir Dash sunan, yalnızca bir kez satın alınabilen, tüketilemeyen bir "yükseltme" satın alma işlemi.
  • dash_subscription_doubler: Abonelik süresi boyunca kullanıcıya tıklama başına iki kat daha fazla kısa çizgi veren bir abonelik.

Öncelikle tüketilebilir ve tüketilemez öğeleri ekleyin.

  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ınc8d66e32f57dee21.png
  4. Ürününüzle ilgili gerekli tüm bilgileri girin. Ürün kimliğinin, kullanmak istediğiniz kimlikle tam olarak eşleştiğinden emin olun.
  5. Kaydet'i tıklayın.
  6. Etkinleştir'i tıklayın.
  7. Kullanıma sunulmuş olmayan "yükseltme" satın alma işlemi için bu işlemi 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ın32a6a9eefdb71dd0.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şlemleriniz Play Console'da ayarlanmış olmalıdır.

6. Firebase'i ayarlama

Bu codelab'de, kullanıcıların satın alma işlemlerini doğrulamak ve izlemek için bir arka uç hizmeti kullanacaksınız.

Arka uç hizmeti kullanmanın bazı avantajları vardır:

  • İşlemleri güvenli bir şekilde doğrulayabilirsiniz.
  • Faturalandırma etkinliklerine uygulama mağazalarından yanıt verebilirsiniz.
  • Satın alma işlemlerini bir veritabanında takip edebilirsiniz.
  • Kullanıcılar, sistem saatlerini geri sararak uygulamanızı premium özellikler sunmaya kandıramaz.

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

Arka uç yazmak bu codelab'in kapsamı dışındadır. Bu nedenle, başlangıçtaki kodda, işe başlamanıza yardımcı olmak için temel satın alma işlemlerini yöneten bir Firebase projesi zaten mevcuttur.

Başlatıcı uygulamaya Firebase eklentileri de dahildir.

Geriye kendi Firebase projenizi oluşturmak, hem uygulamayı hem de arka uç sunucusunu Firebase için yapılandırmak ve son olarak arka uç sunucusunu dağıtmak kalıyor.

Firebase projesi oluşturma

Firebase Konsolu'na gidip yeni bir Firebase projesi oluşturun. Bu örnekte projeyi Dash Clicker olarak adlandırın.

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

  1. Firebase kontrol panelinden Kimlik doğrulaması'na gidin ve gerekirse bu özelliği 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

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

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

Flutter için Firebase'i ayarlama

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

flutterfire configure'i çalıştırırken önceki adımda oluşturduğunuz projeyi seçin.

$ flutterfire configure

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

Ardından, iki platformu seçerek iOS ve Android'i etkinleştirin.

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

firebase_options.dart dosyasını geçersiz kılma hakkında soru sorulduğunda evet'i seçin.

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

Android için Firebase'i ayarlama: Sonraki adımlar

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

Uygulamalarınız'a gidip dashclicker (android) uygulamasını seçin.

b22d46a759c0c834.png

Hata ayıklama modunda Google ile oturum açma özelliğine izin vermek için hata ayıklama sertifikanızı SHA-1 karma oluşturma parmak izini sağlamanız gerekir.

Hata ayıklama imzalama sertifikanızı alma

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

cd android
./gradlew :app:signingReport

Size çok sayıda imzalama anahtarı listesi gösterilir. Hata ayıklama sertifikasının karma oluşturma işlevini aradığınız için Variant ve Config özelliklerinin debug olarak ayarlandığı sertifikayı arayın. Anahtar deposunun, .android/debug.keystore altındaki ana klasörünüzde olması muhtemeldir.

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

SHA-1 karmasını kopyalayın ve uygulama gönderme modal iletişim kutusunun son alanını doldurun.

iOS için Firebase'i ayarlama: Sonraki adımlar

ios/Runnder.xcworkspace dosyasını Xcode ile açın. Dilerseniz tercih ettiğiniz IDE ile de kullanabilirsiniz.

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

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

iOS'te Google ile oturum açma özelliğine izin vermek için derleme plist dosyalarınıza CFBundleURLTypes yapılandırma seçeneğini ekleyin. (Daha fazla bilgi için google_sign_in paketi belgelerini inceleyin.) Bu durumda dosyalar ios/Runner/Info-Debug.plist ve ios/Runner/Info-Release.plist olur.

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

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

Firebase kurulumu tamamlandı.

7. Satın alma güncellemelerini dinleme

Codelab'in bu bölümünde, uygulamayı ürün satın almaya hazır hale getireceksiniz. Bu işlem, uygulama başladıktan sonra satın alma güncellemelerini ve hatalarını dinlemeyi içerir.

Satın alma güncellemelerini dinleme

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

Varsayılan olarak, bir sağlayıcının nesnesi, söz konusu nesne ilk kez istendiğinde tanımlanır. Bu nesne, satın alma güncellemelerini doğrudan uygulama başladığında dinler. Bu nedenle, lazy: false ile bu nesnede yavaş yüklemeyi devre dışı bırakın:

lib/main.dart

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

Ayrıca InAppPurchaseConnection örneğine de ihtiyacınız vardır. Ancak uygulamanın test edilebilir kalması için bağlantıyı taklit etmeniz gerekir. Bunu yapmak için testte geçersiz kılınabilecek bir örnek yöntemi oluşturun ve main.dart öğesine ekleyin.

lib/main.dart

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

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

Testin çalışmaya devam etmesini istiyorsanız testi biraz güncellemeniz gerekir.

test/widget_test.dart

import 'package:dashclicker/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase/in_app_purchase.dart';     // Add this import
import 'package:in_app_purchase_platform_interface/src/in_app_purchase_platform_addition.dart'; // And this import

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

class TestIAPConnection implements InAppPurchase {         // Add from here
  @override
  Future<bool> buyConsumable(
      {required PurchaseParam purchaseParam, bool autoConsume = true}) {
    return Future.value(false);
  }

  @override
  Future<bool> buyNonConsumable({required PurchaseParam purchaseParam}) {
    return Future.value(false);
  }

  @override
  Future<void> completePurchase(PurchaseDetails purchase) {
    return Future.value();
  }

  @override
  Future<bool> isAvailable() {
    return Future.value(false);
  }

  @override
  Future<ProductDetailsResponse> queryProductDetails(Set<String> identifiers) {
    return Future.value(ProductDetailsResponse(
      productDetails: [],
      notFoundIDs: [],
    ));
  }

  @override
  T getPlatformAddition<T extends InAppPurchasePlatformAddition?>() {
    // TODO: implement getPlatformAddition
    throw UnimplementedError();
  }

  @override
  Stream<List<PurchaseDetails>> get purchaseStream =>
      Stream.value(<PurchaseDetails>[]);

  @override
  Future<void> restorePurchases({String? applicationUserName}) {
    // TODO: implement restorePurchases
    throw UnimplementedError();
  }

  @override
  Future<String> countryCode() {
    // TODO: implement countryCode
    throw UnimplementedError();
  }
}                                                          // To here.

lib/logic/dash_purchases.dart'te DashPurchases ChangeNotifier için kodu bulun. Şu anda satın aldığınız Dash'lara yalnızca DashCounter ekleyebilirsiniz.

Bir akış aboneliği özelliği (_subscription, StreamSubscription<List<PurchaseDetails>> _subscription; türü), IAPConnection.instance, ve içe aktarma işlemleri ekleyin. Sonuçta elde edilen kod aşağıdaki gibi görünmelidir:

lib/logic/dash_purchases.dart

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';     // Add this import

import '../main.dart';                                     // And this import
import '../model/purchasable_product.dart';
import '../model/store_state.dart';
import 'dash_counter.dart';

class DashPurchases extends ChangeNotifier {
  DashCounter counter;
  StoreState storeState = StoreState.available;
  late StreamSubscription<List<PurchaseDetails>> _subscription;  // Add this line
  List<PurchasableProduct> products = [
    PurchasableProduct(
      'Spring is in the air',
      'Many dashes flying out from their nests',
      '\$0.99',
    ),
    PurchasableProduct(
      'Jet engine',
      'Doubles you clicks per second for a day',
      '\$1.99',
    ),
  ];

  bool get beautifiedDash => false;

  final iapConnection = IAPConnection.instance;            // And this line

  DashPurchases(this.counter);

  Future<void> buy(PurchasableProduct product) async {
    product.status = ProductStatus.pending;
    notifyListeners();
    await Future<void>.delayed(const Duration(seconds: 5));
    product.status = ProductStatus.purchased;
    notifyListeners();
    await Future<void>.delayed(const Duration(seconds: 5));
    product.status = ProductStatus.purchasable;
    notifyListeners();
  }
}

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

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

lib/logic/dash_purchases.dart

class DashPurchases extends ChangeNotifier {
  DashCounter counter;
  StoreState storeState = StoreState.notAvailable;         // Modify this line
  late StreamSubscription<List<PurchaseDetails>> _subscription;
  List<PurchasableProduct> products = [
    PurchasableProduct(
      'Spring is in the air',
      'Many dashes flying out from their nests',
      '\$0.99',
    ),
    PurchasableProduct(
      'Jet engine',
      'Doubles you clicks per second for a day',
      '\$1.99',
    ),
  ];

  bool get beautifiedDash => false;

  final iapConnection = IAPConnection.instance;

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

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

  Future<void> buy(PurchasableProduct product) async {
    product.status = ProductStatus.pending;
    notifyListeners();
    await Future<void>.delayed(const Duration(seconds: 5));
    product.status = ProductStatus.purchased;
    notifyListeners();
    await Future<void>.delayed(const Duration(seconds: 5));
    product.status = ProductStatus.purchasable;
    notifyListeners();
  }
                                                           // Add from here
  void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
    // Handle purchases here
  }

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

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

Artık uygulama, satın alma güncellemelerini alıyor. Bu nedenle, bir sonraki bölümde satın alma işlemi gerçekleştireceksiniz.

Devam etmeden önce, her şeyin doğru şekilde ayarlandığından emin olmak için testleri "flutter test" ile çalıştırın.

$ flutter test

00:01 +1: All tests passed!                                                                                   

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

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

PurchasableProduct'ı uyarlama

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

lib/model/purchasable_product.dart

import 'package:in_app_purchase/in_app_purchase.dart';

enum ProductStatus {
  purchasable,
  purchased,
  pending,
}

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

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

dash_purchases.dart, dosyasında, sahte satın alma işlemlerini kaldırın ve boş bir listeyle değiştirin, List<PurchasableProduct> products = [];

Mevcut satın alma işlemlerini yükleme

Kullanıcının satın alma işlemi gerçekleştirebilmesi için satın alma işlemlerini mağazadan yükleyin. Öncelikle mağazanın kullanılabilir olup olmadığını kontrol edin. Mağaza kullanılamadığında storeState değerini notAvailable olarak ayarladığınızda kullanıcıya bir hata mesajı gösterilir.

lib/logic/dash_purchases.dart

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

Mağaza kullanıma sunulduğunda mevcut satın alma işlemlerini yükleyin. Önceki Firebase kurulumuna göre storeKeyConsumable, storeKeySubscription, ve storeKeyUpgrade değerlerini görebilirsiniz. Beklenen bir satın alma işlemi mevcut değilse bu bilgileri konsola yazdırın. Bu bilgileri arka uç hizmetine de gönderebilirsiniz.

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

lib/logic/dash_purchases.dart

import '../constants.dart';

  Future<void> loadPurchases() async {
    final available = await iapConnection.isAvailable();
    if (!available) {
      storeState = StoreState.notAvailable;
      notifyListeners();
      return;
    }
    const ids = <String>{
      storeKeyConsumable,
      storeKeySubscription,
      storeKeyUpgrade,
    };
    final response = await iapConnection.queryProductDetails(ids);
    products = response.productDetails.map((e) => PurchasableProduct(e)).toList();
    storeState = StoreState.available;
    notifyListeners();
  }

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

lib/logic/dash_purchases.dart

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

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

lib/logic/dash_purchases.dart

StoreState storeState = StoreState.loading;

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

purchase_page.dart dosyasını ele alalım. PurchasePage widget'ı, StoreState'e bağlı olarak _PurchasesLoading, _PurchaseList, veya _PurchasesNotAvailable, gösterir. Widget'ta, sonraki adımda kullanılan kullanıcının geçmiş satın alma işlemleri de gösterilir.

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

lib/pages/purchase_page.dart

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

Doğru şekilde yapılandırılmışlarsa Android ve iOS mağazalarında mevcut ürünleri görebilirsiniz. Satın alma işlemlerinin ilgili konsollara girildikten sonra kullanılabilir hale gelmesinin biraz zaman alabileceğini unutmayın.

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 olmayanlardan ayırmanız gerekir. Yükseltme ve abonelik ürünleri tüketim amaçlı değildir.

lib/logic/dash_purchases.dart

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

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

lib/logic/dash_purchases.dart

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

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

lib/logic/dash_purchases.dart

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

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

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

9. Arka ucu ayarlama

Satın alma işlemlerini izlemeye ve doğrulamaya geçmeden önce, bu işlemleri desteklemek için bir Dart arka uç ayarlayın.

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

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

Temel projeye genel bakış

Bu projenin bazı bölümleri bu kod laboratuvarının kapsamı dışında kabul edildiğinden başlangıç koduna eklenmiştir. Başlamadan önce, nasıl bir yapı oluşturacağınız hakkında fikir edinmek için başlangıç kodunda neler olduğunu incelemeniz önerilir.

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

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

$ dart ./bin/server.dart

Serving at http://0.0.0.0:8080

Dart arka ucu, API uç noktalarını sunmak için shelf ve shelf_router kullanır. Sunucu varsayılan olarak herhangi bir rota sağlamaz. Daha sonra satın alma doğrulama sürecini yönetecek bir rota oluşturacaksınız.

Başlangıç koduna dahil olan bir bölüm, lib/iap_repository.dart içindeki IapRepository öğesidir. Firestore veya genel olarak veritabanlarıyla nasıl etkileşime geçeceğinizi öğrenmenin bu codelab ile alakalı olmadığı düşünüldüğünden, başlangıç kodunda Firestore'da satın alma işlemleri oluşturmanıza veya güncellemenize olanak tanıyan işlevlerin yanı sıra bu satın alma işlemlerine ait tüm sınıflar yer alır.

Firebase erişimini ayarlama

Firebase Firestore'a erişmek için bir hizmet hesabı erişim anahtarına ihtiyacınız vardır. Firebase proje ayarlarını açıp Hizmet hesapları bölümüne gidip Yeni özel anahtar oluştur'u seçerek bir anahtar oluşturun.

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 için Play Store'a erişmek istiyorsanız bu izinlere sahip bir hizmet hesabı oluşturmanız ve bu hesabın JSON kimlik bilgilerini indirmeniz gerekir.

  1. Google Play Console'a gidin ve Tüm uygulamalar sayfasından başlayın.
  2. Kurulum > API erişimi'ne gidin. 317fdfb54921f50e.png Google Play Console'un yeni bir proje oluşturmanızı veya mevcut bir projeye bağlantı oluşturmanızı istemesi durumunda önce bunu yapın ve ardından bu sayfaya dönün.
  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 listesinde 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, isteğe bağlı olarak amacını hatırlayabilmeniz için bir açıklama da 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 geri dönün ve Hizmet hesaplarını yenile'yi tıklayın. Yeni oluşturduğunuz hesabı listede görürsünüz. 5895a7db8b4c7659.png
  10. Yeni hizmet hesabınız için Erişim izni ver'i tıklayın.
  11. Bir sonraki sayfayı aşağı kaydırarak Finansal veriler bloğuna gidin. Hem Finansal verileri, siparişleri ve iptal anketine verilen yanıtları görüntüleme hem de Siparişleri ve abonelikleri yönetme'yi seçin. 75b22d0201cf67e.png
  12. Kullanıcı davet et'i tıklayın. 70ea0b1288c62a59.png
  13. Hesap oluşturulduğuna göre bazı kimlik bilgileri oluşturmanız yeterlidir. Cloud Console'a geri dönüp hizmet hesapları listesinde hizmet hesabınızı bulun, üç dikey noktayı tıklayın ve Anahtarları yönet'i seçin. 853ee186b0e9954e.png
  14. Yeni bir JSON anahtarı oluşturun ve indirin. 2a33a55803f5299c.png cb4bf48ebac0364e.png
  15. İndirilen dosyayı service-account-google-play.json, olarak yeniden adlandırın ve assets/ dizinine taşıyın.

Yapmamız gereken bir şey daha var. lib/constants.dart, dosyasını açıp androidPackageId değerini Android uygulamanız için seçtiğiniz paket kimliğiyle değiştirin.

Apple App Store erişimini ayarlama

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

  1. App Store Connect'i açın.
  2. Uygulamalarım'a gidip uygulamanızı seçin.
  3. Kenar çubuğu gezinme menüsünde Uygulama İçi Satın Alma İşlemleri > Yönet'e gidin.
  4. Listenin sağ üst kısmında Uygulama Özgün Ortak Gizli Anahtarı'nı tıklayın.
  5. Yeni bir gizli anahtar oluşturun ve kopyalayın.
  6. lib/constants.dart, dosyasını açın ve appStoreSharedSecret değerini, yeni oluşturduğunuz paylaşılan gizli anahtarla değiştirin.

d8b8042470aaeff.png

b72f4565750e2f40.png

Sabitler yapılandırma dosyası

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

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

Sabitlerin geri kalanını şimdilik yoksayabilirsiniz.

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

Satın alma işlemlerini doğrulamayla ilgili genel akış, iOS ve Android için benzerdir.

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

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

Arka uç hizmeti daha sonra satın alma işlemini depolayabilir ve uygulamaya satın alma işleminin geçerli olup olmadığını yanıtlayabilir.

Arka uç hizmetinin, kullanıcınızın cihazında çalışan uygulama yerine mağazalarla doğrulama yapmasını sağlayarak kullanıcının sistem saatini geri sararak premium özelliklere erişmesini önleyebilirsiniz.

Flutter tarafını ayarlama

Kimlik doğrulamayı ayarlama

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

lib/pages/purchase_page.dart

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

class PurchasePage extends StatelessWidget {  
  const PurchasePage({super.key});

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

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

Uygulamadan arama doğrulama uç noktası

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

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

Uygulama sabitlerinde sunucu IP'sini yerel makinenizin IP adresine göre yapılandırın.

lib/logic/dash_purchases.dart

  FirebaseNotifier firebaseNotifier;

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

main.dart:'de DashPurchases oluşturulurken firebaseNotifier'ü ekleme

lib/main.dart

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

Kullanıcı kimliğini satın alma işlemini doğrulama işlevine iletebilmeniz için FirebaseNotifier'a kullanıcı için bir alıcı ekleyin.

lib/logic/firebase_notifier.dart

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

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

lib/logic/dash_purchases.dart

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

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

lib/logic/dash_purchases.dart

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

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

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

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

Uygulamada satın alma işlemlerini doğrulamaya hazırsınız.

Arka uç hizmetini ayarlama

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

Satın alma işleyicileri oluşturma

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

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 dışı satın alma işlemleri) doğrulamak için iki soyut yöntem içeren soyut bir PurchaseHandler sınıfı tanımlayın.

lib/purchase_handler.dart

import 'products.dart';

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

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

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

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

  • userId: Oturum açmış kullanıcının kimliğidir. Böylece satın alma işlemlerini kullanıcıya bağlayabilirsiniz.
  • productData: Ürünle ilgili veriler. Bunu bir dakika içinde tanımlayacaksınız.
  • token: Mağaza tarafından kullanıcıya sağlanan jeton.

Ayrıca, bu satın alma işleyicilerinin kullanımını kolaylaştırmak için hem abonelikler hem de abonelik dışı ürünler için kullanılabilecek bir verifyPurchase() yöntemi ekleyin:

lib/purchase_handler.dart

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

Artık her iki durum için de verifyPurchase işlevini çağırabilirsiniz ancak ayrı uygulamalara sahip olabilirsiniz.

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

lib/products.dart

class ProductData {
  final String productId;
  final ProductType type;

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

ProductType, abonelik veya abonelik dışı olabilir.

lib/products.dart

enum ProductType {
  subscription,
  nonSubscription,
}

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

lib/products.dart

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

Ardından, Google Play Store ve Apple App Store için bazı yer tutucu uygulamaları tanımlayın. Google Play'den başlayın:

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

lib/google_play_purchase_handler.dart

import 'dart:async';

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

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

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

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

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

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

Şu anda işleyici yöntemleri için true döndürüyor. Bu yöntemlere daha sonra değineceğiz.

Fark etmiş olabileceğiniz gibi, kurucu IapRepository örneği alır. Satın alma işleyicisi, daha sonra satın alma işlemleriyle ilgili bilgileri Firestore'da depolamak için bu örneği kullanır. Google Play ile iletişim kurmak için sağlanan AndroidPublisherApi'i kullanırsınız.

Ardından, uygulama mağazası işleyicisi için de aynı işlemi yapın. lib/app_store_purchase_handler.dart sınıfını oluşturun ve PurchaseHandler sınıfını genişleten bir sınıf daha ekleyin:

lib/app_store_purchase_handler.dart

import 'dart:async';

import 'package:app_store_server_sdk/app_store_server_sdk.dart';

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

class AppStorePurchaseHandler extends PurchaseHandler {
  final IapRepository iapRepository;

  AppStorePurchaseHandler(
    this.iapRepository,
  );

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

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

Mükemmel! Artık iki satın alma işleyiciniz var. Ardından, satın alma işlemi doğrulama API'si uç noktasını oluşturalım.

Satın alma işleyicilerini kullanma

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

bin/server.dart

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

  final purchaseHandlers = await _createPurchaseHandlers();

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

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

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

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

  await serveHandler(router.call);
}

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

Yukarıdaki kod aşağıdaki işlemleri gerçekleştirir:

  1. Daha önce oluşturduğunuz uygulamadan çağrılacak bir POST uç noktası tanımlayın.
  2. JSON yükünün kodunu çözerek aşağıdaki bilgileri ayıklayın:
  3. userId: Şu anda giriş yapmış kullanıcının kimliği
  4. source: Kullanılan mağaza (app_store veya google_play).
  5. productData: Daha önce oluşturduğunuz productDataMap kaynağından alınır.
  6. token: Mağazalara gönderilecek doğrulama verilerini içerir.
  7. Kaynağa bağlı olarak GooglePlayPurchaseHandler veya AppStorePurchaseHandler için verifyPurchase yöntemini çağırın.
  8. Doğrulama başarılı olursa yöntem, müşteriye 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şleyicisini yapılandırmanız gerekir. Bunun için önceki adımda edindiğiniz hizmet hesabı anahtarlarını yüklemeniz ve Android Publisher API ile Firebase Firestore API gibi farklı hizmetlere erişimi yapılandırmanız gerekir. Ardından, farklı bağımlılıklara sahip iki satın alma işleyicisi oluşturun:

bin/server.dart

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

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

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

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

Ardından, Google Play satın alma işleyicisini uygulamaya devam edin.

Google, satın alma işlemlerini doğrulamak için ihtiyaç duyduğunuz API'lerle etkileşime geçmek üzere Dart paketleri sağlamaktadır. Bu değişkenleri server.dart dosyasında başlattınız ve şimdi GooglePlayPurchaseHandler sınıfında kullanıyorsunuz.

Abonelik dışı satın alma işlemleri için işleyiciyi uygulayın:

lib/google_play_purchase_handler.dart

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

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

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

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

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

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

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

lib/google_play_purchase_handler.dart

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

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

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

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

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

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

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

lib/google_play_purchase_handler.dart

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

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

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

Google Play satın alma işlemleriniz doğrulanmış ve veritabanında depolanmıştır.

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

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

App Store'daki satın alma işlemlerini doğrulamak için süreci kolaylaştıran app_store_server_sdk adlı bir üçüncü taraf Dart paketi vardır.

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

lib/app_store_purchase_handler.dart

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

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

lib/app_store_purchase_handler.dart

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

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

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

Ardından handleValidation'ü uygulayın:

lib/app_store_purchase_handler.dart

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

App Store satın alma işlemleriniz doğrulandı ve veritabanında saklandı.

Arka ucu çalıştırma

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

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

11. Satın alma işlemlerinizi takip edin

Kullanıcılarınızın satın alma işlemlerini izlemenin önerilen yolu arka uç hizmetindedir. Bunun nedeni, arka uç sunucunuzun mağazadaki etkinliklere yanıt verebilmesi ve bu nedenle önbelleğe alma nedeniyle eski bilgilerle karşılaşma olasılığının daha düşük olması ve ayrıca sunucunun değiştirilme olasılığının daha düşük olmasıdır.

Öncelikle, oluşturmakta olduğunuz Dart arka ucuyla arka uçta mağaza etkinliklerinin işlenmesini ayarlayın.

Mağaza etkinliklerini arka uçta işleme

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

Google Play faturalandırma etkinliklerini işleme

Google Play, cloud pub/sub konusu olarak adlandırdığı bir yöntemle faturalandırma etkinlikleri sağlar. Bunlar temel olarak, mesajların yayınlanabileceği ve tüketilebileceği mesaj sıralarıdır.

Bu işlev Google Play'e özgü olduğundan bu işlevi GooglePlayPurchaseHandler bölümüne dahil edersiniz.

lib/google_play_purchase_handler.dart'ü açıp PubsubApi içe aktarma işlemini ekleyerek başlayın:

lib/google_play_purchase_handler.dart

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

Ardından PubsubApi öğesini GooglePlayPurchaseHandler öğesine iletin ve sınıf kurucusunu aşağıdaki gibi değiştirerek bir Timer oluşturun:

lib/google_play_purchase_handler.dart

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

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

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

Ardından _pullMessageFromSubSub

lib/google_play_purchase_handler.dart

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

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

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

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

Bu yöntem, gelen mesajların kodunu çözer ve gerekirse mevcut handleSubscription veya handleNonSubscription öğesini çağırarak her satın alma işlemi (hem abonelikler hem de abonelik dışı satın alma işlemleri) hakkında güncel bilgileri alır.

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

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

bin/server.dart

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

Ardından PubsubApi örneğini oluşturun:

bin/server.dart

  final pubsubApi = pubsub.PubsubApi(clientGooglePlay);

Son olarak, GooglePlayPurchaseHandler oluşturucuya iletin:

bin/server.dart

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

Google Play kurulumu

Pub/Sub konusundan faturalandırma etkinliklerini tüketecek kodu yazdınız ancak pub/sub konusunu oluşturmadınız ve faturalandırma etkinliği yayınlamıyorsunuz. Ayarlama zamanı geldi.

Öncelikle bir pub/sub konusu oluşturun:

  1. Google Cloud Console'daki 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ğerle aynı bir ad verin. Bu durumda dosyayı play_billing olarak adlandırın. Başka bir şey seçerseniz constants.ts değerini güncellediğinizden emin olun. Konuyu oluşturun. 20d690fc543c4212.png
  4. Yayın/abonelik konularınızın listesinde, az önce oluşturduğunuz konunun üç dikey noktasını ve ardından İzinleri görüntüle'yi tıklayın. ea03308190609fb.png
  5. Sağdaki kenar çubuğunda Müdür ekle'yi seçin.
  6. Burada google-play-developer-notifications@system.gserviceaccount.com hesabını ekleyin ve bu hesaba Pub/Sub yayıncısı rolünü verin. 55631ec0549215bc.png
  7. İzin değişikliklerini kaydedin.
  8. Az önce oluşturduğunuz konunun Konu adını kopyalayın.
  9. Play Console'u tekrar açıp Tüm Uygulamalar listesinden uygulamanızı seçin.
  10. Aşağı kaydırarak Para kazanma > Para kazanma kurulumu'na gidin.
  11. Konuyu eksiksiz doldurup değişikliklerinizi kaydedin. 7e5e875dc6ce5d54.png

Artık tüm Google Play faturalandırma etkinlikleri bu konuda yayınlanacak.

App Store faturalandırma etkinliklerini işleme

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

Bu kod laboratuvarının ikinci çözüme odaklanmasının nedeni, webhook'u uygulamak için sunucunuzu internete açmanız gerekmesidir.

Üretim ortamında ideal olarak her ikisine de sahip olmak istersiniz. App Store'dan etkinlik almak için webhook ve bir etkinliği kaçırmanız veya abonelik durumunu tekrar kontrol etmeniz durumunda Server API.

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

lib/app_store_purchase_handler.dart

final AppStoreServerAPI appStoreServerAPI;

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

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

lib/app_store_purchase_handler.dart

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

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

lib/app_store_purchase_handler.dart

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

Bu yöntem şu şekilde çalışır:

  1. IapRepository'yi kullanarak Firestore'dan etkin aboneliklerin listesini alır.
  2. Her sipariş için App Store Server API'den abonelik durumunu ister.
  3. Söz konusu abonelik satın alma işleminin son işlemini alır.
  4. Son kullanma tarihini kontrol eder.
  5. Firestore'da abonelik durumunu günceller. Süresi dolan abonelikler bu şekilde işaretlenir.

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

bin/server.dart

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

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

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

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


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

App Store kurulumu

Ardından App Store'u ayarlayın:

  1. App Store Connect'e giriş yapın ve Kullanıcılar ve Erişim'i seçin.
  2. Anahtar Türü > Uygulama İçi Satın Alma'ya gidin.
  3. Yeni bir kampanya eklemek için "artı" simgesine dokunun.
  4. "Codelab anahtarı" gibi bir ad verin.
  5. Anahtarı içeren p8 dosyasını indirin.
  6. Dosyayı SubscriptionKey.p8 adıyla öğeler klasörüne kopyalayın.
  7. Yeni oluşturulan anahtardaki anahtar kimliğini kopyalayıp lib/constants.dart dosyasında appStoreKeyId sabit değerine ayarlayın.
  8. Anahtar listesinin en üstündeki Issuer ID'yi kopyalayın ve lib/constants.dart dosyasında appStoreIssuerId sabit değerine ayarlayın.

9540ea9ada3da151.png

Cihazdaki satın almaları izleme

Satın alma işlemlerinizi izlemenin en güvenli yolu sunucu tarafındadır. Bunun nedeni, istemcinin güvenliğinin sağlanmasının zor olmasıdır. Ancak uygulamanın abonelik durumu bilgilerine göre işlem yapabilmesi için bilgileri istemciye geri göndermeniz gerekir. Satın alma işlemlerini Firestore'da depolayarak verileri istemciyle kolayca senkronize edebilir ve otomatik olarak güncel tutabilirsiniz.

List<PastPurchase> purchases'de kullanıcının tüm satın alma verilerini içeren Firestore deposu olan IAPRepo'yu uygulamaya zaten eklemişsinizdir. Depoda ayrıca hasActiveSubscription, bulunur. Bu değer, süresi dolmamış bir productId storeKeySubscription ile satın alma işlemi olduğunda doğru olur. Kullanıcı giriş yapmadığında liste boş olur.

lib/repo/iap_repo.dart

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

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

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

      notifyListeners();
    });
  }

Tüm satın alma mantığı DashPurchases sınıfındadır ve aboneliklerin uygulanacağı veya kaldırılacağı yerdir. Bu nedenle, iapRepo değerini sınıfa bir özellik olarak ekleyin ve iapRepo değerini oluşturucuda atayın. Ardından, doğrudan oluşturucuya bir dinleyici ekleyin ve dinleyiciyi dispose() yönteminde kaldırın. İlk başta dinleyici boş bir işlev olabilir. IAPRepo bir ChangeNotifier olduğundan ve Firestore'daki satın alma işlemleri her değiştiğinde notifyListeners() çağrısı yapıldığından, satın alınan ürünler değiştiğinde purchasesUpdate() yöntemi her zaman çağrılır.

lib/logic/dash_purchases.dart

  IAPRepo iapRepo;

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

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

  void purchasesUpdate() {
    //TODO manage updates
  }

Ardından, main.dart. içindeki kurucuya IAPRepo değerini sağlayın. Provider içinde zaten oluşturulduğu için context.read kullanarak deposu alabilirsiniz.

lib/main.dart

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

Ardından purchaseUpdate() işlevinin kodunu yazın. dash_counter.dart,'te applyPaidMultiplier ve removePaidMultiplier yöntemleri çarpanı sırasıyla 10 veya 1 olarak ayarlar. Bu nedenle, aboneliğin uygulanıp uygulanmadığını kontrol etmeniz gerekmez. Abonelik durumu değiştiğinde, satın alınabilir ürünün durumunu da güncelleyerek satın alma sayfasında etkin olduğunu gösterebilirsiniz. _beautifiedDashUpgrade mülkünü, yükseltmenin satın alınıp alınmadığına göre ayarlayın.

lib/logic/dash_purchases.dart

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

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

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

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

Artık arka uç hizmetinde abonelik ve yükseltme durumunun her zaman güncel olduğundan ve uygulamayla senkronize edildiğinden emin oldunuz. Uygulama, buna göre hareket eder ve abonelik ile yükseltme özelliklerini Dash tıklama oyununuza uygular.

12. İşlem tamamlandı

Tebrikler. Codelab'i tamamladınız. Bu codelab'in tamamlanmış kodunu android_studio_folder.pngtamamlandı klasöründe bulabilirsiniz.

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