|
| 1 | +# Phase 2 Tasks — Harden Checkout, Tenancy, Newsletter |
| 2 | + |
| 3 | +Date: 2025-11-13 |
| 4 | +Branch: 002-harden-checkout-tenancy |
| 5 | + |
| 6 | +This plan breaks the specification into implementable tasks with owners, concrete file targets, validation criteria, and tests. Each task references FR/SC items from the spec. Follow project standards in .github/instructions/* and constitution limits (files <300 lines, functions <50 lines, no any, WCAG AA, multi-tenant isolation). |
| 7 | + |
| 8 | + |
| 9 | +## 0) Cross-cutting foundations |
| 10 | + |
| 11 | +- T0.1 Add standardized API response helpers (FR-008, FR-021) |
| 12 | + - Files: `src/lib/api-response.ts` (new), wire-in in key routes |
| 13 | + - Implement success({ data, meta?, message? }), error({ code, message, details? }) |
| 14 | + - Inject `X-Request-Id` header; expose helper to generate/propagate requestId |
| 15 | + - Tests: `tests/unit/api-response.test.ts` |
| 16 | + |
| 17 | +- T0.2 Request context and correlation ID propagation (FR-021) |
| 18 | + - Files: `src/lib/request-context.ts` (new) using AsyncLocalStorage to store requestId, storeId |
| 19 | + - Adapter in proxy to seed requestId; server routes to pick it up |
| 20 | + - Tests: `tests/unit/request-context.test.ts` |
| 21 | + |
| 22 | +- T0.3 Error class hierarchy and mapper (FR-008) |
| 23 | + - Files: `src/lib/errors.ts` (new) with typed AppError(code, httpStatus, details) |
| 24 | + - Mapper to JSON shape used by api-response |
| 25 | + - Tests: `tests/unit/errors.test.ts` |
| 26 | + |
| 27 | + |
| 28 | +## 1) Secure Checkout hardening (P1) |
| 29 | + |
| 30 | +- T1.1 Enforce authentication and tenant scope in checkout endpoints (FR-001, FR-009) |
| 31 | + - Files: `src/app/api/checkout/complete/route.ts`, `src/app/api/checkout/validate/route.ts` |
| 32 | + - Use `getServerSession(authOptions)`, reject 401 when not authenticated |
| 33 | + - Read storeId from session.user.storeId; remove body storeId usage |
| 34 | + - Add `dynamic = 'force-dynamic'` where route reads headers/cookies |
| 35 | + - Tests: `tests/integration/checkout-auth.test.ts` |
| 36 | + |
| 37 | +- T1.2 Ignore client monetary fields; recalc all totals server-side (FR-002) |
| 38 | + - Files: `src/app/api/checkout/complete/route.ts`, `src/services/checkout-service.ts` |
| 39 | + - Remove acceptance of price/subtotal/taxes in request (keep only items[productId, variantId, quantity], addresses, shippingMethod) |
| 40 | + - Ensure `validateCart` and total calculation drive amounts exclusively |
| 41 | + - Tests: update `src/services/__tests__/checkout-service.test.ts` and add `tests/integration/checkout-recalc.test.ts` (send tampered prices → server ignores) |
| 42 | + |
| 43 | +- T1.3 Validate payment intent/token before order creation (FR-003) |
| 44 | + - Files: `src/services/payment-service.ts` (add `preValidatePaymentIntent(paymentIntentId|token, expectedAmount, currency)`), |
| 45 | + `src/app/api/checkout/complete/route.ts` |
| 46 | + - Flow: compute totals → call preValidate → on success, create order in tx and create payment record with AUTHORIZED/PAID as appropriate |
| 47 | + - Tests: `tests/unit/payment-prevalidate.test.ts` (mock Stripe), `tests/integration/checkout-payment-guard.test.ts` |
| 48 | + |
| 49 | +- T1.4 Wrap order+items+inventory+payment record in one atomic transaction (FR-004) |
| 50 | + - Files: `src/services/checkout-service.ts` (ensure `$transaction` covers inventory decrement and payment record write) |
| 51 | + - Add compensating safeguards: if any write fails, no partial data persists |
| 52 | + - Tests: `tests/integration/checkout-atomicity.test.ts` (fail inventory → no order created) |
| 53 | + |
| 54 | +- T1.5 Plan enforcement at checkout (optional guard) |
| 55 | + - Files: `src/lib/plan-enforcement.ts` (already exists) — verify check and call before order create; return 402 equivalent error if over limit |
| 56 | + - Tests: `tests/unit/plan-enforcement.checkout.test.ts` |
| 57 | + |
| 58 | + |
| 59 | +## 2) Multi-tenancy and canonical domains (P2) |
| 60 | + |
| 61 | +- T2.1 Store resolution from host + canonical redirect (FR-005, FR-020) |
| 62 | + - Schema: add `StoreDomain` model |
| 63 | + - `id`, `storeId`, `domain` (unique), `isPrimary` (canonical), timestamps |
| 64 | + - Files: `prisma/schema.prisma` (model + `@@unique([domain])`), migration; seed example |
| 65 | + - Files: `src/lib/store-resolver.ts` (new): parse host, resolve `storeId` by domain OR `slug.subdomain` |
| 66 | + - Proxy: add canonical redirect: if request for subdomain and primary custom domain exists → 308 redirect |
| 67 | + - Tests: `tests/unit/store-resolver.test.ts`, `tests/integration/canonical-redirect.test.ts` |
| 68 | + |
| 69 | +- T2.2 Remove hardcoded store IDs and enforce tenant scoping (FR-006, FR-009) |
| 70 | + - Sweep: search for `storeId: "..."` literals and replace with session context or resolver |
| 71 | + - Ensure Prisma middleware `registerMultiTenantMiddleware` is seeded via request context in API routes |
| 72 | + - Files: various services/routes; add minimal `setStoreIdContext()` calls in each API route’s handler entry |
| 73 | + - Tests: `tests/integration/tenant-isolation.test.ts` (cross-tenant access must fail) |
| 74 | + |
| 75 | +- T2.3 Per-store dynamic metadata (FR-020) |
| 76 | + - Files: add `generateMetadata` in key storefront routes to pull store title/description/social image |
| 77 | + - Tests: `tests/integration/metadata-storefront.test.ts` |
| 78 | + |
| 79 | + |
| 80 | +## 3) Newsletter subscription (P2) |
| 81 | + |
| 82 | +- T3.1 Server Action for subscribe (single opt-in + consent + audit) (FR-007, FR-019) |
| 83 | + - Files: `src/app/(storefront)/newsletter/actions.ts` (new, 'use server') with Zod validation |
| 84 | + - Write to `Newsletter` with `@@unique([storeId,email])`; respect DNT header (process essential only) |
| 85 | + - Create `ConsentRecord` row and `AuditLog` for subscription |
| 86 | + - Rate limit: call simple limiter (100 rpm/IP) per store/email |
| 87 | + - Tests: `tests/integration/newsletter-action.test.ts` (valid/invalid/duplicate/DNT) |
| 88 | + |
| 89 | +- T3.2 Minimal API endpoint for integrations (optional) |
| 90 | + - Files: `src/app/api/subscriptions/route.ts` — wrapper to call server action for external clients |
| 91 | + - Tests: `tests/integration/newsletter-api.test.ts` |
| 92 | + |
| 93 | + |
| 94 | +## 4) API pipeline and consistency (P3) |
| 95 | + |
| 96 | +- T4.1 Apply api-response and errors to selected routes (FR-008, FR-014, FR-015, FR-021) |
| 97 | + - Routes: orders, checkout, inventory, products (representative set) |
| 98 | + - Ensure `X-Request-Id` set on all responses; proxy seeds it if missing |
| 99 | + - Tests: `tests/integration/api-consistency.test.ts` |
| 100 | + |
| 101 | +- T4.2 Proxy security headers/rate limit already present — extend to add requestId seeding |
| 102 | + - Files: `proxy.ts` (generate UUID v4 if header missing, store in ALS) |
| 103 | + - Tests: `tests/integration/request-id.test.ts` |
| 104 | + |
| 105 | + |
| 106 | +## 5) Caching and invalidation (P3) |
| 107 | + |
| 108 | +- T5.1 Cache tag util + invalidation (FR-011) |
| 109 | + - Files: `src/lib/cache-tags.ts` (new): helpers for tag name construction |
| 110 | + - On create/update/delete in product/category/page services → call `revalidateTag(tag, 'max')` |
| 111 | + - Tests: `tests/unit/cache-tags.test.ts` |
| 112 | + |
| 113 | +- T5.2 Document phased Cache Components plan (FR-012) |
| 114 | + - Files: `docs/cache-components-plan.md` (new) — enablement guardrails and rollout |
| 115 | + |
| 116 | + |
| 117 | +## 6) CSV export (P3) |
| 118 | + |
| 119 | +- T6.1 Streaming for ≤10k, async for >10k (FR-016) |
| 120 | + - Files: `src/services/bulk-export-service.ts`, `src/app/api/orders/route.ts` |
| 121 | + - Implement row counting; if >10k, enqueue background job (existing job infra or stub) and notify via email + in-app `Notification` |
| 122 | + - Tests: `tests/integration/orders-export.test.ts` (mock dataset sizes) |
| 123 | + |
| 124 | + |
| 125 | +## 7) Dashboard and accessibility polish (P3) |
| 126 | + |
| 127 | +- T7.1 Redirect unauthenticated dashboard access (FR-017) |
| 128 | + - Already handled via proxy; add e2e coverage |
| 129 | + - Tests: `tests/e2e/auth-redirect.spec.ts` |
| 130 | + |
| 131 | +- T7.2 Charts/analytics accessible markup (FR-018) |
| 132 | + - Files: targeted pages/components under `src/app/(dashboard)` |
| 133 | + - Wrap charts in `<figure>`/`<figcaption>` with textual summary; ensure focus rings |
| 134 | + - Tests: axe-core scan `tests/integration/a11y-analytics.test.ts` |
| 135 | + |
| 136 | + |
| 137 | +## 8) Testing and coverage (P3) |
| 138 | + |
| 139 | +- T8.1 Unit tests to ≥80% for services, 100% for utilities (FR-013) |
| 140 | + - Add/expand tests covering fraud scenarios, tenancy isolation, newsletter flows, standardized errors |
| 141 | + |
| 142 | +- T8.2 E2E Playwright flows (checkout, tenancy, newsletter, export) (FR-013) |
| 143 | + - Files: `tests/e2e/checkout.spec.ts`, `tests/e2e/tenancy.spec.ts`, `tests/e2e/newsletter.spec.ts`, `tests/e2e/export.spec.ts` |
| 144 | + |
| 145 | + |
| 146 | +## Deliverables checklist (tie to Success Criteria) |
| 147 | + |
| 148 | +- [ ] SC-001 Security review passes: no high-severity findings in checkout/tenancy |
| 149 | +- [ ] SC-002 Tenant isolation verified; no hardcoded store IDs |
| 150 | +- [ ] SC-003 Checkout E2E validates server-side pricing; failure paths abort cleanly |
| 151 | +- [ ] SC-004 Newsletter E2E: consent + audit + idempotency + rate limit + DNT |
| 152 | +- [ ] SC-005 Middleware instrumentation confirms tenant scoping |
| 153 | +- [ ] SC-006 Migrations apply cleanly; data integrity verified |
| 154 | +- [ ] SC-007 API responses standardized + X-Request-Id everywhere |
| 155 | +- [ ] SC-008 Cache invalidation proves timely updates |
| 156 | +- [ ] SC-009 Coverage thresholds hit; tests in place |
| 157 | +- [ ] SC-010 Perf & a11y budgets within thresholds |
| 158 | +- [ ] SC-011 CSV export: streamed ≤10k, async >10k with email + in-app |
| 159 | +- [ ] SC-012 Dashboard unauthenticated redirect verified |
| 160 | +- [ ] SC-013 Per-store metadata present and validated |
| 161 | +- [ ] SC-014 Analytics markup accessible |
| 162 | + |
| 163 | + |
| 164 | +## Execution order (safe incremental rollout) |
| 165 | + |
| 166 | +1) T0 foundations (responses, request context, errors) |
| 167 | +2) T1 secure checkout (auth, recalc, payment pre-validate, atomic tx) |
| 168 | +3) T2 store resolver + canonical redirect + tenant scoping sweep |
| 169 | +4) T3 newsletter server action + endpoint |
| 170 | +5) T4 API consistency + requestId propagation in proxy |
| 171 | +6) T5 caching utilities + invalidations + docs |
| 172 | +7) T6 CSV export streaming/async |
| 173 | +8) T7 a11y polish |
| 174 | +9) T8 testing/coverage expansion |
| 175 | + |
| 176 | + |
| 177 | +## Notes and risks |
| 178 | + |
| 179 | +- Payment pre-validation requires Stripe secret key; in dev/tests use mocks guarded by env fallbacks. Create `.env.local` placeholders if missing. |
| 180 | +- Adding StoreDomain model introduces a migration; coordinate seed data and dev db push. Ensure backwards-compatible fallbacks (use `Store.slug` for subdomain if no domain records). |
| 181 | +- Ensure Next.js 16 async params/searchParams and cookies/headers await semantics are preserved in any touched routes. |
| 182 | +- Respect constitution size limits by extracting helper functions to utilities; avoid monolith handlers. |
0 commit comments