Stripe Pagos Únicos — Configuración
Módulo autocontenido para pagos únicos con Stripe en un proyecto de Next.js App Router. Sin suscripciones ni facturación: solo crear un pago, seguir su estado y reembolsarlo si hace falta.
Dependencias
Este módulo usa:
stripe— SDK del lado del servidor, usado enlib/stripe/service.ts@stripe/stripe-js— SDK del lado del cliente, usado enlib/stripe/client.ts
No se instalan automáticamente: agregalos con tu gestor de paquetes preferido antes de usar el módulo.
Mapa de archivos
lib/stripe/
├── service.ts # Internal logic + Stripe SDK instance. Don't touch/import directly elsewhere.
├── actions.ts # Server Actions — import into Server Components.
├── types.ts # Shared types — safe to import anywhere.
└── client.ts # Client-side helpers — import into Client Components.
app/api/webhooks/stripe/
└── route.ts # Webhook endpoint. Register this URL in your Stripe Dashboard.
.env.local # Stripe keys (see below).
1. Variables de entorno
Completá .env.local:
| Variable | Dónde conseguirla |
|---|---|
STRIPE_SECRET_KEY | Dashboard → Developers → API keys |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | Dashboard → Developers → API keys |
STRIPE_WEBHOOK_SECRET | Dashboard → Developers → Webhooks (o la salida de stripe listen en local) |
2. Webhooks
En el Stripe Dashboard, agregá un endpoint que apunte a:
https://yourdomain.com/api/webhooks/stripe
Suscribilo como mínimo a:
payment_intent.succeededpayment_intent.payment_failedpayment_intent.canceledcharge.refundedcheckout.session.completed
Para probar en local, usá el Stripe CLI:
stripe listen --forward-to localhost:3000/api/webhooks/stripe
La lógica de negocio de lo que pasa con cada evento (cumplir un pedido, enviar un
email, etc.) va dentro de stripeService.handleWebhookEvent en service.ts:
es la única parte de service.ts pensada para extenderse a medida que tu app crece.
3. Uso
Checkout alojado (lo más simple: redirigir a una página alojada por Stripe)
Del lado del servidor, en un Server Component o en una form action:
import { createCheckoutSession } from "@/lib/stripe/actions";
const result = await createCheckoutSession({
amount: 2000, // $20.00, in cents
currency: "usd",
productName: "Pro plan — one time",
successUrl: "https://yourdomain.com/success",
cancelUrl: "https://yourdomain.com/cancel",
});
if (result.success) {
// result.data.url — redirect the user here, or:
// result.data.sessionId — pass to redirectToCheckout() client-side
}
Del lado del cliente, si preferís redirigir desde el navegador con el id de la sesión:
"use client";
import { redirectToCheckout } from "@/lib/stripe/client";
await redirectToCheckout(sessionId);
Formulario de pago personalizado (embebido, con Stripe Elements)
Del lado del servidor:
import { createPaymentIntent } from "@/lib/stripe/actions";
const result = await createPaymentIntent({ amount: 2000, currency: "usd" });
// result.data.clientSecret — pass to your Elements form
Del lado del cliente, después de recolectar los datos de la tarjeta con Stripe Elements:
"use client";
import { confirmCardPayment } from "@/lib/stripe/client";
await confirmCardPayment(clientSecret, paymentMethodId);
Consultar el estado
import { getPayment, getPaymentStatus } from "@/lib/stripe/actions";
const status = await getPaymentStatus(paymentIntentId);
const full = await getPayment(paymentIntentId);
Cancelar / reembolsar
import { cancelPayment, refundPayment } from "@/lib/stripe/actions";
await cancelPayment(paymentIntentId);
await refundPayment({ paymentIntentId }); // full refund
await refundPayment({ paymentIntentId, amount: 500 }); // partial, $5.00
Métodos de pago
import { listPaymentMethods } from "@/lib/stripe/actions";
const methods = await listPaymentMethods(stripeCustomerId);
Manejo de errores
Cada función de actions.ts y client.ts devuelve:
{ success: true, data: T } | { success: false, error: string }
Verificá siempre result.success antes de leer result.data: nada lanza errores
a través del límite del módulo, así que nunca necesitás un try/catch alrededor de estas llamadas.
Notas
service.tses el único archivo que importa el paquetestripeo leeSTRIPE_SECRET_KEY/STRIPE_WEBHOOK_SECRET. Mantenelo así: es lo que hace imposible filtrar la secret key a un Client Component por accidente.- Las páginas de éxito/cancelación (
successUrl/cancelUrl) no están incluidas acá: este módulo solo produce los datos y las URLs; apuntalas a tus páginas de éxito/cancelación de checkout existentes.