← Tüm case study'ler
Subscription Billing Dunning Management Multi-Currency
Case Study: SaaS Abonelik Platformu
Kapsam: Backend + Billing Engine + Frontend

Global SaaS Abonelik
& Billing Platformu

Shopify Subscriptions üzerine inşa edilmiş özel billing engine. Plan değişikliklerinde prorated hesap, başarısız ödemelerde smart dunning, 8 para biriminde abonelik yönetimi ve Twilio SMS bildirimi.

1) Yönetici Özeti

Uluslararası bir SaaS ürünü, Shopify mağazası üzerinden abonelik satıyordu. Shopify'ın native subscription desteği plan geçişleri, prorate hesabı ve dunning senaryolarını yönetmek için yetersiz kalıyordu. Shopify'ın üzerine tam özellikli bir billing engine inşa ettik; Shopify hem ödeme kapısı hem de ürün kataloğu olarak kaldı.

2) İş Hedefleri

Hedefler

  • Plan upgrade/downgrade'de adil prorate hesabı
  • Başarısız ödemelerde akıllı retry ile revenue recovery
  • 8 para biriminde abonelik döngüsü yönetimi
  • Müşteriye otomatik bildirim (email + SMS)

Başarı Kriterleri

  • Plan geçişinde müşteri fazla veya eksik ücretlendirilmemeli
  • Başarısız ödeme → otomatik retry → müşteri kaybedilmesin
  • Shopify webhook'ları idempotent işlenmeli, duplicate işlem sıfır
  • Tüm billing hareketleri audit log'da geri izlenebilir

3) Mimari

Shopify Katmanı

Ürün/fiyat kataloğu, native ödeme ve checkout deneyimi. Özelleştirmemiz yok, sadece API kullanımı.

Billing Engine

Plan lifecycle yönetimi, prorate hesabı, dunning koordinasyonu, state machine.

Notification Layer

BullMQ zamanlanmış iş, Sendgrid email, Twilio SMS. Başarısızlıkta 3x retry.

4) Subscription State Machine

trial active past_due dunning recovered cancelled

Her abonelik bir state machine üzerinden yönetilir. Geçişler deterministik, loglanır ve audit trail'e kaydedilir. İzinsiz state geçişi imkânsız.

Aktif → Past Due

  • Ödeme başarısız → past_due
  • Dunning servisine iş kuyruğa eklenir
  • Müşteriye SMS + email bildirim

Dunning → Recovered

  • Retry başarılı → active
  • Audit log: önceki state, yeni state, timestamp
  • Müşteriye "ödeme alındı" SMS gönderilir

5) Dunning Management

Başarısız ödemeden sonra pasif beklemek yerine sistematik retry ve müşteri iletişimi:

Gün Aksiyon Bildirim State
0 İlk ödeme başarısız SMS + Email past_due
3 2. deneme Email: kart güncelle CTA dunning
5 3. deneme SMS: acil bildirim dunning
7 Son deneme Email: iptal uyarısı dunning
10 Abonelik iptal Email: erişim kapanıyor cancelled

BullMQ delayed job sistemi ile tam kontrol. Her retry idempotent — ağ problemi olursa aynı iş iki kez çalışmaz.

6) Prorate Hesabı

Kullanıcı dönem ortasında plan değiştiğinde ne kadar ücretlendirilmeli? Saniye seviyesinde hassas hesap:

Upgrade Senaryosu

  • Kalan gün sayısı hesaplanır (sıfıra kadar)
  • Eski plan × kalan gün oranı → kredi
  • Yeni plan tam fiyatı → kredit çıkartılır
  • Fark anında tahsil edilir

Downgrade Senaryosu

  • Mevcut dönem sonuna kadar eski plan korunur
  • Yeni dönemde yeni fiyat aktif olur
  • Müşteriye dönem sonu hatırlatma bildirimi
  • Geri ödeme yok — süreç adil ve şeffaf

7) Shopify Webhook Güvenliği

8) Redis & Queue

9) Operasyon

10) Sonuçlar

Metrik Öncesi Sonrası
Aylık churn oranı %8.6 %5.1 (%41 düşüş)
Ödeme başarı oranı %84 %98.4
Prorate şikâyeti Ayda 8–12 0
Dunning recovery Yok Başarısız ödemelerin %38'i kurtarıldı
Duplicate işlem Ayda 2–4 0

11) Production Snapshot

Sistemin 90 günlük production ortalaması. Ölçümler Prometheus + Grafana ile alındı.

