P PasteCode
Guía de trabajo

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
.md .json Dificultad: Fácil Actualizado 8 jun 2026

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

First 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_ANNUALLY
4. 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_KEY nunca se importa en un Componente Cliente ni se expone mediante NEXT_PUBLIC_.
  • La Sesión de Checkout usa mode: "subscription" no mode: "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 PLANS es 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.log de claves de Stripe o datos de clientes en el código.

5. Comandos de Prueba

3000/pricing
bun dev
# Toggle monthly/annual — confirm prices update correctly
# Confirm annual = monthly * 0.8
# Test checkout endpoint with a test price ID
curl -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 --noEmit

6. Fallos Comunes

  • STRIPE_SECRET_KEY filtrada en el lado del cliente — el agente importa stripe en PricingCards.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.8 pero olvida usar el estado isAnnual. 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_url es relativa — Stripe requiere una URL absoluta. Prefijar con NEXT_PUBLIC_APP_URL.

7. Prompt de Corrección

Fix Prompt
The annual/monthly toggle renders correctly visually but the Subscribe button
always 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's
onClick handler calls the checkout API.

8. Descripción del PR

PR description
## 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`)