Prompt-to-PR: Ajouter une page de tarification
SOP pour générer une page de tarification de production avec bascule mensuelle/annuelle, matrice de fonctionnalités et liens Stripe Checkout dans un projet Next.js Tailwind.
CursorClaude CodeCodexWindsurf Next.jsTypeScriptTailwind
L’une des pages les plus générées dans les sessions de codage IA. Ce playbook contraint l’agent à produire de véritables liens Stripe Checkout câblés plutôt que des boutons factices.
1. Exigence
Ajouter une page /pricing avec deux ou trois niveaux, une bascule de facturation mensuelle/annuelle (annuelle donne une réduction de 20 %), une matrice de comparaison des fonctionnalités et une intégration Stripe Checkout. Les prix doivent provenir de variables d’environnement, ne pas être codés en dur dans JSX.
2. Première invite
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. Modifications de fichiers attendues
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. Liste de vérification
STRIPE_SECRET_KEYn’est jamais importé dans un composant client ni exposé viaNEXT_PUBLIC_.- La session Checkout utilise
mode: "subscription"et nonmode: "payment"pour la facturation récurrente. - Les IDs de prix annuels sont des objets Stripe Price séparés — pas une remise calculée dans le code.
- La bascule montre clairement les économies annuelles (par exemple « Économisez 20 % »).
- Le tableau
PLANSest la source unique de vérité — la matrice de fonctionnalités réutilise les mêmes données, pas un tableau codé en dur séparé. - Gestion des erreurs sur le POST checkout : renvoie une erreur 500 avec un message si Stripe échoue.
- Aucun
console.logdes clés Stripe ou des données client laissé dans le code.
5. Commandes de test
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. Échecs courants
STRIPE_SECRET_KEYdivulgué côté client — l’agent importestripedansPricingCards.tsx. Déplacez tous les appels Stripe vers la route API.- La bascule annuelle affiche le même prix que mensuel — l’agent calcule
price * 0.8mais oublie d’utiliser l’étatisAnnual. Confirmez que l’état de la bascule est passé à l’affichage du prix. - Checkout renvoie 500 avec « No such price » — la variable d’environnement de l’ID de prix n’est pas définie ou le mauvais environnement (test vs live). Vérifiez avec
stripe prices retrieve <id>dans la Stripe CLI. success_urlest relative — Stripe nécessite une URL absolue. Préfixez avecNEXT_PUBLIC_APP_URL.
7. Invite de correction
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. Description de la 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`)