SurfSense/_bmad-output/implementation-artifacts/5-1-pricing-plan-selection-ui.md
Vonic 04fb9eec0f docs: rewrite story 3.5 and epic 5 stories to match actual codebase
Stories were written for a subscription SaaS model, but SurfSense is a
self-hosted product with BYOK + optional PAYG page packs via Stripe.

Key corrections:
- Story 3.5: Not "remove BYOK + token billing" → actual gap is adding
  HTTP-layer quota pre-check before document upload enqueue
- Story 5.1: Pricing UI already exists (Free/PAYG/Enterprise) → gap is
  wiring "Get Started" button to existing Stripe checkout endpoint
- Story 5.2: mode=payment PAYG already works → needs verification/hardening
  not a subscription checkout rewrite
- Story 5.3: Webhook already handles checkout.session.completed correctly
  → no subscription events needed, gap is idempotency test + purchase history UI
- Story 5.4: PageLimitService + enforcement in tasks/connectors already exists
  → gap is HTTP-layer pre-check, quota UI indicator, and 402 frontend handling

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

76 lines
3.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Story 5.1: Kết nối Pricing UI với Stripe Checkout
Status: ready-for-dev
## Context / Correction Note
> **⚠️ Story gốc bị sai hướng.** Story gốc mô tả tạo mới pricing page với Free/Pro/Team subscription tiers. Thực tế, pricing page **đã tồn tại** với mô hình PAYG (Pay-As-You-Go page packs), không phải subscription. Stripe checkout endpoint cũng đã tồn tại. Task thực tế là **wire up** nút "Get Started" của PAYG tier với endpoint hiện có.
## Story
As a Người dùng đã đăng nhập,
I want bấm "Get Started" trên trang Pricing để mua page packs,
so that tôi có thể tiếp tục upload tài liệu sau khi hết quota.
## Actual Architecture (as-is)
**Đã tồn tại và hoạt động:**
- `surfsense_web/app/(home)/pricing/page.tsx` — pricing page route
- `surfsense_web/components/pricing/pricing-section.tsx` — UI với 3 tiers:
- **FREE**: 500 pages included, button href="/login"
- **PAY AS YOU GO**: $1/1,000 pages, button href="/login" ← **cần sửa**
- **ENTERPRISE**: Contact Sales, button href="/contact"
- `surfsense_backend/app/routes/stripe_routes.py:create_checkout_session` — endpoint `POST /api/v1/stripe/create-checkout-session` đã implement, mode=`payment`, yêu cầu `search_space_id``quantity`
**Chưa làm:**
- Nút "Get Started" của PAYG tier chỉ link đến `/login`, chưa gọi Stripe checkout
- Không có flow chọn số lượng page packs (quantity)
## Acceptance Criteria
1. Khi user đã đăng nhập bấm "Get Started" ở PAYG tier, hiện modal/form cho phép chọn số lượng pack (1, 5, 10, etc.).
2. Sau khi confirm, gọi `POST /api/v1/stripe/create-checkout-session` với `quantity``search_space_id`, nhận `checkout_url`.
3. Redirect user đến `checkout_url` (Stripe-hosted checkout page).
4. Nếu user chưa đăng nhập, redirect đến `/login` trước (behavior hiện tại giữ nguyên cho FREE tier).
## Tasks / Subtasks
- [ ] Task 1: Cập nhật nút PAYG trong `pricing-section.tsx`
- [ ] Subtask 1.1: Thay `href="/login"` bằng `onClick` handler. Nếu user chưa authenticated, redirect `/login`. Nếu đã authenticated, mở modal chọn quantity.
- [ ] Subtask 1.2: Tạo `PurchasePagesModal` component với dropdown/input chọn số pack (110), hiển thị tổng tiền (`quantity × $1`).
- [ ] Task 2: Gọi Stripe checkout API
- [ ] Subtask 2.1: Khi user confirm trong modal, gọi `POST /api/v1/stripe/create-checkout-session` với `{ quantity, search_space_id }`.
- [ ] Subtask 2.2: Nhận `checkout_url` và redirect bằng `window.location.href = checkout_url`.
- [ ] Task 3: Xử lý return URL sau checkout
- [ ] Subtask 3.1: Kiểm tra success/cancel URL config hiện tại trong `stripe_routes.py` (`_get_checkout_urls`).
- [ ] Subtask 3.2: Sau purchase thành công, hiển thị toast "Mua thành công! X pages đã được thêm vào tài khoản."
## Dev Notes
### Stripe Checkout Request Schema (hiện tại)
```python
class CreateCheckoutSessionRequest(BaseModel):
search_space_id: int
quantity: int # số pack, mỗi pack = STRIPE_PAGES_PER_UNIT pages
```
### API Endpoint
```
POST /api/v1/stripe/create-checkout-session
Authorization: Bearer <token>
Body: { "search_space_id": 1, "quantity": 2 }
Response: { "checkout_url": "https://checkout.stripe.com/..." }
```
### References
- `surfsense_web/components/pricing/pricing-section.tsx`
- `surfsense_backend/app/routes/stripe_routes.py` (lines ~204271)
- `surfsense_web/app/dashboard/[search_space_id]/purchase-cancel/page.tsx`
## Dev Agent Record
### Agent Model Used
_TBD_
### File List
- `surfsense_web/components/pricing/pricing-section.tsx`
- `surfsense_web/components/pricing/PurchasePagesModal.tsx` (new)