Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
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>
);
}// 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>
);
}stripe es 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-js proporciona la función loadStripe que 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-js proporciona componentes de React (Elements, PaymentElement, CardElement) y hooks (useStripe, useElements) que envuelven Stripe.js.Elements debe envolver cualquier componente que use hooks o elements de Stripe. Inicializa Stripe.js y lo pone a disposición mediante React Context.loadStripe devuelve una Promise que se resuelve una vez que el script carga. Pasar esta Promise directamente a Elements es el patrón recomendado: gestiona la carga asíncrona internamente.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>Stripe del paquete stripe está tipado con la versión de la API. Pasa siempre un apiVersion explícito para evitar desajustes de tipos.loadStripe devuelve Promise<Stripe | null>. El componente Elements gestiona internamente el caso null.Stripe como tipo desde stripe para el tipado del lado del servidor, y Stripe desde @stripe/stripe-js para 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";stripe (SDK del servidor) en componentes cliente. Expone tu clave secreta y fallará en tiempo de compilación.NEXT_PUBLIC_ para estar disponible en código del lado del cliente en Next.js.loadStripe debe llamarse fuera de los componentes (a nivel de módulo) para evitar recrear la instancia de Stripe en cada renderizado.apiVersion que 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ú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.
| 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 |
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)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.
stripe depende de APIs de Node.js no disponibles en el navegadorLlamarlo 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.
useStripe, useElements o componentes Element de Stripe debe ser descendiente de <Elements>loadStripe y gestiona la carga asíncrona internamenteEnvuelve 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.
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.
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<Elements
stripe={stripePromise}
options={{ clientSecret: "pi_xxx_secret_yyy" }}
>
{children}
</Elements>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.
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.
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.
Revisado por Chris St. John·Última actualización: 7 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