Merhaba. Geçen yıl (2023 sonu) geliştirici olarak kayıt olup uygulama yayınlamaya başlayan zm soft'um. Geliştiriciler arasındaki iş birliğiyle kapalı testi aşmak için geliştiriciler için bir uygulama da yayınlamayı planlıyoruz, ilginizi çekerse lütfen kontrol edin.
Uygulama içi satın almayı uyguladınız mı? Uygulama geliştiricileri için kullanıcıyı memnun etmek ve bunun karşılığında ödeme almak en iyisidir; geliştirme çabalarının karşılık bulduğu andır bu. Bu sefer bunu nasıl gerçekleştireceğimiz hakkında yazmak istiyorum.
Uygulama İçi Satın Alma Nedir
Bildiğiniz üzere, uygulama içi satın alma iki türe ayrılır:
- Tüketilebilir öğeler
- Periyodik abonelik öğeleri
Her ikisi de Google Play Billing Library aracılığıyla Play Store'da önceden ayarlanan öğelere erişme yaklaşımını benimser ve elde edilen gelirin bir kısmı Google'a komisyon olarak ödenir. Uygulama açısından her ikisi benzerdir, ancak periyodik abonelik öğelerinin kendine özgü dikkat noktaları vardır. Bu dikkat noktalarını kişisel deneyimimden hareketle açıklayacağım.
Play Store Ayarları
Öğe Kaydı
Önce öğeleri kaydedin, çünkü kayıt yapılmadan görüntülenemezler. Fiyat, ad vb. ile o öğeyi gerçekten kullanıp kullanmayacağınız da dahil olmak üzere her şey sonradan değiştirilebilir, bu yüzden geçici veriler olsa da sorun yok. Ancak yalnızca ID değiştirilemez, bu nedenle test için oluşturulan öğeleri değiştirerek üretim ortamında kullanmayı düşünüyorsanız, [product_1,2,3...] veya [sub_1,2,3...] gibi yeniden kullanılabilir ID'lerle kaydetmek daha iyi olur. Kayıt [Monetize] üzerinden yapılabilir; [In-app products] ve [Subscriptions] sırasıyla tüketilebilir öğeler ve periyodik abonelik öğeleridir. Her öğe [Create xx] düğmesiyle kaydedilebilir.

Temel olarak giriş ekranını takip edebilirsiniz, ancak beni şaşırtan periyodik abonelik öğelerinin fiyat ayarıydı. Başlangıçta tüm bölgeler için fiyatları tek seferde nasıl ayarlayacağımı bilmiyordum. Her bölgeyi ayrı ayrı girmek gerektiğini düşündüm, ancak şu sırayla yapılabilir: [Set prices] - [Country / region] - [Set price].

Düğmeye basıldığında aşağıdaki iletişim kutusu belirir ve tek seferde ayar yapılmasına olanak tanır.

Test Hesabı Kaydı
Ardından test hesabını kaydedin. Kayıt yapmadan test satın alma gerçekleştirirseniz gerçek ödeme oluşur, bu yüzden dikkat edin. Test hesabı kaydı, bir posta listesi kaydedilerek yapılır. Kayıt, Play Console'a giriş yaptıktan sonraki ilk ekranda (geliştirici ekranı) [Settings] - [License testing] yoluyla yapılabilir.

