Files
Momento/docs/story-3.7-billing-ux.md
Antigravity bd495be965
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
- Sidebar: dynamic brand-accent colors, brainstorm section restyled
- AI chat general: popup panel with expand/collapse, hides when contextual AI open
- AI chat contextual: tabs reordered (Actions first), X close button, height fix
- Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.)
- Global color cleanup: emerald/orange hardcoded → brand-accent dynamic
- Brainstorm page: orange → brand-accent throughout
- PageEntry animation component added to key pages
- Floating AI button: bg-brand-accent instead of hardcoded black
- i18n: all 15 locales updated with new AI/billing keys
- Billing: freemium quota tracking, BYOK, stripe subscription scaffolding
- Admin: integrated into new design
- AGENTS.md + CLAUDE.md project rules added
2026-05-16 12:59:30 +00:00

244 lines
10 KiB
Markdown
Raw Permalink 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 3.7: Billing & Subscription UX — Complete User Journey
> **Epic:** 3 — The SaaS Commercial Engine
> **Priority:** High
> **Status:** ready-for-dev
> **Depends on:** 3.1 (quota tracking), 3.6 (Stripe tiers)
> **Blocks:** 4.3 (data portability export needs billing status)
---
## Context
The backend billing infrastructure (Stripe checkout, webhook sync, quota tracking via Redis) is fully implemented. The base UI components exist:
- `billing-plans.tsx` — Plan selection cards + Stripe embedded checkout
- `usage-meter.tsx` — Sidebar quota bar with upgrade modal
- `/settings/billing` page — Current plan + upgrade CTA
- API routes — `/billing/status`, `/billing/create-checkout`, `/billing/portal`, `/billing/webhook`, `/usage/current`
However, the user experience has significant gaps that block a production launch:
1. **No detailed usage breakdown** — Users see a tiny progress bar in the sidebar but have no dedicated view showing per-feature consumption
2. **No billing history** — Users cannot view past invoices or download receipts
3. **No inline paywall** — When a quota-exceeded error occurs during an AI action, the user gets a generic error toast instead of being guided to upgrade
4. **No billing cycle overview** — Missing period start/end, next billing date, tokens used this cycle
5. **Upgrade flow not triggered from error states** — Quota exhaustion in AI features shows a toast but doesn't offer a path to resolution
---
## User Stories
### US-1: Detailed Usage Dashboard
**As a** user on any tier,
**I want** to see a detailed breakdown of my AI feature usage for the current billing period,
**So that** I understand exactly where my quota is being consumed.
**Acceptance Criteria:**
- **Given** I am on the Billing settings page
- **When** I scroll below the current plan card
- **Then** I see a "Usage this period" section with a per-feature breakdown:
- Feature name (i18n key)
- Progress bar: used / limit
- Numeric count (e.g., "47 / 100")
- Visual state: normal (violet), warning ≥75% (amber), exhausted 100% (rose)
- **And** for unlimited features (Enterprise), the bar is full and shows "Unlimited"
- **And** the data refreshes every 30s via the existing `useQuery` with `refetchInterval`
- **And** the section shows the period dates (e.g., "May 1 May 31, 2026")
### US-2: Billing History
**As a** paying user,
**I want** to see my past invoices and download them,
**So that** I have records for accounting.
**Acceptance Criteria:**
- **Given** I have an active or past paid subscription
- **When** I view the Billing settings page
- **Then** I see a "Billing History" section below usage
- **And** it lists invoices sorted by date (newest first) with:
- Date
- Amount (formatted in user currency)
- Status (Paid, Pending, Failed) with color badge
- Download PDF link (via Stripe hosted invoice URL)
- **And** for BASIC/free users, the section is hidden entirely
- **And** the "Open Billing Portal" button remains for payment method changes
### US-3: Inline Paywall (Quota Exceeded → Upgrade)
**As a** free user who just exhausted my AI Discovery Pack,
**I want** to see a clear upgrade prompt instead of a cryptic error,
**So that** I can take action immediately.
**Acceptance Criteria:**
- **Given** I trigger an AI feature (e.g., auto-tag) and my quota is exhausted
- **When** the API returns a `QuotaExceededError`
- **Then** the UI shows an inline paywall panel (not just a toast) with:
- Clear message: "You've used all your [feature] credits this month"
- Two CTAs:
1. "Upgrade to Pro" → links to `/settings/billing`
2. "Use your own API key" → links to `/settings/ai#byok`
- A "Maybe later" dismiss button
- **And** the paywall replaces the AI action result area (not a full-page overlay)
- **And** the paywall auto-dismisses if the user navigates away or after 10s
### US-4: Billing Cycle Overview
**As a** paying user,
**I want** to see my current billing period details,
**So that** I know when I'll be charged next and how much I've used.
**Acceptance Criteria:**
- **Given** I have an active paid subscription
- **When** I view the Billing page
- **Then** the Current Plan card also shows:
- Billing period: "May 1 May 31, 2026"
- Next billing date (or "Access until [date]" if canceled)
- A small ring/donut chart showing total AI credits used vs total limit (aggregate across features)
### US-5: Upgrade Success Confirmation
**As a** user who just completed checkout,
**I want** to see a clear confirmation that my plan is now active,
**So that** I have confidence the payment worked.
**Acceptance Criteria:**
- **Given** I return from Stripe checkout (via `session_id` URL param)
- **When** the page loads
- **Then** I see a success banner at the top: "Welcome to [Plan Name]! Your subscription is now active."
- **And** the banner auto-dismisses after 5s
- **And** the usage meter in the sidebar updates immediately
- **And** the plan cards section is hidden (replaced with current plan + manage)
---
## Technical Design
### Architecture
All changes are **frontend-only** — no new API routes needed. The existing APIs provide all necessary data:
| Data Need | Existing API | Field |
|---|---|---|
| Per-feature quotas | `GET /api/usage/current` | `quotas[feature].{used,limit,remaining}` |
| Billing status | `GET /api/billing/status` | `tier, status, currentPeriodEnd, cancelAtPeriodEnd` |
| Invoices | Stripe Customer Portal | Via `/api/billing/portal` redirect |
| Checkout | `POST /api/billing/create-checkout` | `{clientSecret, url}` |
### Component Structure
```
settings/billing/page.tsx
├── BillingPlans (existing — current plan card + plan selection)
│ ├── CurrentPlanCard (enhanced with billing cycle dates + mini usage ring)
│ ├── PlanCard[] (existing — unchanged for BASIC)
│ └── ManageBilling (existing — portal button for paid users)
├── UsageBreakdown (NEW — per-feature usage dashboard)
│ ├── PeriodHeader ("Usage for May 131, 2026")
│ └── FeatureRow[] (one per feature: label + progress bar + count)
└── BillingHistory (NEW — for paid users, via Stripe portal link)
```
```
components/quota-paywall.tsx (NEW — inline paywall)
├── Message ("You've used all X credits")
├── CTAs (Upgrade link + BYOK link)
└── Dismiss button
```
### Files to Create
| File | Description |
|---|---|
| `components/settings/usage-breakdown.tsx` | Detailed per-feature usage table with progress bars |
| `components/settings/billing-history.tsx` | Invoice list with download links (via Stripe) |
| `components/quota-paywall.tsx` | Inline paywall component for quota-exceeded errors |
### Files to Modify
| File | Change |
|---|---|
| `app/(main)/settings/billing/page.tsx` | Add `UsageBreakdown` + `BillingHistory` sections |
| `components/settings/billing-plans.tsx` | Enhance `CurrentPlanCard` with billing period dates + mini usage ring |
| `components/usage-meter.tsx` | Add click handler to navigate to `/settings/billing` (not just on exhausted) |
| Various AI feature components | Catch `QuotaExceededError` and show `QuotaPaywall` instead of toast |
### Design Tokens (Existing — No New Colors)
| Element | Token/Class |
|---|---|
| Section header | `text-[11px] font-bold uppercase tracking-[0.15em] text-muted-foreground` |
| Card container | `rounded-xl border border-border/40 bg-paper p-6 space-y-4` |
| Progress bar track | `h-1.5 rounded-full bg-secondary/40` |
| Progress bar fill (normal) | `bg-gradient-to-r from-violet-400 to-purple-400` |
| Progress bar fill (warning ≥75%) | `bg-amber-400` |
| Progress bar fill (exhausted) | `bg-rose-400` |
| Feature count text | `text-[11px] text-muted-ink tabular-nums` |
| Badge (active) | `bg-emerald-100 text-emerald-700` |
| Badge (past due) | `bg-amber-100 text-amber-700` |
| CTA primary | `bg-[#D4A373] text-white hover:bg-[#C49363]` (highlighted) |
| CTA secondary | `border border-border hover:bg-foreground/5` |
| Paywall panel | `rounded-xl border border-rose-200 bg-rose-50/50 dark:border-rose-800/40 dark:bg-rose-900/10 p-4` |
### i18n Keys to Add (15 locales)
```json
{
"billing": {
"usageThisPeriod": "Usage this period",
"periodRange": "{start} {end}",
"featureUsedLimit": "{used} / {limit}",
"unlimited": "Unlimited",
"noUsage": "No AI usage yet this period.",
"billingHistory": "Billing History",
"noInvoices": "No invoices yet.",
"viewInvoices": "View all invoices in Stripe portal",
"nextBillingDate": "Next billing date",
"billingPeriod": "Billing period",
"planSince": "Subscribed since {date}"
},
"quotaPaywall": {
"title": "Monthly limit reached",
"description": "You've used all your {feature} credits for this month.",
"upgrade": "Upgrade plan",
"useOwnKey": "Use your own API key",
"later": "Maybe later"
}
}
```
---
## Out of Scope
- **Stripe Customer Portal customization** — uses the default hosted portal (already configured)
- **Downgrade flow** — handled via Stripe portal (out of scope for this story)
- **Enterprise self-serve** — "Contact Sales" mailto link is sufficient
- **Real-time WebSocket quota updates** — polling every 30s is sufficient
- **Admin billing dashboard** — deferred to a later epic
- **Currency switching** — EUR default, handled by Stripe locale
---
## Testing Notes
- **Unit**: `billing-sync.test.ts`, `billing-price-map.test.ts` already exist and pass
- **Integration**: Test checkout flow with Stripe test mode (`sk_test_` keys)
- **Visual**: Verify in both light and dark mode
- **i18n**: All 15 locale files must have the new keys
- **Responsive**: Plan cards grid must collapse to single column on mobile (<768px)
- **Edge cases**:
- User with no subscription (BASIC) should see only usage + upgrade cards
- User with canceled subscription should see "Access until [date]" + no upgrade cards
- User with past_due subscription should see warning banner
- Redis down → usage section shows "Unable to load usage" (fail-open on API, graceful on UI)
---
## Estimated Effort
| Task | Hours |
|---|---|
| `usage-breakdown.tsx` component | 2h |
| `billing-history.tsx` component | 1.5h |
| `quota-paywall.tsx` component | 1.5h |
| Enhance `billing-plans.tsx` with cycle info | 1h |
| Wire paywall into AI feature error handlers | 2h |
| i18n keys (15 locales) | 1h |
| Visual testing + dark mode | 1h |
| **Total** | **~10h** |