Ana Sayfa / Blog / StoreKit 2 ile abonelik gelirinin %10’unu sessizce kaybetmemek

StoreKit 2 ile abonelik gelirinin %10’unu sessizce kaybetmemek

Abonelik akışında kod doğru görünse bile düzgün kurgulanmayan 5 edge case, StoreKit 2'ye geçtiğimde öğrendiğim pahalı dersler.

12 iOS uygulamamda StoreKit entegrasyonu yaptım, sekizinde abonelik modeli var. Her yeni uygulamada bir önceki projede gözden kaçırdığım bir edge case ortaya çıkıyor. Bu yazıda sessizce gelir kaybına yol açan 5 durumu anlatacağım.

1. Receipt validation server-side değilse sızıntı kaçınılmaz

Apple’ın client-side receipt check’i kullanıcıya göstermek için yeterli. Ama gelir hesabı için client’a asla güvenme.

Sebep basit: kullanıcı uygulamayı jailbreak edilmiş bir cihazda açıyor, sahte bir receipt enjekte ediyor, client’ın Transaction.currentEntitlements yapısı “valid” diyor, kullanıcı premium erişim kazanıyor, Apple’a hiç ödeme gitmiyor.

Fix: her entitlement check’inde transaction’ı backend’inize gönderin, backend de Apple’ın verifyReceipt endpoint’ine sunucu-sunucu doğrulama yapsın. Bu fazladan bir network hop ekler, ama %100 sizin gelir katmanınız. StoreKit 2’de bu JWSTransaction.jwsRepresentation üzerinden doğrulanabiliyor.

Parademi’de ilk 3 ayda bunu atladım, sonra raporlarda kullanıcı premium kullanıyor ama satır App Store Connect’te hiç görünmüyordu. %4-5 kayıp. Backend validation’dan sonra sıfırlandı.

2. Subscription status updates handler’ı her zaman açık olmalı

Bir kullanıcı Family Sharing’den eklendiğinde, iade aldığında, abonelik seviyesini değiştirdiğinde, bu değişiklikler app çalışmıyorken de oluyor. Apple server’dan notification gönderir, backend buna hazır olmalı.

StoreKit 2’de Transaction.updates async stream’ini app başlamasında dinlemeye başlaman gerekiyor. Task.detached içinde for await ile stream’i dinleyip her yeni transaction’ı handle ediyorsun.

Bunu AppDelegate ya da @main struct’ta açmazsan, kullanıcının abonelik değişikliğini bir sonraki uygulama açılışına kadar kaçırırsın. O arada kendi tarafında yanlış entitlement gösteriyorsun demektir.

3. Grace period ve billing retry kullanıcıyı kesmeden korumak

Abonelik yenilenirken ödeme başarısız olabilir (kart süresi dolmuş, kart limiti aşılmış). Apple’ın default davranışı: 3-16 günlük billing retry period. Bu süreçte kullanıcı premium kalmaya devam eder, ama çoğu uygulama bunu kod tarafında handle etmiyor.

Product.SubscriptionInfo.renewalState değerine bakın:

  • .subscribed: aktif, sorun yok
  • .expired: bitmiş, premium’u kapatabilirsiniz
  • .inBillingRetryPeriod: ödeme denemeli, hala premium göster
  • .inGracePeriod: grace period’da, hala premium göster
  • .revoked: iade edildi, premium’u hemen kapat

Bu state’leri yanlış handle etmek kullanıcı kaybıdır. Ödemesi bir kart süresi sorunu yüzünden başarısız olan kullanıcıya “premium’un bitti” diye bağırmanın hiç gereği yok.

4. Introductory offer sadece ilk defa alan için

Introductory offer (“ilk ay ücretsiz”, “ilk 3 ay %50 indirimli”) her kullanıcı için değil, Apple ID başına ömür boyunca bir kez. Bunu client tarafında doğru göstermezsen, hak kazanmayan kullanıcıya da bu teklifi gösterir ve teklif satın alındığında Apple reddedeceği için satın alma başarısız olur.

Fix: her product için Product.SubscriptionInfo.isEligibleForIntroOffer değerini check et. False dönüyorsa, intro offer göstermeyin, direkt standart fiyat gösterin.

5. Refund request handler (sessiz ama önemli)

StoreKit 2’nin en güzel yeniliklerinden biri: kullanıcı iade isterse Transaction.beginRefundRequest ile uygulama içinde yapabiliyor. Eskiden Apple’a email atması gerekiyordu.

Ama iade onaylandığında sen haberdar olmalısın ki kullanıcının premium’unu kapatabilesin. Transaction.updates stream’ini dinlerken her transaction’ın revocationReason property’sini kontrol et. None değilse, iade onaylanmış demektir, backend’ine bildir, premium’u kes.

Refund edilmiş bir kullanıcıya premium vermeye devam edersen Apple bu abuse’u takip ediyor ve app rating’ini düşürüyor. Uzun vadede app’in App Store rankings’ine zarar veriyor.

Test etmek için sandbox user’ınız olsun

Bütün bunların hepsini production’a atmadan test etmek lazım. App Store Connect’te sandbox tester hesabı oluşturun, cihazınızda bu hesapla giriş yapın. Sandbox’ta abonelikler saatler içinde yenileniyor (production’da ay), dolayısıyla renewal, retry, grace period’u birkaç saat içinde test edebilirsin.

Ben her yeni proje için 3-4 sandbox tester hesabı tutuyorum, farklı country code’larda, farklı abonelik seviyelerinde. Abonelik akışının tamamını geçmeden proje yayına çıkmıyor.

Sonuç

StoreKit 2 gerçekten eski StoreKit’e göre çok daha temiz bir API. Ama “temiz API” demek “işin hepsi halledildi” demek değil, hala gelir etkileyen edge case’ler var. Bunları test edin, monitoring koyun ve her yeni release’de bir kez daha gözden geçirin.

Bu konuda bir projeniz mi var?

Kısa bir özet bırakın, 24 saat içinde size dönüş yapayım.

İletişime Geç