Prompt-to-PR: Añadir una Página de Precios
SOP para generar una página de precios de producción con alternancia mensual/anual, matriz de funciones y enlaces a Stripe Checkout en un proyecto Next.js Tailwind.
CursorClaude CodeCodexWindsurf Next.jsTypeScriptTailwind
Una de las páginas más generadas en sesiones de coding con IA. Este manual restringe al agente para que produzca enlaces reales a Stripe Checkout en lugar de botones de marcador de posición.
1. Requisito
Añadir una página /pricing con dos o tres niveles, un conmutador de facturación mensual/anual (anual ofrece un 20% de descuento), una matriz de comparación de funciones e integración con Stripe Checkout. Los precios deben provenir de variables de entorno, no estar codificados en JSX.
2. Primer Prompt
Add a /pricing page to this Next.js 15 App Router project with Tailwind.
Requirements:1. Create `src/app/pricing/page.tsx` — a Server Component that renders `<PricingCards>`.2. Create `src/components/PricingCards.tsx` — a Client Component with: - Monthly/annual toggle (useState). Annual prices are monthly × 0.8. - Three tiers: Hobby (free), Pro, Business. Read plan data from `src/config/pricing.ts` — not hardcoded in JSX. - A feature matrix table below the cards showing which features each tier includes. Use checkmarks and dashes; do not use emojis in code. - A "Get started" CTA for Hobby (links to /sign-up) and "Subscribe" for paid tiers.3. Create `src/config/pricing.ts` exporting a `PLANS` array. Each plan has: { id, name, monthlyPriceUsd, stripePriceIdMonthly, stripePriceIdAnnually, features: string[], highlighted: boolean } Read Stripe price IDs from env vars: NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY NEXT_PUBLIC_STRIPE_PRICE_PRO_ANNUALLY NEXT_PUBLIC_STRIPE_PRICE_BIZ_MONTHLY NEXT_PUBLIC_STRIPE_PRICE_BIZ_ANNUALLY4. Create `src/app/api/checkout/route.ts` (POST). Accept `{ priceId, userId }`. Create a Stripe Checkout Session (mode: "subscription") with: - success_url: NEXT_PUBLIC_APP_URL + /dashboard?checkout=success - cancel_url: NEXT_PUBLIC_APP_URL + /pricing Return `{ url }`.5. In PricingCards, the Subscribe button POSTs to /api/checkout and redirects to the returned url.6. Install `stripe` package. Use env var STRIPE_SECRET_KEY.3. Cambios de Archivos Esperados
package.json (stripe)src/app/pricing/page.tsx (new — Server Component shell)src/components/PricingCards.tsx (new — Client Component with toggle)src/config/pricing.ts (new — plan definitions)src/app/api/checkout/route.ts (new — Stripe Checkout Session).env.local.example (STRIPE_* and NEXT_PUBLIC_* vars)4. Lista de Verificación de Revisión
STRIPE_SECRET_KEYnunca se importa en un Componente Cliente ni se expone medianteNEXT_PUBLIC_.- La Sesión de Checkout usa
mode: "subscription"nomode: "payment"para facturación recurrente. - Los IDs de precio anual son objetos de Precio de Stripe separados, no un descuento calculado en código.
- El conmutador muestra claramente el ahorro anual (por ejemplo, “Ahorra 20%”).
- El array
PLANSes la única fuente de verdad: la matriz de funciones reutiliza los mismos datos, no una tabla codificada aparte. - Manejo de errores en el POST de checkout: devuelve un 500 con un mensaje si Stripe lanza una excepción.
- No queda
console.logde claves de Stripe o datos de clientes en el código.
5. Comandos de Prueba
bun dev
# Toggle monthly/annual — confirm prices update correctly# Confirm annual = monthly * 0.8
# Test checkout endpoint with a test price IDcurl -X POST http://localhost:3000/api/checkout \ -H "Content-Type: application/json" \ -d '{"priceId":"price_test_xxx","userId":"user_1"}' | jq .url# Expect a Stripe Checkout URL
bun tsc --noEmit6. Fallos Comunes
STRIPE_SECRET_KEYfiltrada en el lado del cliente — el agente importastripeenPricingCards.tsx. Mover todas las llamadas a Stripe a la ruta de API.- El conmutador anual muestra el mismo precio que el mensual — el agente calcula
price * 0.8pero olvida usar el estadoisAnnual. Confirmar que el estado del conmutador se pasa a la visualización del precio. - Checkout devuelve 500 con “No such price” — la variable de entorno del ID de precio no está configurada o está en el entorno incorrecto (prueba vs producción). Verificar con
stripe prices retrieve <id>en la CLI de Stripe. success_urles relativa — Stripe requiere una URL absoluta. Prefijar conNEXT_PUBLIC_APP_URL.
7. Prompt de Corrección
The annual/monthly toggle renders correctly visually but the Subscribe buttonalways sends the monthly priceId regardless of which toggle state is active.
In PricingCards.tsx, the `priceId` passed to the checkout POST must be`isAnnual ? plan.stripePriceIdAnnually : plan.stripePriceIdMonthly`.Check that the isAnnual state variable is read at the point the button'sonClick handler calls the checkout API.8. Descripción del PR
## Feature: Pricing page with Stripe Checkout
- `/pricing` page with monthly/annual toggle and three tiers (Hobby, Pro, Business)- Feature matrix driven from `src/config/pricing.ts` — one source of truth- POST `/api/checkout` creates a Stripe Subscription Checkout Session- Price IDs read from env vars — no hardcoded Stripe IDs in code- Annual plan = 20% discount via separate Stripe Price objects
**Required env vars**: `STRIPE_SECRET_KEY`, `NEXT_PUBLIC_STRIPE_PRICE_*`,`NEXT_PUBLIC_APP_URL` (see `.env.local.example`)