Primeros pasos con Stripe
Receta
Instala los tres paquetes de Stripe, configura variables de entorno, crea instancias de Stripe en servidor y cliente, y envuelve tu app con el proveedor Elements.
npm install stripe @stripe/stripe-js @stripe/react-stripe-jsConfigura las variables de entorno en .env.local:
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...Crea la instancia de Stripe del lado del servidor:
// lib/stripe.ts
import Stripe from "stripe";
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-12-18.acacia",
typescript: true,
});Crea el cargador de Stripe del lado del cliente:
// lib/stripe-client.ts
import { loadStripe } from "@stripe/stripe-js";
export const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);Envuelve tu app con el proveedor Elements:
// app/providers.tsx
"use client";
import { Elements } from "@stripe/react-stripe-js";
import { stripePromise } from "@/lib/stripe-client";
export function StripeProvider({ children }: { children: React.ReactNode }) {
return <Elements stripe={stripePromise}>{children}</Elements>;
}// app/layout.tsx
import { StripeProvider } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<StripeProvider>{children}</StripeProvider>
</body>
</html>
);
}Ejemplo funcional
// app/checkout/page.tsx
"use client";
import { useStripe, useElements } from "@stripe/react-stripe-js";
export default function CheckoutPage() {
const stripe = useStripe();
const elements = useElements();
if (!stripe || !elements) {
return <div>Cargando Stripe...</div>;
}
return (
<div>
<h1>Checkout</h1>
<p>Stripe se cargó correctamente. Listo para aceptar pagos.</p>
<p>
Usa la tarjeta de prueba: <code>4242 4242 4242 4242</code> con cualquier
fecha de vencimiento futura y cualquier CVC.
</p>
</div>
);
}Profundización
Cómo funciona
- El paquete npm
stripees el SDK de Node.js del lado del servidor. Solo debe importarse en código de servidor (Server Actions, Route Handlers, server components). @stripe/stripe-jsproporciona la funciónloadStripeque carga de forma asíncrona el script de Stripe.js desde la CDN de Stripe. Esto simplifica el cumplimiento PCI porque los datos de la tarjeta nunca pasan por tu servidor.@stripe/react-stripe-jsproporciona componentes de React (Elements,PaymentElement,CardElement) y hooks (useStripe,useElements) que envuelven Stripe.js.- El proveedor
Elementsdebe envolver cualquier componente que use hooks o elements de Stripe. Inicializa Stripe.js y lo pone a disposición mediante React Context. loadStripedevuelve una Promise que se resuelve una vez que el script carga. Pasar esta Promise directamente aElementses el patrón recomendado: gestiona la carga asíncrona internamente.
Variaciones
Carga diferida de Elements solo en páginas de checkout:
// app/checkout/layout.tsx
"use client";
import { Elements } from "@stripe/react-stripe-js";
import { stripePromise } from "@/lib/stripe-client";
export default function CheckoutLayout({
children,
}: {
children: React.ReactNode;
}) {
return <Elements stripe={stripePromise}>{children}</Elements>;
}Pasa un client secret a Elements para flujos de PaymentIntent:
<Elements
stripe={stripePromise}
options={{ clientSecret: "pi_xxx_secret_yyy" }}
>
{children}
</Elements>Notas de TypeScript
- El constructor
Stripedel paquetestripeestá tipado con la versión de la API. Pasa siempre unapiVersionexplícito para evitar desajustes de tipos. loadStripedevuelvePromise<Stripe | null>. El componenteElementsgestiona internamente el casonull.- Importa
Stripecomo tipo desdestripepara el tipado del lado del servidor, yStripedesde@stripe/stripe-jspara el tipado del lado del cliente. Son tipos distintos.
// Tipo del lado del servidor
import type Stripe from "stripe";
// Tipo del lado del cliente
import type { Stripe as StripeJS } from "@stripe/stripe-js";Errores comunes
- Nunca importes
stripe(SDK del servidor) en componentes cliente. Expone tu clave secreta y fallará en tiempo de compilación. - La clave publicable debe llevar el prefijo
NEXT_PUBLIC_para estar disponible en código del lado del cliente en Next.js. loadStripedebe llamarse fuera de los componentes (a nivel de módulo) para evitar recrear la instancia de Stripe en cada renderizado.- En modo de prueba, todas las llamadas a la API usan datos de prueba. No se crean cargos reales. Cambia a claves en vivo solo después de probar a fondo.
- El
apiVersionque pasas al SDK del servidor debe coincidir con la versión configurada en tu Stripe Dashboard. Fijarlo evita sorpresas cuando Stripe publica nuevas versiones de la API.
Números de tarjeta de prueba
| Número de tarjeta | Escenario |
|---|---|
| 4242 4242 4242 4242 | Pago exitoso |
| 4000 0000 0000 3220 | Autenticación 3D Secure requerida |
| 4000 0000 0000 9995 | Rechazado (fondos insuficientes) |
| 4000 0000 0000 0002 | Rechazo genérico |
| 4000 0025 0000 3155 | Requiere autenticación |
Usa cualquier fecha de vencimiento futura y cualquier CVC de 3 dígitos para todas las tarjetas de prueba.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Proveedor Elements global | Configuración simple, funciona en todas partes | Carga Stripe.js en cada página |
| Envoltorio Elements por página | Solo carga Stripe en páginas de checkout | Requiere envolver cada página de pago |
| Importación dinámica de Elements | Bundle inicial más pequeño | Configuración más compleja, ligero retraso en checkout |
FAQs
¿Cuáles son los tres paquetes npm necesarios para Stripe en una app de Next.js y qué hace cada uno?
stripe-- SDK de Node.js del lado del servidor para llamar a la API de Stripe@stripe/stripe-js-- cargador del lado del cliente (loadStripe) que obtiene Stripe.js desde la CDN@stripe/react-stripe-js-- componentes de React (Elements,PaymentElement) y hooks (useStripe,useElements)
¿Por qué la clave publicable debe usar el prefijo NEXT_PUBLIC_?
Next.js solo expone variables de entorno al código del lado del cliente si empiezan con NEXT_PUBLIC_. Sin el prefijo, la variable es undefined en el navegador y loadStripe fallará en silencio.
¿Qué ocurre si importas el paquete stripe del lado del servidor en un componente cliente?
- Expone tu clave secreta al bundle del navegador
- La compilación fallará porque el paquete
stripedepende de APIs de Node.js no disponibles en el navegador - Mantén siempre las importaciones del SDK del servidor solo en Server Actions, Route Handlers o server components
¿Por qué loadStripe debe llamarse a nivel de módulo en lugar de dentro de un componente?
Llamarlo dentro del cuerpo de un componente volvería a ejecutarse en cada renderizado, creando una nueva instancia de Stripe cada vez. Llamarlo a nivel de módulo garantiza una única Promise compartida que se resuelve una sola vez.
¿Cuál es el propósito del proveedor Elements?
- Inicializa Stripe.js y pone la instancia de Stripe a disposición mediante React Context
- Cualquier componente que use
useStripe,useElementso componentes Element de Stripe debe ser descendiente de<Elements> - Acepta directamente la Promise de
loadStripey gestiona la carga asíncrona internamente
¿Cómo cargar Stripe de forma diferida solo en páginas de checkout en lugar de globalmente?
Envuelve solo el layout.tsx de la ruta de checkout con el proveedor <Elements> en lugar del layout raíz. Así Stripe.js no se carga en páginas que no lo necesitan.
¿Qué hace la opción apiVersion al crear la instancia de Stripe del lado del servidor?
Fija la versión de la API de Stripe que tu código espera. Sin ella, Stripe usa la versión predeterminada de tu Dashboard, que puede cambiar y provocar comportamientos incompatibles de forma inesperada.
Trampa: ¿Cuál es la diferencia entre el tipo Stripe de stripe y el tipo Stripe de @stripe/stripe-js?
Son tipos completamente distintos. El Stripe del lado del servidor (de stripe) tipa el SDK de Node. El Stripe del lado del cliente (de @stripe/stripe-js) tipa la instancia de Stripe.js en el navegador. Impórtalos por separado usando import type.
import type Stripe from "stripe"; // servidor
import type { Stripe as StripeJS } from "@stripe/stripe-js"; // cliente¿Cómo pasar un client secret a Elements para un flujo de PaymentIntent?
<Elements
stripe={stripePromise}
options={{ clientSecret: "pi_xxx_secret_yyy" }}
>
{children}
</Elements>¿Qué devuelve loadStripe y cómo gestiona Elements el caso null?
loadStripe devuelve Promise<Stripe | null>. Se resuelve a null si el script no carga. El componente <Elements> lo gestiona internamente: hooks como useStripe devolverán null hasta que la carga tenga éxito.
TypeScript: ¿Cómo tipar las props del componente StripeProvider?
function StripeProvider({ children }: { children: React.ReactNode }) {
return <Elements stripe={stripePromise}>{children}</Elements>;
}children está tipado como React.ReactNode. La prop stripe de <Elements> acepta Promise<Stripe | null> | Stripe | null.
Trampa: ¿Puedes usar claves de modo de prueba y modo en vivo en el mismo entorno?
No. Las claves de prueba (sk_test_, pk_test_) y las claves en vivo (sk_live_, pk_live_) operan en entornos completamente separados. Mezclarlas (p. ej., el servidor usa en vivo y el cliente usa prueba) provocará errores de API. Usa siempre pares coincidentes.