Prompt Details
Model
(claude-4-6-opus)
Token size
1,197
Example input
[FEATURE_DESCRIPTION]: Shopping cart with real-time price calculation, discount code validation, and guest checkout support
[TECH_STACK]: Next.js TypeScript Stripe API MongoDB Tailwind
[TARGET_FILES]: /lib/cart.ts /pages/api/cart.ts /components/CartDrawer.tsx /pages/checkout.tsx
[REQUIREMENTS]: Users can add and remove items, discount codes validate in real time, guest users can complete checkout, payment errors handled gracefully
[CONSTRAINTS]: No new dependencies, Stripe test mode only, must support mobile viewports, cart state persists across page refreshes
Example output
This is a substantial feature. Let me build the complete implementation package.
---
## 0. EXECUTIVE SUMMARY
**Scope:** A shopping cart system for a Next.js e-commerce app comprising four files: a cart state library with localStorage persistence, an API route handling discount validation and Stripe checkout session creation, a slide-out cart drawer component, and a dedicated checkout page supporting guest users. Real-time price recalculation occurs client-side; discount codes are validated server-side against MongoDB.
**Exclusions:**
- No user authentication or account management (guest-only by constraint)
- No inventory/stock checking (no product catalog service defined)
- No order history or post-purchase flows
- No Stripe live mode (test mode only per constraint)
- No webhook handling for post-payment events (e.g., fulfillment)
**Risks:**
| # | Risk | Mitigation |
|---|------|------------|
| 1 | Cart state in localStorage can desync with actual product prices | Prices are re-validated server-side at checkout creation; client prices are display-only |
| 2 | Discount codes could be brute-forced | Rate limiting on the API route via in-memory counter; codes stored hashed is a future improvement |
| 3 | Stripe session could expire before user completes payment | Checkout page detects expired sessions and prompts retry with fresh session |
**Tech Debt Warnings:**
- In-memory rate limiting resets on server restart; replace with Redis in production
- Product catalog is hardcoded as a type; needs a proper products collection
- No Stripe webhook to confirm payment β orders are marked pending until manually verified
**Assumptions:**
- VERIFY: MongoDB connection string is available as `MONGODB_URI` env var
- VERIFY: Stripe secret key is available as `STRIPE_SECRET_KEY` env var
- VERIFY: A `products` collection exists or product data is passed from the consuming page
- VERIFY: The app already has a `_app.tsx` wrapper where providers can be added
- INFERRED: Cart items include `productId`, `name`, `price`, `image`, and `quantity`
---
## 1. INPUT CONFIRMATION
| Element | Classification | Notes |
|---------|---------------|-------|
| Add/remove items | CONFIRMED | Explicit requirement |
| Real-time discount validation | CONFIRMED | Explicit requirement |
| Guest checkout | CONFIRMED | Explicit requirement |
| Payment error handling | CONFIRMED | Explicit requirement |
| localStorage persistence | CONFIRMED | Explicit constraint |
| Mobile viewport support | CONFIRMED | Explicit constraint |
| Stripe test mode only | CONFIRMED | Explicit constraint |
| No new dependencies | CONFIRMED | Explicit constraint |
| MongoDB for discount codes | INFERRED | Stack includes MongoDB; discount codes need persistent storage |
| Stripe Checkout Sessions | INFERRED | Standard Stripe integration pattern without new deps |
| Cart context provider | INFERRED | React Context is built-in; needed for cross-component cart access |
| Order document in MongoDB | INFERRED | Need to record checkout attempts |
**Acceptance Checks:**
| # | Check | Pass Criteria |
|---|-------|--------------|
| AC-1 | Add item to cart | Cart count increments, item appears in drawer, localStorage updated |
| AC-2 | Remove item from cart | Item removed, totals recalculated, localStorage updated |
| AC-3 | Update quantity | Quantity changes, subtotal recalculates instantly |
| AC-4 | Apply valid discount code | API returns discount details, total reduces in real time |
| AC-5 | Apply invalid discount code | API returns error, user sees inline error message |
| AC-6 | Guest checkout completes | Stripe session created, redirect to Stripe, return to success page |
| AC-7 | Payment error shown | User sees friendly error message, no internals exposed |
| AC-8 | Cart persists on refresh | Close and reopen browser tab; cart items remain |
| AC-9 | Mobile responsive | Cart drawer and checkout page usable at 375px width |
| AC-10 | Expired discount code rejected | API returns error for expired code |
---
## 2. ARCHITECTURE AND DECISIONS
### DECISION LOG
| # | Decision | Rationale | Rejected Alternatives |
|---|----------|-----------|----------------------|
| D-1 | React Context + useReducer for cart state | Built into React (no new deps), supports complex state transitions, works with SSR | Zustand (new dep), Redux (new dep), useState (too simple for cart logic) |
| D-2 | localStorage for persistence | Meets constraint, works for guest users, no server roundtrip | cookies (size limit), IndexedDB (overkill), server session (needs auth) |
| D-3 | Server-side price validation at checkout | Prevents price manipulation; client prices are for display only | Trust client prices (insecure), re-fetch all prices on every cart change (slow) |
| D-4 | Stripe Checkout Sessions (redirect) | No new frontend deps needed, PCI compliance handled by Stripe, mobile-friendly | Stripe Elements (needs @stripe/react-stripe-js β new dep), custom form (PCI burden) |
| D-5 | Discount codes in MongoDB `discounts` collection | Persistent, queryable, supports expiry dates and usage limits | Hardcoded codes (inflexible), env vars (not scalable) |
| D-6 | Single API route with method + action dispatch | Keeps to 4 target files, RESTful within Next.js pages router conventions | Separate routes per action (more files than specified) |
| D-7 | In-memory rate limiter for discount endpoint | No new deps; sufficient for moderate traffic | express-rate-limit (new dep), Redis (new infra) |
| D-8 | Tailwind for all styling | Specified in stack, utility-first works well for responsive design | CSS modules (not specified), styled-components (new dep) |
### Data Model
**MongoDB `discounts` collection:**
```javascript
// DDL equivalent β run in mongosh
db.createCollection("discounts", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["code", "type", "value", "active"],
properties: {
code: { bsonType: "string", description: "Unique uppercase code" },
type: { enum: ["percentage", "fixed"], description: "Discount type" },
value: { bsonType: "number", description: "Discount amount (percent or cents)" },
active: { bsonType: "bool" },
expiresAt: { bsonType: ["date", "null"] },
minCartCents: { bsonType: "number", description: "Minimum cart total in cents" },
maxUsages: { bsonType: ["number", "null"] },
usageCount: { bsonType: "number" }
}
}
}
});
db.discounts.createIndex({ code: 1 }, { unique: true });
// Seed test data
db.discounts.insertMany([
{ code: "SAVE10", type: "percentage", value: 10, active: true, expiresAt: new Date("2027-12-31"), minCartCents: 1000, maxUsages: null, usageCount: 0 },
{ code: "FLAT500", type: "fixed", value: 500, active: true, expiresAt: null, minCartCents: 2000, maxUsages: 100, usageCount: 12 }
]);
```
**MongoDB `orders` collection:**
```javascript
db.createCollection("orders");
db.orders.createIndex({ stripeSessionId: 1 }, { unique: true });
// Document shape:
// { email, items[], subtotalCents, discountCents, totalCents, discountCode, stripeSessionId, status, createdAt }
```
### API Contracts
**POST /api/cart** β Unified endpoint dispatched by `action` field.
*Action: `validate-discount`*
```
Request: { action: "validate-discount", code: "SAVE10", cartTotalCents: 5000 }
Success: { discount: { code, type, value, appliedAmountCents: 500 } }
Errors: 400 { error: { message: "Discount code is invalid" } }
400 { error: { message: "Cart total does not meet minimum" } }
429 { error: { message: "Too many requests. Try again shortly." } }
```
*Action: `create-checkout`*
```
Request: { action: "create-checkout", items: [{ productId, name, priceCents, quantity }], email: "a@b.com", discountCode?: "SAVE10" }
Success: { sessionUrl: "https://checkout.stripe.com/..." }
Errors: 400 { error: { message: "Cart is empty" } }
400 { error: { message: "Invalid email address" } }
502 { error: { message: "Payment service unavailable. Please try again." } }
```
*Any other method:*
```
405 { error: { message: "Method not allowed" } }
```
### Component Map
```
_app.tsx (existing)
ββ CartProvider (from /lib/cart.ts)
ββ CartDrawer.tsx β slide-out panel, item list, discount input, totals, checkout CTA
β ββ CartItem (internal) β single line item with quantity controls and remove
β ββ DiscountInput (internal) β code input with real-time validation feedback
ββ /pages/checkout.tsx β full-page checkout form
ββ GuestForm (internal) β email capture
ββ OrderSummary (internal) β final price breakdown
ββ CheckoutButton (internal) β triggers Stripe session creation
```
### State Machine
```
CART STATES:
empty β has_items (addItem)
has_items β empty (removeItem when last)
has_items β has_items (addItem | removeItem | updateQty)
has_items β discount_applied (applyDiscount success)
discount_applied β has_items (removeDiscount | cart change below minimum)
discount_applied β checkout_pending (createCheckout)
has_items β checkout_pending (createCheckout)
checkout_pending β checkout_error (Stripe/API error)
checkout_error β checkout_pending (retry)
checkout_pending β redirected (Stripe session URL received)
DISCOUNT VALIDATION:
idle β validating (user submits code)
validating β valid (API success)
validating β invalid (API error)
valid β idle (code removed)
invalid β validating (user retries)
```
### Dependency Graph
```
/lib/cart.ts
β uses: mongodb (driver), stripe (SDK) [both assumed already installed]
β env: MONGODB_URI, STRIPE_SECRET_KEY, NEXT_PUBLIC_BASE_URL
/pages/api/cart.ts
β imports: /lib/cart.ts (getMongoDb, validateDiscount, createCheckoutSession)
/components/CartDrawer.tsx
β imports: /lib/cart.ts (useCart, CartItem type)
β calls: /pages/api/cart (fetch POST validate-discount)
/pages/checkout.tsx
β imports: /lib/cart.ts (useCart)
β imports: /components/CartDrawer.tsx (optional cross-reference)
β calls: /pages/api/cart (fetch POST create-checkout)
```
---
## 3. IMPLEMENTATION ROADMAP
| Step | File | What | Depends On | Verification |
|------|------|------|------------|-------------|
| 1 | `/lib/cart.ts` | Define TypeScript types: CartItem, CartState, DiscountInfo, CartAction | β | Types compile without error |
| 2 | `/lib/cart.ts` | Implement cartReducer with all action handlers | Step 1 | Unit: each action produces correct state |
| 3 | `/lib/cart.ts` | Implement localStorage read/write with JSON safety | Step 1 | Cart survives page refresh |
| 4 | `/lib/cart.ts` | Build CartProvider and useCart hook with Context | Steps 2-3 | Provider renders children, hook returns state |
| 5 | `/lib/cart.ts` | Add price calculation helpers (subtotal, discount, total) | Step 1 | Math matches expected values |
| 6 | `/lib/cart.ts` | Add MongoDB connection helper (singleton) | β | Connection established in API test |
| 7 | `/lib/cart.ts` | Add validateDiscountCode server function | Step 6 | Valid code returns discount; invalid returns null |
| 8 | `/lib/cart.ts` | Add createStripeCheckoutSession server function | Step 6 | Returns session URL in test mode |
| 9 | `/pages/api/cart.ts` | Implement API route with method guard and action dispatch | Steps 7-8 | curl tests pass for all actions |
| 10 | `/pages/api/cart.ts` | Add input validation and rate limiting | Step 9 | Malformed requests return 400; flood returns 429 |
| 11 | `/components/CartDrawer.tsx` | Build drawer shell with open/close animation | Step 4 | Drawer slides in/out on trigger |
| 12 | `/components/CartDrawer.tsx` | Render cart items with quantity controls and remove | Step 4 | Items display, controls update state |
| 13 | `/components/CartDrawer.tsx` | Add discount code input with real-time validation | Steps 4, 9 | Valid code shows green, invalid shows red |
| 14 | `/components/CartDrawer.tsx` | Add price summary and checkout CTA | Step 5 | Totals match calculation, button navigates to checkout |
| 15 | `/pages/checkout.tsx` | Build checkout page layout with order summary | Step 4 | Page renders with cart contents |
| 16 | `/pages/checkout.tsx` | Add guest email form with validation | β | Invalid email shows error, valid enables submit |
| 17 | `/pages/checkout.tsx` | Integrate Stripe session creation and redirect | Steps 8-9 | Click creates session, redirects to Stripe |
| 18 | `/pages/checkout.tsx` | Handle success/cancel return states | Step 17 | URL params trigger appropriate UI |
| 19 | All | Mobile responsive pass | Steps 11-18 | All views usable at 375px |
| 20 | All | Error state review | All | Every failure matrix scenario has UI feedback |
**Validation Rules Summary:**
| Input | Type | Required | Allowed Values | Max Size |
|-------|------|----------|---------------|----------|
| discount code | string | no | A-Z0-9, uppercase | 20 chars |
| email | string | yes (checkout) | RFC 5322 basic | 254 chars |
| quantity | number | yes | integer 1-99 | β |
| productId | string | yes | non-empty string | 100 chars |
| priceCents | number | yes | positive integer | 99999999 |
| action | string | yes | "validate-discount" or "create-checkout" | β |
---
## 4. COMPLETE CODE
I'll now produce all four files:Here are all four files. Now completing the remaining specification sections:
---
## 5. FAILURE MATRIX
| Scenario | Severity | Detection Point | Response | User Message | Test Ref |
|----------|----------|-----------------|----------|-------------|----------|
| `MONGODB_URI` missing | Critical | `getMongoDb()` | 500 | "Something went wrong. Please try again." | TC-001 |
| MongoDB connection timeout | Critical | `getMongoDb()` connect | 500 | "Something went wrong. Please try again." | TC-002 |
| `STRIPE_SECRET_KEY` missing | Critical | `createCheckoutSession()` | 500 | "Something went wrong. Please try again." | TC-003 |
| Stripe API error (network) | High | Stripe SDK call | 502 | "Payment service unavailable. Please try again." | TC-004 |
| Stripe returns no session URL | High | `createCheckoutSession()` | 502 | "Payment service unavailable. Please try again." | TC-005 |
| Invalid discount code | Medium | MongoDB query | 400 | "Discount code is invalid" | TC-006 |
| Expired discount code | Medium | Expiry check | 400 | "Discount code has expired" | TC-007 |
| Cart below discount minimum | Medium | Min cart check | 400 | "Cart total must be at least $X.XX" | TC-008 |
| Empty cart at checkout | Medium | Validation | 400 | "Cart is empty" | TC-009 |
| Invalid email format | Medium | Regex validation | 400 | "Invalid email address" | TC-010 |
| Malformed JSON body | Medium | Body parse | 400 | "Request body is required" | TC-011 |
| Unknown action string | Medium | Switch dispatch | 400 | "Unknown action..." | TC-012 |
| Rate limit exceeded | Medium | `isRateLimited()` | 429 | "Too many requests. Try again shortly." | TC-013 |
| GET/PUT/DELETE request | Medium | Method guard | 405 | "Method not allowed" | TC-014 |
| Quantity out of range | Medium | Payload validation | 400 | "Invalid quantity" | TC-015 |
| localStorage corrupted | Low | `loadCartFromStorage()` | N/A (client) | Cart starts empty (graceful reset) | TC-016 |
---
## 6. VERIFICATION PACK
### Test Cases
| ID | Description | Input | Expected Result | Setup |
|----|------------|-------|-----------------|-------|
| TC-001 | Missing MONGODB_URI | POST validate-discount with no env | 500, generic error | Unset MONGODB_URI |
| TC-002 | MongoDB timeout | POST validate-discount, DB unreachable | 500, generic error | Set MONGODB_URI to bad host |
| TC-003 | Missing STRIPE_SECRET_KEY | POST create-checkout | 500, generic error | Unset STRIPE_SECRET_KEY |
| TC-004 | Stripe network failure | POST create-checkout with valid payload | 502, payment unavailable | Mock Stripe SDK to throw |
| TC-005 | Stripe returns null URL | POST create-checkout | 502, payment unavailable | Mock session.url = null |
| TC-006 | Invalid discount code | `{ action: "validate-discount", code: "FAKE", cartTotalCents: 5000 }` | 400, "invalid" | No matching code in DB |
| TC-007 | Expired discount code | `{ code: "EXPIRED10", cartTotalCents: 5000 }` | 400, "expired" | Insert code with past expiresAt |
| TC-008 | Below discount minimum | `{ code: "SAVE10", cartTotalCents: 100 }` | 400, "minimum" | Code has minCartCents: 1000 |
| TC-009 | Empty items array | `{ action: "create-checkout", items: [], email: "a@b.com" }` | 400, "empty" | β |
| TC-010 | Bad email | `{ action: "create-checkout", items: [...], email: "notanemail" }` | 400, "invalid email" | β |
| TC-011 | No body | POST with empty body | 400, "body required" | β |
| TC-012 | Unknown action | `{ action: "refund" }` | 400, "unknown action" | β |
| TC-013 | Rate limit | 21 rapid POSTs | 429 on 21st | β |
| TC-014 | Wrong method | GET /api/cart | 405 | β |
| TC-015 | Quantity = 0 | `items: [{ quantity: 0, ... }]` | 400, "invalid quantity" | β |
| TC-016 | Corrupted localStorage | Set `shop_cart_state` to `"{broken"` then mount | Cart renders empty, no crash | Browser dev tools |
| TC-HP-01 | Happy path: add item | Dispatch ADD_ITEM | Item appears, count = 1, drawer opens | Mount CartProvider |
| TC-HP-02 | Happy path: valid discount | POST with code SAVE10, cart 5000 | 200, discount object returned | Seed SAVE10 in DB |
| TC-HP-03 | Happy path: full checkout | POST create-checkout with items + email | 200, sessionUrl returned | Stripe test mode |
| TC-HP-04 | Happy path: quantity update | Dispatch UPDATE_QUANTITY qty=3 | Item quantity changes, total recalcs | Item in cart |
| TC-HP-05 | Happy path: persistence | Add item, refresh page | Item still in cart | Browser |
### Smoke Checklist (pre-deployment)
1. App starts without errors with all env vars set
2. Cart drawer opens/closes and renders items
3. Add, remove, and quantity change all update totals correctly
4. Valid discount code applies and shows green confirmation
5. Invalid discount code shows red inline error
6. Checkout with guest email redirects to Stripe test page
7. Success URL shows confirmation screen and clears cart
8. All views render correctly at 375px viewport width
### Regression Checklist (post-deployment)
1. Cart state survives full page refresh in production build
2. Discount code validation returns correct errors for expired/invalid codes
3. Rate limiter blocks rapid requests (verify with curl loop)
4. Stripe checkout sessions create successfully in test mode
5. Cancel URL returns to checkout with cart intact
6. Empty cart state shows appropriate messaging on both drawer and checkout page
7. No console errors on any page at any viewport
8. API returns 405 for non-POST methods
---
## 7. DEPLOYMENT PLAYBOOK
**Pre-deployment:**
- Run MongoDB seed script for `discounts` collection (DDL from Section 2)
- Create indexes: `discounts.code` (unique), `orders.stripeSessionId` (unique)
- VERIFY: Set env vars: `MONGODB_URI`, `STRIPE_SECRET_KEY`, `NEXT_PUBLIC_BASE_URL`
- VERIFY: Stripe account is in test mode with test key prefix `sk_test_`
- Wrap `_app.tsx` with `CartProvider` from `/lib/cart`
**Deploy sequence:**
1. Merge code to main branch
2. Run `npm run build` β ensure zero TypeScript errors
3. Deploy via your hosting provider (Vercel, etc.)
4. Verify `NEXT_PUBLIC_BASE_URL` matches the production domain
**Post-deployment monitoring:**
- Track API route response times (target: p95 < 500ms for discount validation, < 2s for checkout creation)
- Monitor Stripe Dashboard for failed session creations
- Alert if `/api/cart` error rate exceeds 5% over any 5-minute window
- Monitor MongoDB connection pool for exhaustion
**Rollback procedure:**
1. Revert to previous deployment via hosting provider rollback
2. If DB schema changed: no destructive migrations in this release, so rollback is safe
3. Existing orders in `orders` collection are append-only and do not need cleanup
4. Verify cart localStorage format hasn't changed (it hasn't β backward compatible)
---
## 8. QUICK START
```bash
# 1. Set environment variables
cp .env.example .env.local # then fill in values
# 2. Seed MongoDB (run once)
mongosh "$MONGODB_URI" < seed-discounts.js
# 3. Wrap your _app.tsx with CartProvider
# import { CartProvider } from '../lib/cart'
# export default function App({ Component, pageProps }) {
# return <CartProvider><Component {...pageProps} /></CartProvider>
# }
# 4. Run dev server
npm run dev
# 5. Test: open http://localhost:3000, add items, apply code SAVE10, checkout
```
**.env.example:**
```
MONGODB_URI=mongodb://localhost:27017/shop
STRIPE_SECRET_KEY=sk_test_your_key_here
NEXT_PUBLIC_BASE_URL=http://localhost:3000
```
By purchasing this prompt, you agree to our terms of service
CLAUDE-4-6-OPUS
π What if every feature you build came with production-ready code, a tested architecture, and a deployment plan β before you write a single line?
βͺοΈ Go from idea to implementation in minutes
βͺοΈ Catch edge cases and failures before they reach production
βͺοΈ Ship with confidence using built-in rollback procedures
βͺοΈ Spend less time planning, more time building
Built for developers who value speed without cutting corners.
π Step-by-step instructions included. Start shipping smarter today.
...more
Added 3 weeks ago
