feat(story-3.6): complete Stripe subscription tiers — enterprise card, build fix, i18n
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 4s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 4s
Story 3.6: Stripe Subscription Tiers - Verified all pre-existing billing implementation (API routes, webhook, sync, UI) - Added Enterprise plan card with contact sales CTA (mailto:sales@momento.app) - Fixed lib/stripe.ts build error (lazy getStripe() + placeholder default) - Added enterpriseFeature1-5 i18n keys to all 15 locales - 22/22 unit tests pass, build succeeds - Story status: ready-for-dev → review
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Story 3.6: Stripe Subscription Tiers
|
||||
|
||||
Status: ready-for-dev
|
||||
Status: review
|
||||
|
||||
<!-- Ultimate context engine analysis completed - comprehensive developer guide created -->
|
||||
|
||||
@@ -33,31 +33,31 @@ so that I can unlock higher quotas and features.
|
||||
|
||||
## Tasks / Subtasks
|
||||
|
||||
- [ ] Task 1: Dependencies & Stripe client (AC: #9)
|
||||
- [ ] Subtask 1.1: Add `stripe` npm package (+ `@stripe/stripe-js` if embedded checkout client-side)
|
||||
- [ ] Subtask 1.2: Create `lib/stripe.ts` — singleton `Stripe` server client, `getStripe()`, price ID map from env
|
||||
- [ ] Subtask 1.3: Extend `memento-note/.env.example` with `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `STRIPE_PRICE_PRO_MONTHLY`, `STRIPE_PRICE_PRO_ANNUAL`, `STRIPE_PRICE_BUSINESS_MONTHLY`, `STRIPE_PRICE_BUSINESS_ANNUAL`, `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`, `NEXT_PUBLIC_FEATURE_BILLING_ENABLED`
|
||||
- [ ] Task 2: Billing API routes (AC: #1, #4, #9)
|
||||
- [ ] Subtask 2.1: `POST /api/billing/create-checkout` — body `{ tier: 'PRO'|'BUSINESS', interval: 'month'|'year' }`; create/find Stripe Customer; create Checkout Session (`mode: 'subscription'`, metadata `userId`, `tier`)
|
||||
- [ ] Subtask 2.2: Return `{ clientSecret }` for embedded checkout OR `{ url }` for redirect mode
|
||||
- [ ] Subtask 2.3: `POST /api/billing/portal` — Stripe Billing Portal session for `stripeCustomerId`
|
||||
- [ ] Subtask 2.4: `GET /api/billing/status` — current tier, status, period end, cancelAtPeriodEnd (for billing page)
|
||||
- [ ] Task 3: Webhook & subscription sync (AC: #2, #5, #6)
|
||||
- [ ] Subtask 3.1: `POST /api/billing/webhook` — **raw body** route config (`export const runtime = 'nodejs'`, disable JSON parse middleware for this route only)
|
||||
- [ ] Subtask 3.2: Implement `lib/billing/sync-subscription-from-stripe.ts` — map Stripe subscription → Prisma `Subscription` upsert
|
||||
- [ ] Subtask 3.3: Map `stripePriceId` → `SubscriptionTier` via env price ID table (single function `priceIdToTier(priceId)`)
|
||||
- [ ] Subtask 3.4: Idempotency: store processed event ids in DB or skip if `updatedAt` on subscription already newer (minimal: trust Stripe retry + upsert by `stripeSubscriptionId`)
|
||||
- [ ] Task 4: Billing UI (AC: #1, #3, #7, #8)
|
||||
- [ ] Subtask 4.1: `app/(main)/settings/billing/page.tsx` + client component — plan cards, interval toggle, embedded checkout mount
|
||||
- [ ] Subtask 4.2: Add Billing to `SettingsNav` (CreditCard icon), i18n **all 15** locale files
|
||||
- [ ] Subtask 4.3: Update `UsageMeter` upgrade CTA: `href="/settings/billing"`; optional: open checkout modal directly from exhausted state (stretch)
|
||||
- [ ] Subtask 4.4: On checkout success callback: toast + `queryClient.invalidateQueries(['usage','current'])`
|
||||
- [ ] Subtask 4.5: Enterprise card → contact sales (no checkout)
|
||||
- [ ] Task 5: Tests & local dev (AC: all)
|
||||
- [ ] Subtask 5.1: `tests/unit/billing-price-map.test.ts` — priceId → tier mapping
|
||||
- [ ] Subtask 5.2: `tests/unit/billing-sync.test.ts` — mock Stripe subscription object → Prisma upsert payload
|
||||
- [ ] Subtask 5.3: Document `stripe listen --forward-to localhost:3000/api/billing/webhook` in Dev Agent Record
|
||||
- [ ] Subtask 5.4: `npm run build` in `memento-note/`
|
||||
- [x] Task 1: Dependencies & Stripe client (AC: #9)
|
||||
- [x] Subtask 1.1: Add `stripe` npm package (+ `@stripe/stripe-js` if embedded checkout client-side)
|
||||
- [x] Subtask 1.2: Create `lib/stripe.ts` — singleton `Stripe` server client, `getStripe()`, price ID map from env
|
||||
- [x] Subtask 1.3: Extend `memento-note/.env.example` with `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `STRIPE_PRICE_PRO_MONTHLY`, `STRIPE_PRICE_PRO_ANNUAL`, `STRIPE_PRICE_BUSINESS_MONTHLY`, `STRIPE_PRICE_BUSINESS_ANNUAL`, `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`, `NEXT_PUBLIC_FEATURE_BILLING_ENABLED`
|
||||
- [x] Task 2: Billing API routes (AC: #1, #4, #9)
|
||||
- [x] Subtask 2.1: `POST /api/billing/create-checkout` — body `{ tier: 'PRO'|'BUSINESS', interval: 'month'|'year' }`; create/find Stripe Customer; create Checkout Session (`mode: 'subscription'`, metadata `userId`, `tier`)
|
||||
- [x] Subtask 2.2: Return `{ clientSecret }` for embedded checkout OR `{ url }` for redirect mode
|
||||
- [x] Subtask 2.3: `POST /api/billing/portal` — Stripe Billing Portal session for `stripeCustomerId`
|
||||
- [x] Subtask 2.4: `GET /api/billing/status` — current tier, status, period end, cancelAtPeriodEnd (for billing page)
|
||||
- [x] Task 3: Webhook & subscription sync (AC: #2, #5, #6)
|
||||
- [x] Subtask 3.1: `POST /api/billing/webhook` — **raw body** route config (`export const runtime = 'nodejs'`, disable JSON parse middleware for this route only)
|
||||
- [x] Subtask 3.2: Implement `lib/billing/sync-subscription-from-stripe.ts` — map Stripe subscription → Prisma `Subscription` upsert
|
||||
- [x] Subtask 3.3: Map `stripePriceId` → `SubscriptionTier` via env price ID table (single function `priceIdToTier(priceId)`)
|
||||
- [x] Subtask 3.4: Idempotency: store processed event ids in DB or skip if `updatedAt` on subscription already newer (minimal: trust Stripe retry + upsert by `stripeSubscriptionId`)
|
||||
- [x] Task 4: Billing UI (AC: #1, #3, #7, #8)
|
||||
- [x] Subtask 4.1: `app/(main)/settings/billing/page.tsx` + client component — plan cards, interval toggle, embedded checkout mount
|
||||
- [x] Subtask 4.2: Add Billing to `SettingsNav` (CreditCard icon), i18n **all 15** locale files
|
||||
- [x] Subtask 4.3: Update `UsageMeter` upgrade CTA: `href="/settings/billing"`; optional: open checkout modal directly from exhausted state (stretch)
|
||||
- [x] Subtask 4.4: On checkout success callback: toast + `queryClient.invalidateQueries(['usage','current'])`
|
||||
- [x] Subtask 4.5: Enterprise card → contact sales (no checkout)
|
||||
- [x] Task 5: Tests & local dev (AC: all)
|
||||
- [x] Subtask 5.1: `tests/unit/billing-price-map.test.ts` — priceId → tier mapping
|
||||
- [x] Subtask 5.2: `tests/unit/billing-sync.test.ts` — mock Stripe subscription object → Prisma upsert payload
|
||||
- [x] Subtask 5.3: Document `stripe listen --forward-to localhost:3000/api/billing/webhook` in Dev Agent Record
|
||||
- [x] Subtask 5.4: `npm run build` in `memento-note/`
|
||||
|
||||
---
|
||||
|
||||
@@ -370,14 +370,57 @@ Per `CLAUDE.md`: backup before any migration. **This story should not require sc
|
||||
|
||||
### Agent Model Used
|
||||
|
||||
{{agent_model_name_version}}
|
||||
glm-5.1 (via opencode)
|
||||
|
||||
### Debug Log References
|
||||
|
||||
- Build failed initially due to `STRIPE_SECRET_KEY` check at module evaluation time in `lib/stripe.ts`. Fixed by using placeholder default `sk_test_placeholder` with lazy `getStripe()` for runtime validation.
|
||||
- Most of the story was **pre-implemented** — Tasks 1-3 backend, Task 4 UI, and Task 5 tests were already in place. Only Task 4.5 (Enterprise card) and the `lib/stripe.ts` build fix were needed.
|
||||
|
||||
### Completion Notes List
|
||||
|
||||
- **Task 1:** `stripe` (^22.1.1), `@stripe/stripe-js` (^9.5.0), `@stripe/react-stripe-js` (^6.3.0) already installed. `lib/stripe.ts` already existed — added lazy `getStripe()` function for runtime safety. `.env.example` already documented all `STRIPE_*` vars.
|
||||
- **Task 2:** All 4 billing API routes (`create-checkout`, `portal`, `status`, `webhook`) already implemented with auth, embedded checkout support, and proper error handling.
|
||||
- **Task 3:** Webhook handler handles `checkout.session.completed`, `customer.subscription.created/updated/deleted`, `invoice.payment_failed`. `sync-subscription-from-stripe.ts` maps Stripe statuses to Prisma enums correctly. Idempotent via `upsert` on `stripeSubscriptionId`.
|
||||
- **Task 4:** Billing page with plan cards, interval toggle, embedded checkout modal, usage overview grid, billing info panel. SettingsNav includes Billing with CreditCard icon. UsageMeter links to `/settings/billing`. **Added Enterprise card** with `mailto:sales@momento.app` CTA (no checkout). Added `enterpriseFeature1-5` i18n keys to all 15 locales.
|
||||
- **Task 5:** 22 unit tests pass (billing-price-map: 10, billing-sync: 12). Build succeeds.
|
||||
|
||||
### Local Development
|
||||
|
||||
To test Stripe integration locally:
|
||||
1. Set `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, and price IDs in `.env`
|
||||
2. Run `stripe listen --forward-to localhost:3000/api/billing/webhook`
|
||||
3. Use test card `4242 4242 4242 4242` for checkout
|
||||
4. Set `NEXT_PUBLIC_FEATURE_BILLING_ENABLED=true` to show billing UI
|
||||
|
||||
### File List
|
||||
|
||||
**NEW (added in this session):**
|
||||
- (none — Enterprise card added inline to existing file)
|
||||
|
||||
**MODIFIED:**
|
||||
- `memento-note/lib/stripe.ts` — added lazy `getStripe()` + placeholder default for build safety
|
||||
- `memento-note/components/settings/billing-plans.tsx` — added Enterprise plan card (contact sales CTA), grid → 4 cols
|
||||
- `memento-note/locales/*.json` (15 files) — added `enterpriseFeature1-5` keys
|
||||
|
||||
**PRE-EXISTING (verified working):**
|
||||
- `memento-note/app/api/billing/create-checkout/route.ts`
|
||||
- `memento-note/app/api/billing/portal/route.ts`
|
||||
- `memento-note/app/api/billing/status/route.ts`
|
||||
- `memento-note/app/api/billing/webhook/route.ts`
|
||||
- `memento-note/lib/billing/stripe-prices.ts`
|
||||
- `memento-note/lib/billing/sync-subscription-from-stripe.ts`
|
||||
- `memento-note/app/(main)/settings/billing/page.tsx`
|
||||
- `memento-note/components/settings/billing-history.tsx`
|
||||
- `memento-note/components/settings/SettingsNav.tsx`
|
||||
- `memento-note/components/usage-meter.tsx`
|
||||
- `memento-note/tests/unit/billing-price-map.test.ts`
|
||||
- `memento-note/tests/unit/billing-sync.test.ts`
|
||||
|
||||
### Change Log
|
||||
|
||||
- 2026-05-16: Story 3.6 completed — verified all pre-existing billing implementation, added Enterprise card with contact sales CTA, fixed lib/stripe.ts build issue, added Enterprise i18n keys to all 15 locales. 22/22 unit tests pass, build succeeds.
|
||||
|
||||
---
|
||||
|
||||
## Story Completion Status
|
||||
@@ -385,5 +428,5 @@ Per `CLAUDE.md`: backup before any migration. **This story should not require sc
|
||||
- Story ID: 3.6
|
||||
- Story Key: `3-6-stripe-subscription-tiers`
|
||||
- File: `docs/3-6-stripe-subscription-tiers.md`
|
||||
- Status: **ready-for-dev**
|
||||
- Status: **review**
|
||||
- Completion Note: Ultimate context engine analysis completed — comprehensive developer guide created.
|
||||
|
||||
Reference in New Issue
Block a user