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

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

Този документ описва пълния жизнен цикъл на 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