Posta listesine kayıtlı bir kullanıcıyla uygulama satın alma işlemi yapıldığında ödeme bilgileri "test kartı" olarak görüntülenir ve gerçek ödeme olmadan test yapılabilir.
Uygulama Çalışmaları
Uygulama tarafındaki uygulamada aşağıdaki çalışmalar gereklidir:
- Kütüphane içe aktarımı
- Mağazaya bağlantı ve öğe bilgisi alma
- Öğe görüntüleme ekranının uygulanması
- Satın alma talebi
- Satın alma tamamlandığında işlem
Aşağıda her birini ayrıntılı olarak açıklayacağım.
Kütüphane İçe Aktarımı
Kütüphane içe aktarımı için aşağıdakiler gereklidir:
- build.gradle düzenlemesi
- android.manifest düzenlemesi
build.gradle düzenleme örneği:
dependencies {
implementation "com.android.billingclient:6.0.0"
}
manifest düzenleme örneği:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="com.android.vending.BILLING" />
<application
Sürüm vb. için lütfen resmi belgeleri inceleyin.
Mağazaya Bağlantı ve Öğe Bilgisi Alma
BillingClient'i başlatıp startConnection çalıştırarak mağazayla iletişim mümkün hale gelir. Her öğenin bilgisi queryProductDetailsAsync ile alınabilir. Ayrıntılar için lütfen resmi belgeleri inceleyin. Benim durumumda nedense toplu alma düzgün çalışmadı, bu yüzden önce öğe listesini alıp ardından queryProductDetailAsync ile her öğenin ayrıntılarını tek tek aldım. Alınan öğenin (SKU) tüketilebilir mi yoksa periyodik abonelik mi olduğu, türünün inapp veya subs olup olmadığından anlaşılabilir.
Öğe Görüntüleme Ekranının Uygulanması
Öğe bilgisi alındıktan sonra, satın alma seçimi yapılabilmesi için liste halinde görüntüleyin. Periyodik abonelik öğelerini burada işlerken Subscription Policy uyumluluğu gereklidir ve benim durumumda uygulama güncellemeleri arka arkaya iki nedenden reddedildi:
- Fiyat ve koşulların eksik yerelleştirilmesi
- Teklif koşullarının eksik açıklanması
Önce fiyat ve koşulların eksik yerelleştirilmesi hakkında: neden, dönemin görüntülenmesiydi (örnek: $10/month). Sorun, çeviriyi desteklemeyen dil bölge ayarıyla çalıştırıldığında "month" kısmının çevrilmemesiydi. BillingClient kullanılarak bilgi alındığında, fiyat tutarı birim bilgisini de içeren formattedPrice dizesi olarak alınır, bu nedenle uygulamada çeviriye gerek yoktur. Buna karşın dönem bilgisi P1M gibi ISO 8601 biçiminde bildirilir ve bu uygulamada çevrilmesi gerekir. Her dil için şu şekilde dönüştürme işlemleri ekleyerek hallettim:
fun formatBillingPeriod(billingPeriod: String, languageCode: String): String {
return when(languageCode) {
"en" -> {
when (billingPeriod) {
"P1W" -> "weekly"
"P1M" -> "monthly"
"P3M" -> "every 3 months"
"P6M" -> "every 6 months"
"P1Y" -> "annually"
else -> "unknown"
}
}
"ja" -> {
when (billingPeriod) {
"P1W" -> "週間"
"P1M" -> "月額"
"P3M" -> "3ヶ月ごと"
"P6M" -> "6ヶ月ごと"
"P1Y" -> "年額"
else -> "不明"
}
}
"fr" -> {
when (billingPeriod) {
"P1W" -> "hebdomadaire"
"P1M" -> "mensuel"
"P3M" -> "tous les 3 mois"
"P6M" -> "tous les 6 mois"
"P1Y" -> "annuel"
else -> "inconnu"
}
}
"es" -> {
when (billingPeriod) {
"P1W" -> "semanal"
"P1M" -> "mensual"
"P3M" -> "cada 3 meses"
"P6M" -> "cada 6 meses"
"P1Y" -> "anual"
else -> "desconocido"
}
}
"de" -> {
when (billingPeriod) {
"P1W" -> "wöchentlich"
"P1M" -> "monatlich"
"P3M" -> "alle 3 Monate"
"P6M" -> "alle 6 Monate"
"P1Y" -> "jährlich"
else -> "unbekannt"
}
}
else -> "unknown"
}
}
Ardından teklif koşullarının eksik açıklanması hakkında:

Sorunu anladım, ancak çözüm olarak somut olarak hangi ifadeyi yazmam gerektiğini bilemedim. Aşağıda, reddetme bildirimini aldığım e-postanın orijinal metninden bir alıntı yer almaktadır:
Issue found: Violation of Subscriptions policy
Your app does not comply with the Subscriptions policy.
- Your offer does not clearly and accurately describe the terms of your trial offer or introductory pricing, including when a free trial will convert to a paid subscription, how much the paid subscription will cost, and that a user can cancel if they do not want to convert to a paid subscription.
Çözüm olarak, diğer uygulamalardan ifadeler ödünç aldım. Büyük ölçüde standart ifadeler gibi göründüğünden, sıfırdan düşünmek yerine kanıtlanmış emsal örnekleri takip etmenin daha iyi olduğunu düşündüm.
Satın Alma Talebi
Ekrandan öğe seçilebildikten sonra nihayet öğe satın alma işlemi gelir. Alınan öğe için satın alma akışı çalıştırılarak gerçek satın alma ekranı çağrılabilir.
Test
Test, başlangıçta hazırlanan test hesabıyla giriş yapılarak gerçek ücret oluşturmadan yapılabilir. Dikkat edilmesi gereken iki nokta şunlardır:
- Satın alma akışı yürütme mağazaya kayıtlı uygulamayla yapılır
- Periyodik abonelik öğeleri için süre özel olarak ayarlanır
Satın alma akışı yürütme konusunda, uygulama debug build ise satın alma ekranında hata görüntülenir ve akış çalıştırılamaz.
Periyodik abonelik öğeleri konusunda ise gerçek satın alma ekranına geçildiğinde test kartıyla ödeme görüntülenir. Bu sırada satın alma ekranında yenileme süresi gösterilir, ancak görüntülenen bu süre öğe için gerçekten ayarlanan süreden farklıdır. Benim durumumda 5 dakika görüntüleniyordu. Abonelik yenileme davranışı için son derece kısa bir sürenin sabit olarak gösterildiği bir özellik gibi görünüyor. (Bir yerde ayar yanlış yaptığımı sanarak bir süre ayarları gözden geçirdim.)
Özet
Başlangıçta zor görünse de genel resmi anladıktan sonra, uygulama içi satın alma resmi belgeler gibi kamuya açık bilgilere bakılarak şaşırtıcı derecede kolayca uygulanabilir. Ancak periyodik abonelikler için deneyip görmeden anlaşılamayan tuzaklar da var, bu yüzden takıldığım noktaların gelecekte bunu uygulayacak geliştiricilere referans olmasını umuyorum.