--- phase: 03-operator-experience plan: 03 type: execute wave: 2 depends_on: ["03-01"] files_modified: - packages/portal/app/(dashboard)/billing/page.tsx - packages/portal/components/subscription-card.tsx - packages/portal/components/billing-status.tsx - packages/portal/lib/queries.ts autonomous: false requirements: - PRTA-05 must_haves: truths: - "Operator can subscribe to a per-agent monthly plan via Stripe Checkout" - "Operator can upgrade (add agents) and downgrade (remove agents) their subscription" - "Operator can cancel their subscription" - "Operator can manage payment methods and view invoices via Stripe Billing Portal" - "Feature limits are enforced based on subscription state (agents deactivated on cancellation)" - "14-day free trial with full access is available" artifacts: - path: "packages/portal/app/(dashboard)/billing/page.tsx" provides: "Billing management page with subscription status and actions" - path: "packages/portal/components/subscription-card.tsx" provides: "Card showing current plan, agent count, status, trial info" - path: "packages/portal/components/billing-status.tsx" provides: "Status badge for subscription state (trialing, active, past_due, canceled)" key_links: - from: "packages/portal/app/(dashboard)/billing/page.tsx" to: "/api/portal/billing/checkout" via: "POST to create Checkout Session, then redirect to Stripe" pattern: "billing/checkout" - from: "packages/portal/app/(dashboard)/billing/page.tsx" to: "/api/portal/billing/portal" via: "POST to create Billing Portal session, then redirect" pattern: "billing/portal" --- Billing management page with Stripe subscription integration -- subscribe, upgrade, downgrade, cancel, and manage payment via Stripe Billing Portal. Purpose: Operators can self-serve their subscription lifecycle through the portal, with per-agent monthly pricing that matches the "hire an employee" metaphor. Output: Billing page with subscription card, Checkout redirect, Billing Portal redirect, status display. @/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md @/home/adelorenzo/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-operator-experience/03-CONTEXT.md @.planning/phases/03-operator-experience/03-RESEARCH.md @.planning/phases/03-operator-experience/03-01-SUMMARY.md From packages/shared/shared/api/billing.py (created in Plan 01): ```python # POST /api/portal/billing/checkout -> { "checkout_url": "https://checkout.stripe.com/..." } # Body: { "tenant_id": str, "agent_count": int } # POST /api/portal/billing/portal -> { "portal_url": "https://billing.stripe.com/..." } # Body: { "tenant_id": str } # POST /api/webhooks/stripe -> webhook handler (no portal UI interaction) ``` From packages/shared/shared/models/tenant.py (updated in Plan 01): ```python class Tenant(Base): # New billing fields: stripe_customer_id: Mapped[str | None] stripe_subscription_id: Mapped[str | None] stripe_subscription_item_id: Mapped[str | None] subscription_status: Mapped[str] # "none" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" trial_ends_at: Mapped[datetime | None] agent_quota: Mapped[int] ``` Established portal patterns: - shadcn/ui components (Card, Badge, Button, Dialog) - TanStack Query for data fetching - API client in lib/api.ts Task 1: Billing page with subscription management packages/portal/app/(dashboard)/billing/page.tsx, packages/portal/components/subscription-card.tsx, packages/portal/components/billing-status.tsx, packages/portal/lib/queries.ts 1. Create `packages/portal/components/billing-status.tsx`: - Badge component showing subscription status with color coding: - "trialing" -> blue badge with trial end date - "active" -> green badge - "past_due" -> amber badge with "Payment required" text - "canceled" -> red badge - "none" -> gray badge "No subscription" - Uses shadcn/ui Badge component 2. Create `packages/portal/components/subscription-card.tsx`: - shadcn/ui Card displaying: - BillingStatus badge (top right) - Plan name: "AI Employee Plan" (per-agent monthly) - Price: "$49/agent/month" (from user decision) - Current agent count vs quota - Trial info: if trialing, show "Trial ends {date}" with days remaining - Agent count adjuster: +/- buttons to change quantity (triggers Stripe subscription item update) - Action buttons: - If status="none": "Subscribe" button -> creates Checkout Session -> redirects to Stripe - If status="trialing" or "active": "Manage Billing" button -> creates Billing Portal session -> redirects to Stripe hosted portal - If status="past_due": "Update Payment" button -> Billing Portal redirect - If status="canceled": "Resubscribe" button -> new Checkout Session 3. Create `packages/portal/app/(dashboard)/billing/page.tsx`: - Reads tenant_id from query or session - Fetches tenant data (includes billing fields) via existing useTenant() hook - Renders SubscriptionCard - Handles ?session_id= searchParam (Stripe Checkout success redirect): show success toast, refetch tenant - If subscription_status is "past_due": show a top banner warning about payment failure - Per user decision: per-agent monthly pricing ($49/agent/month), 14-day free trial with full access, credit card required upfront 4. Add TanStack Query hooks to `packages/portal/lib/queries.ts`: - useCreateCheckoutSession() — mutation POST /billing/checkout, returns { checkout_url } - useCreateBillingPortalSession() — mutation POST /billing/portal, returns { portal_url } - useUpdateSubscriptionQuantity() — mutation (if needed for +/- agent count) 5. Add "Billing" link to dashboard navigation/sidebar. cd /home/adelorenzo/repos/konstruct/packages/portal && npx next build 2>&1 | tail -20 - Billing page renders subscription card with current status - Subscribe button creates Checkout Session and redirects to Stripe - Manage Billing button redirects to Stripe Billing Portal - Status badges show correct colors for all subscription states - Trial info displayed when status is "trialing" - Past due banner shown when payment has failed - Portal builds without errors Task 2: Verify billing page and subscription UI n/a Human verifies the billing management page: 1. Start the portal: `cd packages/portal && npm run dev` 2. Navigate to /billing — verify subscription card renders 3. Verify status badge shows "No subscription" for new tenant 4. Verify "Subscribe" button is present 5. Verify pricing shows "$49/agent/month" and "14-day free trial" 6. Verify "Billing" link appears in dashboard navigation 7. Check that agent count +/- controls are present Human visual inspection of billing page Operator confirms billing page renders correctly with subscription card, pricing, and action buttons - Portal builds: `cd packages/portal && npx next build` - Billing page renders without errors - All subscription states display correctly - Navigation to billing page works - Operator can see subscription status and pricing on billing page - Subscribe flow initiates Stripe Checkout redirect - Billing Portal accessible for managing payment/invoices - Status badges accurately reflect subscription_status field - Portal builds successfully After completion, create `.planning/phases/03-operator-experience/03-03-SUMMARY.md`