Преминете към основното съдържание

Жизнен цикъл на абонамент

Този документ описва пълния жизнен цикъл на Stripe абонамент в Dictaro — от създаване на checkout session до отказ и downgrade.

Архитектура на billing системата

Checkout процес

1. Избор на план

Потребителят избира план чрез страницата GET /billing/choose-plan:

PricingService кешира цените от Stripe за 5 минути (cacheTTL: 5 * time.Minute), за да намали API заявките.

2. Създаване на Checkout Session

Lazy Stripe Customer създаване:

  • Stripe Customer се създава само при първия checkout
  • Метаданни: dictaro_user_id за свързване с вътрешния потребител
  • stripe_customer_id се запазва в users таблицата

Promotion codes:

  • AllowPromotionCodes: true позволява потребителите да въведат промо код при checkout

3. Успешно плащане

Race condition handling: Checkout success redirect може да пристигне преди webhook-а. Сървърът проверява и обновява плана проактивно, за да осигури незабавен достъп.

Webhook обработка

Поддържани webhook events

checkout.session.completed

Функция: HandleCheckoutCompleted

1. Извлича customer_id и subscription_id от event data
2. Търси потребител по stripe_customer_id
3. Idempotent проверка: ако вече е pro с този subscription → skip
4. UPDATE users SET plan='pro', stripe_subscription_id=? WHERE id=?
5. Логва upgrade

customer.subscription.updated

Функция: HandleSubscriptionUpdated

1. Извлича customer_id и status от event data
2. Търси потребител по stripe_customer_id
3. Ако status == 'active' И user.plan != 'pro' → upgrade
4. Потвърждава статус чрез лог

Този event се получава при:

  • Подновяване на абонамент
  • Промяна на план (monthly ↔ annual)
  • Реактивиране след неуспешно плащане

customer.subscription.deleted

Функция: HandleSubscriptionDeleted

1. Извлича customer_id от event data
2. Търси потребител по stripe_customer_id
3. UPDATE users SET plan='free', stripe_subscription_id=NULL
4. Логва downgrade

Този event се получава когато:

  • Абонаментът изтече след отказ (cancel_at_period_end)
  • Администраторско отменяне от Stripe Dashboard
  • Абонаментът е прекратен поради многократно неуспешно плащане

invoice.payment_failed

Функция: HandlePaymentFailed

1. Логва предупреждение с customer_id и subscription_id
2. Не променя плана (Stripe опитва повторно автоматично)

Бележка: Stripe има вградена retry логика за неуспешни плащания. Планът се променя на free едва когато Stripe изпрати subscription.deleted след изчерпване на опитите.

Състояния на абонамент

Stripe sync при login

При всеки OAuth login, сървърът синхронизира състоянието с Stripe:

SyncStripeSubscription осигурява, че Stripe е source of truth за статуса на абонамента. Това обработва случаи, при които webhook е бил пропуснат.

Stripe Customer Portal

Потребителят може от Customer Portal да:

  • Промени платежен метод
  • Прегледа фактури и разписки
  • Откаже абонамент (cancel_at_period_end)
  • Реактивира отказан абонамент
  • Промени план (monthly ↔ annual)

Return URL: {BASE_URL}/billing/portal-return

Metrics и мониторинг

Системата събира Prometheus метрики за billing операциите:

МетрикаLabelsОписание
billing_checkouts_totalstatus: success/errorОбщ брой checkout сесии
billing_webhooks_totalevent_type, statusWebhook events по тип и резултат

Сигурност

  • Webhook верификация: Всеки webhook се верифицира чрез Stripe-Signature header и STRIPE_WEBHOOK_SECRET
  • Body limit: Максимален размер на webhook payload: 1 MB
  • Idempotency: HandleCheckoutCompleted проверява за дублиращи се events
  • Production валидация: STRIPE_WEBHOOK_SECRET е задължителен в production (GIN_MODE=release)
  • JWT обновяване: Нов JWT с актуален план се издава при checkout success redirect