Metrik Değer Kapsam
Aktif abone sayısı ~8.200 4 para birimi, 6 pazar
Aylık faturalama döngüsü ~8.200 işlem/ay Shopify recurring charge
Ortalama RPS (billing endpoint) 18 Gece yarısı faturalama loadu dahil
Peak RPS 94 Ay başı faturalama dalgası
Dunning job süresi (toplam küme) Ortalama 3.2 dk BullMQ, 36 concurrent worker
P95 webhook işleme latency 84ms Shopify → DB commit dahil
Ödeme başarı oranı (dunning sonrası) %98.4 Retry dahil, ilk deneme %84
API Pod sayısı 2 HPA min:2 / max:6
Worker Pod sayısı 3 BullMQ concurrency: 12/pod
Redis Managed Redis (2 node, primary+replica) AOF enabled, TTL stratejisi aktif
PostgreSQL 1 primary + 1 read replica Streaming replication, lag <150ms
Uptime (90 gün) %99.74 Planned maintenance hariç
Deployment modeli Rolling update Kubernetes, zero-downtime

12) Incident Log

INC-001 — Shopify Webhook Sıralama Bozukluğu (2024-10-08, 08:44 UTC)

  • Tetikleyen: Shopify, subscription_contracts/update event'ini billing_attempts/success'den önce gönderdi
  • Etki: State machine active yerine past_due'da takıldı, 14 aboneye yanlış dunning başladı
  • Tespit: Dunning alert — olması gereken sürenin 40 saat öncesi tetiklendi
  • Çözüm: Webhook işleyiciye recheck_after_delay mekanizması eklendi: billing success 10sn içinde gelirse state güncellenir, dunning iptal
  • Kullanıcı etkisi: 14 aboneye hatalı SMS gitti; manuel apology email gönderildi
  • Sonraki aksiyon: Tüm state geçişleri event timestamp ile sıralanıyor, out-of-order event'ler kuyruğa alınıyor

INC-002 — Twilio SMS Rate Limit (2024-12-01, 00:02 UTC)

  • Tetikleyen: Ay başı faturalama dalgası — 8.200 aboneye 4 dk içinde SMS gönderilmeye çalışıldı
  • Etki: Twilio 429 döndü, BullMQ worker'lar hata aldı; 1.340 SMS kuyruğa takıldı
  • Tespit: worker_queue_depth alert — eşik 200, gerçekleşen: 1.340
  • Engelleme: BullMQ exponential backoff + Twilio retry → 3 saat içinde tüm SMS teslim edildi
  • Kullanıcı etkisi: SMS gecikmeli geldi, ödeme etkilenmedi
  • Sonraki aksiyon: SMS gönderimi rate-throttle middleware eklendi (max 50 SMS/sn), ay başı loadu artık dakikalara yayılıyor

13) Observability & Alerting

Prometheus Metric Listesi

  • subscription_active_count{currency, plan}
  • billing_attempt_success_rate — gauge, 60s
  • dunning_recovery_rate — recovered / total_past_due
  • churn_rate_monthly — cancelled / active, rolling 30d
  • webhook_processing_latency_p95 — histogram
  • duplicate_webhook_rate{source} — counter
  • idempotency_hit_ratio — cache hit / (hit+miss)
  • worker_queue_depth{queue_name}
  • sms_delivery_success_rate{provider}
  • prorate_error_count — counter, alarm eşiği: 1

Alert Eşikleri

Alert Koşul Severity
billing_attempt_success_rate < 0.80 10dk P1
prorate_error_count > 0 Anlık P1
worker_queue_depth > 200 5dk P2
dunning_recovery_rate < 0.25 1 gün P2
churn_rate_monthly artışı > %20 Haftalık P2
sms_delivery_success_rate < 0.90 15dk P3
duplicate_webhook_rate > 10/dk 2dk P2

Churn spike: churn_rate_monthly önceki 4 hafta ortalamasının %30 üzerine çıkarsa Slack kanalına otomatik analiz özeti gönderilir.

14) Security Hardening

  • Shopify Webhook Doğrulama: Her webhook HMAC-SHA256 ile doğrulanır. Hatalı imza → 401, loglana, işlenmez.
  • Replay Attack: Event ID Redis'e yazılır (24h TTL). Aynı event ID ikinci kez → sessiz skip, HTTP 200.
  • JWT Rotation: Access token TTL: 15dk. Refresh token: 7 gün, tek kullanımlık. Logout → Redis'ten temizlenir.
  • Sensitive Log Masking: Kart son 4 hane görünür, IBAN regex ile maskelenir. Uygulama logları PII içermez.
  • Secrets Management: Shopify API key, Twilio token, DB credentials — Kubernetes Secret + Vault injection. Kod içinde hardcode sıfır.
  • CSRF: Stateless JWT, cookie session yok. Admin endpoint'leri Bearer token zorunlu.
  • Rate Limiting: Billing API endpoint'leri için IP + user_id bazlı Redis sliding window (max 10 req/dk).
  • Prorate Güvenliği: Plan değişikliği validasyonu server-side; client'tan gelen tutar kabul edilmez, her zaman server hesaplar.
  • SQL Injection: Tüm sorgular Prisma ORM ile parameterized.
  • Dependency Audit: CI/CD'de npm audit --audit-level=high zorunlu; critical CVE → deploy bloke.

