Pushizo Docs

Faturalama, planlar ve kota (Faz 13)

Plan matrisi, deneme, Stripe/iyzico/mock sağlayıcıları, kullanım raporu, kota ve özellik kapıları, uyarı e-postaları. Tasarım kararları: ADR-0020. Ortak kod: packages/billing (plan matrisi, sağlayıcı adaptörleri, kota…

Plan matrisi, deneme, Stripe/iyzico/mock sağlayıcıları, kullanım raporu, kota ve özellik kapıları, uyarı e-postaları. Tasarım kararları: docs/adr/0020-faturalama-kota-admin.md. Ortak kod: packages/billing (plan matrisi, sağlayıcı adaptörleri, kota yardımcıları, DNS doğrulaması, tek kullanımlık e-posta listesi).

1. Planlar

plans tablosu migration 0021 ile tohumlanır; packages/billing/src/plans.ts tek kaynaktır (ensurePlans eksik satırı tamamlar). Dashboard Organizasyon → Faturalama ve GET /internal/v1/billing/plans aynı veriyi okur.

PlanAylıkAboneMesaj/ayAPI rpsÜyeAppSaklamaJourneySegmentA/B varyantÖzellikler
free010k100k203130 gün1102push, e-posta, in-app, journey (A/B, SMS, onay akışı, composer yok)
starter$4950k1M10010390 gün10504+ A/B, SMS, audit export, composer
growth$199500k10M5005010365 gün10050010+ onay akışı
enterpriseözel∞∞2000∞∞730 gün∞∞10+ SSO

Diğer limitler: tags_per_user, aliases_per_user, subscriptions_per_user, report_history_days, webhooks, api_keys. null = sınırsız. Rol kapısı: features.roles dışındaki rol atanamaz (HS_PLAN_FEATURE_REQUIRED, details.feature = "roles").

Etkin plan (effectivePlanCode): ödemesi geçerli abonelik planı (active, trialing, past_due; PAID_PLAN_STATUSES) > deneme sürüyorsa starter (yalnızca yükseltir: platform yöneticisinin atadığı daha yüksek organizations.plan_code denemede düşürülmez) > organizations.plan_code > free. incomplete/incomplete_expired abonelik planı açmaz. Yeni organizasyon 14 gün deneme alır (trial_ends_at); 3 gün kala ve bitince e-posta gönderilir.

2. Sağlayıcılar

SağlayıcıSeçimNotlar
stripeSTRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRETREST (SDK yok): Checkout Session (subscription modu, client_reference_id = org_id, metadata.plan_code), Customer Portal, cancel_at_period_end, fatura listesi. Webhook POST /hooks/billing/stripe — Stripe-Signature (t/v1 HMAC-SHA256, 5 dk tolerans). Plan fiyatı plans.stripe_price_id ile eşlenir (Stripe fiyatının metadata.plan_code alanı da okunur).
iyzicoIYZICO_API_KEY + IYZICO_SECRET_KEY + IYZICO_MERCHANT_ID + billing.iyzico feature flag (ya da BILLING_PROVIDER=iyzico)Abonelik Checkout Form (IYZWSv2 imzalı). Akış: POST …/billing/checkout → subscription/checkoutform/initialize (müşteri adı, e-posta, GSM — fatura bilgisindeki telefon +90… biçimine normalize edilir) → form içeriği Redis hs:iyzico:form:<token> (30 dk) → kullanıcı API'nin barındırdığı GET /hooks/billing/iyzico/form/:token sayfasına gider (sayfaya özel CSP) → iyzico POST /hooks/billing/iyzico/checkout (token) → GET /v2/subscription/checkoutform/{token} ile sonuç sorgulanır, abonelik/plan senkronlanır ve dashboard'a 303 ile dönülür. Kart güncelleme POST /hooks/billing/iyzico/card. Webhook POST /hooks/billing/iyzico: X-IYZ-SIGNATURE-V3 = hex HMAC-SHA256(secretKey, merchantId + secretKey + iyziEventType + subscriptionReferenceCode + orderReferenceCode + customerReferenceCode); olaylar `subscription.order.success
mockanahtar yok (üretim dışı)Checkout …/settings/billing?checkout=success&mock_checkout=<id>&plan=<kod>&customer=<cus> adresine döner; dashboard POST …/billing/mock/complete çağırır → Stripe biçiminde checkout.session.completed + customer.subscription.created + `invoice.paid

BILLING_PROVIDER=auto (varsayılan): Stripe anahtarı varsa stripe, iyzico bayrağı + anahtar varsa iyzico, aksi halde mock.

none durumu — üretimde (NODE_ENV=production) Stripe/iyzico yapılandırılmamışsa okuma uçları çalışır: GET …/billing provider: "none" döner, GET …/billing/plans ve GET /internal/v1/billing/plans tüm planları purchasable: false verir; faturalar ve kullanım raporu etkilenmez. Checkout ve portal 503 HS_SERVICE_UNAVAILABLE (details.reason = billing_provider_not_configured, "Ödeme sağlayıcısı yapılandırılmamış"). Dashboard mevcut planı, limitleri ve kullanımı gösterir; satın alma satırı yerine "Plan değişikliği için destek ile iletişime geçin" kartı (markanın destek e-postası, yoksa support@pushizo.com) çıkar. Plan değişikliği platform admin panelinden yapılır.

Webhook işleme (BillingService.applyEvent): billing_events ile idempotent (işleme hata verirse olay kaydı silinir, sağlayıcının yeniden denemesi tekrar işler); abonelik olayı billing_subscriptions upsert + organizations.plan_code senkronu (iptal → free) + plan önbelleği geçersiz; fatura olayı invoices upsert, invoice.payment_failed → past_due + payment_failed e-postası (owner/admin/billing).

3. Uçlar

YolİzinAçıklama
GET /internal/v1/billing/plansoturumPlan matrisi (purchasable: sağlayıcı fiyatı tanımlı ya da mock)
GET /internal/v1/orgs/:orgId/billingbilling.manageDurum: sağlayıcı, plan, deneme, abonelik, askıya alma, fatura bilgisi
PUT …/billing/infobilling.manageFatura/vergi bilgileri (settings.billing)
POST …/billing/checkout {plan_code}billing.manageCheckout URL'si; aktif abonelik varsa portal kullanılmalı (409)
POST …/billing/portalbilling.manageMüşteri portalı
POST …/billing/cancel {cancel_at_period_end}billing.manageDönem sonunda iptal / geri al
GET …/billing/invoices?sync=1billing.manageFaturalar (sync sağlayıcıdan çeker)
GET …/usage?days=30analytics.readKullanım raporu
GET …/featuresoturumPlan özellikleri/limitleri + feature flag değerleri
POST …/billing/mock/completebilling.manageMock checkout simülasyonu (üretim dışı)
GET /v1/organization/usage?days=org geneli analytics:read/admin anahtarıAynı kullanım raporu (public; Node hs.organization.usage(), PHP $hs->organization->usage(), diğer SDK'lar organization.usage)
POST /hooks/billing/:providerimzaSağlayıcı webhook'u

Kullanım raporu: metrics[] (metric, used, limit, percent, hard), apps[] (messages_sent, active_subscriptions, mau), daily[] (son N gün messages_sent / api_requests / events_ingested), trialing, trial_ends_at.

4. Kullanım ve kota

  • Rollup (usage-rollup, saatte bir; apps/worker/src/processors/billing.ts): dün + bugün için usage_daily (org × app × metrik; app_id = '' toplam): messages_sent (deliveries.sent_at), events_ingested, api_requests (api_request_logs), active_subscriptions, mau (son 30 gün last_active_at).
  • Aylık mesaj sayacı Redis hs:usage:{org}:{YYYYMM}:messages: API/kampanya gönderimleri oluşturma anında tahmini alıcı sayısı kadar rezerve edilir (QuotaService.assertMessages, options.quota_reserved); worker kitle çözümlemede gerçek hedef sayısıyla farkı bir kez düzeltir (hs:usage:adj:<mesaj>), kalan kotayı aşan teslimatlar suppressed/quota_exceeded olur. Zamanlanmış/tekrarlı kampanyalar zamanlamada ön kontrol edilir (rezervasyonsuz). Rollup sayacı aşağı çekmez, yukarı uzlaştırır.
  • Sert sınırlar (HS_QUOTA_EXCEEDED 429, details { metric, limit, used, add, plan }): aylık mesaj, app, üye + bekleyen davet, aktif journey (yayında), segment, webhook, API anahtarı, A/B varyant sayısı, kullanıcı başına tag/alias/abonelik; yeni organizasyon ilk 24 saat günde 1000 mesaj (new_org_daily_messages). Abone sayısı yumuşak sınırdır (uyarı). Worker kitle çözümlemede sayaç limiti aşmışsa teslimatları suppressed/quota_exceeded yazar (son savunma).
  • Özellik kapıları (HS_PLAN_FEATURE_REQUIRED 403, details { feature, plan }): sms/email kanal kurulumu, in_app mesaj oluşturma, journeys yayını, ab_testing (varyantlar), roles (rol ataması).
  • Plan bazlı rate limit: API anahtarı varsayılan kapasitesi planın api_rps değeri (RateLimitGuard; org plan önbelleği 60 sn).
  • Uyarılar (usage_alerts, dönem başına bir kez): %80 ve %100 (messages_per_month, subscriptions) e-postaları owner/admin/billing üyelerine; deneme bitişi; ödeme başarısız.

Etkin plan önbelleği Redis hs:plan:{org} (60 sn); plan/abonelik değişiminde QuotaService.invalidate.

5. Askıya alma

organizations.status = suspended (platform admin): API anahtarı istekleri 403 HS_ORG_SUSPENDED, dashboard yalnızca faturalama/durum okur, SDK uçları 403, worker teslimatları suppressed/org_suspended. Kaldırılınca org_unsuspended e-postası. Ayrıntı: platform-admin.md.

6. Testler

Stripe test modu (Faz 13 kabulü): tests/e2e/billing-stripe.spec.ts kayıt → deneme → kota aşımı 429 → Stripe Checkout (yayımlanmış test kartı) → webhook → Growth → fatura → gönderim akışını doğrular. Yalnızca E2E_STRIPE=1 iken koşar; CI'da e2e.yml → e2e-stripe işi depo değişkeni STRIPE_E2E=true ve gizli anahtarlar STRIPE_TEST_SECRET_KEY (yalnızca sk_test_/rk_test_ kabul edilir), STRIPE_TEST_PRICE_GROWTH tanımlıyken çalışır; webhook'lar stripe listen --forward-to http://localhost:4000/hooks/billing/stripe ile yönlendirilir ve STRIPE_WEBHOOK_SECRET iş içinde üretilir. Yerelde: test anahtarlarıyla API'yi BILLING_PROVIDER=stripe başlatın, plans.stripe_price_id değerini Growth test fiyatına ayarlayın, stripe listen çalıştırın ve E2E_STRIPE=1 npx playwright test tests/e2e/billing-stripe.spec.ts.

packages/billing/src/__tests__/billing.test.ts (plan matrisi, etkin plan, kota/eşik/itibar yardımcıları, Stripe imza/normalizasyon/REST, iyzico yetki/webhook, mock, DNS, tek kullanımlık e-posta), apps/api/src/__tests__/phase13.int.test.ts (planlar/deneme/kullanım, kota ve özellik kapıları, mock checkout → abonelik → fatura, webhook imzası, askıya alma 403, support session, admin uçları, status page, OAuth, DNS), apps/worker/src/__tests__/billing.int.test.ts (rollup, uyarılar, bastırmalar, itibar, DNS, kota rezervasyonu, şikâyet incelemesi), apps/api/src/__tests__/iyzico.int.test.ts (sahte iyzico sunucusuyla form → sonuç → webhook), E2E tests/e2e/billing-admin.spec.ts (deneme → kota aşımı 429 → mock yükseltme → gönderim açılır) ve tests/e2e/billing-stripe.spec.ts.

Bu sayfada