SurfSense/_bmad-output/implementation-artifacts/deferred-work.md
Vonic 20c4f128bb feat(story-5.3): add Stripe webhook subscription lifecycle handlers
- Add migration 125: subscription_current_period_end column
- Add PLAN_LIMITS config (free/pro_monthly/pro_yearly token + pages limits)
- Add subscription webhook handlers: created/updated/deleted, invoice payment
- Handle checkout.session.completed for subscription mode separately from PAYG
- Idempotency: subscription_id + status + plan_id + period_end guard
- pages_limit upgraded on activation, gracefully downgraded on cancel
- Token reset on subscription_create and subscription_cycle billing events
- Period_end forward-only guard against out-of-order webhook delivery

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 00:43:07 +07:00

2 KiB

Deferred Work

Deferred from: code review of story 3-5-model-selection-via-quota (2026-04-14)

  • stripe_subscription_id has no unique constraint [surfsense_backend/app/db.py] — Column added without UNIQUE constraint. Should be enforced once Stripe integration (Epic 5) is implemented to prevent duplicate subscription mappings.
  • load_llm_config_from_yaml reads API keys directly from YAML file, not env vars [surfsense_backend/app/config.py] — Pre-existing: YAML config stores API keys inline. Spec Task 1.2 says "đọc API keys từ env vars" but this is the existing pattern used throughout the project. To be refactored when security hardening is prioritized.

Deferred from: code review of story 5-1 (2026-04-14)

  • ref cast as any on Switch component in pricing.tsx:99 — pre-existing issue, not introduced by this change. Should use proper React.ComponentRef<typeof Switch> type.

Deferred from: code review of story 5-2 (2026-04-14)

  • Webhook handler needs to distinguish mode='subscription' from mode='payment' in checkout.session.completed and update User's subscription_status, plan_id, stripe_subscription_id — scope of Story 5.3.
  • Subscription lifecycle events (invoice.paid, customer.subscription.updated/deleted, invoice.payment_failed) not handled — scope of Story 5.3.
  • _get_or_create_stripe_customer can create orphaned Stripe customers if db_session.commit() fails after customers.create. Consider idempotency key in future.

Deferred from: code review of story-5.3 (2026-04-15)

  • Race condition: checkout.session.completed and customer.subscription.deleted can fire near-simultaneously; if deleted arrives between checkout handlers, subscription can be reactivated. Fix requires Stripe API call to verify subscription status before activation.
  • invoice.payment_succeeded does not update subscription_current_period_end — currently relies on customer.subscription.updated firing in the same event sequence. If that event is lost, period_end is stale.