15) Redis Unavailable — Derinlemesine Senaryo

Redis erişilemez olduğunda hiçbir faturalama veya abonelik işlemi durmaz.

  • Idempotency fallback: Redis miss → DB idempotency_keys tablosu, UNIQUE(key) constraint. İki eşzamanlı webhook gelirse biri UniqueConstraintError alır, retry cached sonucu döner.
  • Plan geçiş kilidi fallback: Redis SET NX başarısız → PostgreSQL SELECT FOR UPDATE. Performans düşer, concurrent double-charge imkânsız kalır.
  • Subscription cache: Redis miss → DB'den okuma; ~20ms ek gecikme, kullanıcı fark etmez.
  • BullMQ persistent mode: Redis cluster down → BullMQ, DB-backed fallback queue'a geçer. Dunning zamanlaması gecikmeli ama kayıpsız devam eder.
  • Rate limit soft-disable: Redis down → rate limiting geçici devre dışı, WARN: rate_limit_degraded log, PagerDuty P3 alert. Servis kesilmez.
  • Prorate kesinlik: Tüm hesaplar decimal.js ile yapıldı, Redis'e bağımlı değil. Redis down hiçbir şekilde hesap hassasiyetini etkilemez.

16) Billing Platform Production Proof

Bu sistem production'da çalıştırılmış, aşağıdaki maddeler gözlemlenmiş veya test edilmiş davranışlardır.

Idempotency Yaklaşımı

  • Her Shopify webhook event ID normalize edilip Redis'e yazılır
  • Redis SET NX EX 86400 — ilk yazma atomik
  • Duplicate gelirse process atlanır, HTTP 200 döner
  • Redis down → DB UNIQUE constraint, race condition yok
  • 8.200 abonelik faturasında sıfır duplicate charge

Prorate Güvenilirliği

  • Tüm hesaplar decimal.js ile kuruşa kadar doğru
  • Plan değişikliği anında server-side hesaplanır
  • Müşteriden gelen tutar hiçbir zaman kabul edilmez
  • Concurrent plan değişikliği → distributed lock korur
  • Devreye girdiğinden bu yana prorate şikâyeti: 0

Dunning Davranışı

  • State machine deterministik — izinsiz geçiş imkânsız
  • 4 aşamalı retry: 0 / 3 / 5 / 7. gün
  • Her retry BullMQ delayed job, idempotent çalışır
  • Başarısız ödemelerin %38'i dunning ile kurtarıldı
  • Her geçiş audit log'a: önceki state, yeni state, timestamp

SLA & Devir

  • P1 (tüm ödemeler durdu): <1 saat müdahale, Shopify native fallback
  • P2 (dunning çalışmıyor, SMS gelmiyor): <4 saat
  • Tüm runbook'lar Notion'da, bağımsız okunabilir
  • Infrastructure as Code (Terraform), ortam yeniden kurulabilir
  • Testler: kritik billing yolları %100 coverage

17) Architecture Topology Overview

Trafik soldan sağa katmanlar üzerinden akar. Shopify hem ödeme kapısı hem ürün kataloğu olarak kalır.

Shopify
Checkout + ödeme
Ürün kataloğu
HMAC webhook gönderir
API Pods ×2
Node.js stateless
JWT auth
Webhook doğrulama
Billing Engine
State machine
Prorate hesabı
Dunning koordinasyonu
Redis + PostgreSQL
Idempotency cache
Subscription state
Audit ledger
Worker Pods ×3
BullMQ dunning jobs
Sendgrid email
Twilio SMS

Dunning trafiği: Billing Engine → Redis Queue → Worker Pods → Shopify API (retry charge) + Twilio/Sendgrid (bildirim) → PostgreSQL (state update).

Kaynak Kodu Hakkında

Bu projeye ait kaynak kodlar, müşteri ile imzalanan gizlilik sözleşmesi (NDA) kapsamında paylaşılamamaktadır. Teknik yapı, mimari kararlar ve implementasyon detayları hakkında sorularınızı doğrudan iletebilirsiniz; müsait olanı kapsamında yanıtlayırız.