Keeps subscription SaaS vision from PRD while adding accurate as-is analysis of existing code. Each story now has an "As-Is" table showing what exists and where the gaps are. Key points: - Story 3.5: Transition from BYOK to system-managed models with token billing. BYOK stays for self-hosted mode (deployment_mode=self-hosted), system models + subscription quota for hosted mode. - Story 5.1: Pricing UI exists (Free/PAYG/Enterprise) but needs redesign to subscription tiers (Free/Pro) with monthly/yearly toggle. - Story 5.2: PAYG checkout exists (mode=payment), need NEW subscription endpoint (mode=subscription) with stripe_customer_id binding. - Story 5.3: Webhook infrastructure exists (signature verify, PAYG handlers). Need subscription event handlers (customer.subscription.*) alongside. - Story 5.4: PageLimitService fully implemented. Gap is HTTP-layer pre-check, plan-based limits, frontend quota indicator, and 402 error handling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.1 KiB
Story 5.2: Tích hợp Stripe Subscription Checkout (Stripe Payment Integration)
Status: ready-for-dev
Story
As a Người dùng, I want bấm "Nâng cấp" và được chuyển tới trang thanh toán an toàn, so that tôi có thể điền thông tin thẻ tín dụng mà không sợ bị lộ dữ liệu trên máy chủ của SurfSense.
Acceptance Criteria
- Khi User bấm thanh toán, BE gọi API Stripe lấy
sessionIdvớimode='subscription'(recurring billing, không phải one-time payment). - Hệ thống redirect User an toàn qua cổng Stripe Hosted Checkout.
- Sau thanh toán thành công, user được redirect về app với subscription activated.
As-Is (Code hiện tại)
| Component | Hiện trạng | File |
|---|---|---|
| Checkout Endpoint | Đã tồn tại nhưng chỉ hỗ trợ mode='payment' (one-time page packs) |
surfsense_backend/app/routes/stripe_routes.py line ~205 |
| Checkout Request Schema | CreateCheckoutSessionRequest(search_space_id, quantity) — cho page packs |
stripe_routes.py |
| Stripe Client | Đã tồn tại — get_stripe_client(), config từ env vars |
stripe_routes.py |
| Success/Cancel URLs | Đã tồn tại — _get_checkout_urls() |
stripe_routes.py |
| Stripe Config | STRIPE_SECRET_KEY, STRIPE_PRICE_ID, STRIPE_WEBHOOK_SECRET đã có |
config.py |
| User ↔ Stripe mapping | Không có stripe_customer_id trên User model |
db.py |
Gap: Cần thêm endpoint subscription checkout mới (giữ endpoint page purchase cũ nếu muốn). Cần stripe_customer_id để Stripe quản lý recurring billing.
Tasks / Subtasks
-
Task 1: Thêm
stripe_customer_idvào User (Backend DB)- Subtask 1.1: Alembic migration thêm column
stripe_customer_id(String, nullable, unique, indexed) vàoUser. - Subtask 1.2: Tạo helper function
get_or_create_stripe_customer(user)— nếustripe_customer_idnull → gọistripe.customers.create(email=user.email)→ lưu ID vào DB.
- Subtask 1.1: Alembic migration thêm column
-
Task 2: Tạo Subscription Checkout Endpoint (Backend)
- Subtask 2.1: Tạo endpoint
POST /api/v1/stripe/create-subscription-checkout. - Subtask 2.2: Request body:
{ "plan_id": "pro_monthly" | "pro_yearly" }. - Subtask 2.3: Map
plan_id→ Stripe Price ID từ env vars (STRIPE_PRO_MONTHLY_PRICE_ID,STRIPE_PRO_YEARLY_PRICE_ID). Tuyệt đối không nhận price từ frontend — phòng tránh giả mạo giá. - Subtask 2.4: Gọi
stripe.checkout.sessions.create(mode='subscription', customer=stripe_customer_id, ...). - Subtask 2.5: Trả về
{ "checkout_url": "https://checkout.stripe.com/..." }.
- Subtask 2.1: Tạo endpoint
-
Task 3: Kết nối Frontend với Endpoint mới
- Subtask 3.1: Từ
pricing-section.tsx, nút "Upgrade to Pro" gọiPOST /api/v1/stripe/create-subscription-checkoutvớiplan_id. - Subtask 3.2: Redirect đến
checkout_url. - Subtask 3.3: Xử lý success return URL — hiển thị toast "Subscription activated!"
- Subtask 3.1: Từ
Dev Notes
Giữ song song PAYG và Subscription?
Endpoint page purchase cũ (create-checkout-session với mode='payment') có thể giữ nguyên. Endpoint subscription mới chạy song song. Quyết định business logic.
Security
- Giá
stripe_price_idmap ở backend env vars (STRIPE_PRO_MONTHLY_PRICE_ID,STRIPE_PRO_YEARLY_PRICE_ID). - Frontend chỉ gửi
plan_id(string enum), backend resolve ra Stripe Price ID.
Stripe Customer
Khi tạo subscription checkout, bắt buộc phải có customer parameter. Stripe dùng customer ID để quản lý recurring billing, invoices, payment methods. Nên create Stripe customer ngay khi user đăng ký hoặc lần đầu checkout.
Webhook (xem Story 5.3)
Sau checkout, Stripe sẽ gửi checkout.session.completed → webhook handler cần detect mode='subscription' và activate subscription trên DB.
References
surfsense_backend/app/routes/stripe_routes.py— endpoint PAYG hiện tại (tham khảo pattern)- Stripe Subscription Checkout docs: https://stripe.com/docs/billing/subscriptions/build-subscriptions