Skip to content

B2B Billing (Stripe Abonelik) Akışı

Company billing sayfası, Stripe Checkout subscription, webhook aktivasyonu ve seat proration güncellemesi.

Bu doküman, Achidemy B2B (company) tarafındaki koltuk bazlı (per-seat) Stripe abonelik akışını açıklar.

Dosya: app/routes/company.billing.tsx (action)

Temel prensip:

  • Stripe Checkout session mode: "subscription"
  • line_items[0].quantity = tenant.seatLimit
  • Session metadata ile webhook’ta B2B olduğunun güvenli şekilde anlaşılması

Önemli metadata alanları:

  • metadata.type = "b2b_subscription"
  • metadata.organizationId = <orgId>
  • client_reference_id = org_<orgId>

Gerekli env:

  • STRIPE_SECRET_KEY
  • STRIPE_B2B_PRICE_ID

Success/Cancel URL’leri dil destekli üretilir:

  • /${lang}/company/billing?success=true
  • /${lang}/company/billing?canceled=true

Webhook: checkout.session.completed → Şirketi Aktifleştir

Section titled “Webhook: checkout.session.completed → Şirketi Aktifleştir”

Dosya: app/routes/api.stripe.webhook.ts

Stripe checkout.session.completed event’inde:

  • session.metadata.type === "b2b_subscription" ise B2B branch’e girer
  • organizations kaydı güncellenir:
    • stripeCustomerId
    • stripeSubscriptionId
    • isActive = true

Bu adım, demo/soft-lock kısıtlarını kaldıran “gizli sinyal” işlevi görür.

Dosya: app/lib/stripe-b2b.ts

updateStripeSeatCount(secretKey, subscriptionId, newQuantity):

  1. Aboneliği retrieve eder → subscription.items.data[0].id (subscription item)
  2. stripe.subscriptions.update(subscriptionId, { items: [{ id, quantity: newQuantity }], proration_behavior: "create_prorations" })

Böylece şirket seatLimit arttığında Stripe kalan dönemi prorate eder ve farkı faturaya yansıtır.

  • Price ID eksik: STRIPE_B2B_PRICE_ID olmadan checkout oluşturulamaz.
  • Metadata unutulursa: webhook B2B olduğunu anlayamaz, organizations güncellenmez.
  • SeatLimit 0/boş: quantity beklenmeyen faturalara yol açabilir; DB’de default ve UI doğrulaması önemlidir.