Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Usa PaymentElement para un input de pago unificado que admite tarjetas, billeteras y transferencias bancarias. Usa CardElement cuando solo necesitas pagos con tarjeta y quieres un control más granular a nivel de campo. Estila ambos con la Appearance API de Stripe.
PaymentElement (recomendado en la mayoría de los casos):
// app/checkout/payment-form.tsx
"use client";
import { PaymentElement, useStripe, useElements } from "@stripe/react-stripe-js";
import { useState, type FormEvent } from "react";
export function PaymentForm() {
const stripe = useStripe();
const elements = useElements();
const [error, setError] = useState<string | null>(null);
const [processing, setProcessing] = useState(false);
async function handleSubmit(e: FormEvent) {
e.preventDefault();
if (!stripe || !elements) return;
setProcessing(true);
const { error: submitError } = await elements.submit();
if (submitError) {
setError(submitError.message ?? "Error de validación");
setProcessing(false);
return;
}
const { error: confirmError } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: `${window.location.origin}/success` },
});
if (confirmError) {
setError(confirmError.message ?? "Pago fallido");
setProcessing(false);
}
}
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
{error && <p className="text-red-500 mt-2">{error}</p>}
<button type="submit" disabled={!stripe || processing}>
{processing ? "Procesando..." : "Pagar"}
</button>
</form>
);
}CardElement (solo tarjeta):
"use client";
import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js";
import { useState, type FormEvent } from "react";
export function CardForm() {
const stripe = useStripe();
const elements = useElements();
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: FormEvent) {
e.preventDefault();
if (!stripe || !elements) return;
const cardElement = elements.getElement(CardElement);
if (!cardElement) return;
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: "card",
card: cardElement,
});
if (error) {
setError(error.message ?? "Error");
} else {
// Envía paymentMethod.id a tu servidor
console.log("PaymentMethod:", paymentMethod.id);
}
}
return (
<form onSubmit={handleSubmit}>
<CardElement options={{ style: { base: { fontSize: "16px" } } }} />
{error && <p className="text-red-500 mt-2">{error}</p>}
<button type="submit" disabled={!stripe}>Pagar</button>
</form>
);
}Un formulario de pago estilizado que coincide con una app de tema oscuro:
// app/checkout/styled-checkout.tsx
"use client";
import { useEffect, useState } from "react";
import { Elements } from "@stripe/react-stripe-js";
import type { Appearance } from "@stripe/stripe-js";
import { stripePromise } from "@/lib/stripe-client";
import { createPaymentIntent } from "@/app/actions/payment";
import { PaymentForm } from "./payment-form";
const appearance: Appearance = {
theme: "night",
variables: {
colorPrimary: "#6366f1",
colorBackground: "#1e1e2e",
colorText: "#e2e8f0",
colorDanger: "#ef4444",
fontFamily: "Inter, system-ui, sans-serif",
borderRadius: "8px",
spacingUnit: "4px",
},
rules: {
".Input": {
border: "1px solid #334155",
boxShadow: "none",
padding: "12px",
},
".Input:focus": {
border: "1px solid #6366f1",
boxShadow: "0 0 0 1px #6366f1",
},
".Label": {
fontWeight: "500",
fontSize: "14px",
},
".Tab": {
border: "1px solid #334155",
borderRadius: "8px",
},
".Tab--selected": {
backgroundColor: "#6366f1",
borderColor: "#6366f1",
},
},
};
export default function StyledCheckout() {
const [clientSecret, setClientSecret] = useState<string | null>(null);
useEffect(() => {
createPaymentIntent(4999).then(({ clientSecret }) => {
setClientSecret(clientSecret);
});
}, []);
if (!clientSecret) return <div className="text-white">Cargando...</div>;
return (
<div className="max-w-lg mx-auto p-8 bg-[#1e1e2e] rounded-xl">
<h2 className="text-xl font-bold text-white mb-6">Completar pago</h2>
<Elements
stripe={stripePromise}
options={{
clientSecret,
appearance,
layout: {
type: "tabs",
defaultCollapsed: false,
},
}}
>
<PaymentForm />
</Elements>
</div>
);
}PaymentElement es un único componente que renderiza dinámicamente campos de entrada para todos los métodos de pago habilitados (tarjetas, Apple Pay, Google Pay, débitos bancarios, etc.). Stripe determina qué métodos mostrar según la moneda de la transacción, el importe y la ubicación del cliente.CardElement renderiza un input de tarjeta de una sola línea con los campos de número, vencimiento y CVC combinados. Solo admite pagos con tarjeta.theme, variables y rules. Los temas (stripe, night, flat) proporcionan una base; las variables sobrescriben tokens de diseño; las reglas apuntan a selectores similares a CSS para elementos individuales."tabs" los muestra como pestañas seleccionables, "accordion" como secciones expandibles y "auto" deja que Stripe decida.Layout de acordeón:
<Elements
stripe={stripePromise}
options={{
clientSecret,
layout: { type: "accordion", defaultCollapsed: false, radios: true },
}}
>
<PaymentForm />
</Elements>Controlar la recopilación de datos de facturación:
<PaymentElement
options={{
layout: "tabs",
fields: {
billingDetails: {
name: "auto",
email: "never", // recopila el email tú mismo
phone: "never",
address: "auto",
},
},
}}
/>Campos de tarjeta separados (CardNumber, CardExpiry, CardCvc):
import {
CardNumberElement,
CardExpiryElement,
CardCvcElement,
} from "@stripe/react-stripe-js";
function SplitCardForm() {
return (
<div className="space-y-4">
<div>
<label>Número de tarjeta</label>
<CardNumberElement />
</div>
<div className="flex gap-4">
<div className="flex-1">
<label>Vencimiento</label>
<CardExpiryElement />
</div>
<div className="flex-1">
<label>CVC</label>
<CardCvcElement />
</div>
</div>
</div>
);
}Appearance desde @stripe/stripe-js para tipar tu configuración de apariencia.PaymentElement acepta PaymentElementProps, que incluye una prop options tipada como PaymentElementOptions.CardElement usan CardElementOptions con una API de estilo diferente (style.base, style.invalid, etc.) que la Appearance API.import type { Appearance, LayoutObject } from "@stripe/stripe-js";
const layout: LayoutObject = {
type: "tabs",
defaultCollapsed: false,
};PaymentElement requiere un clientSecret en el proveedor Elements. No puedes usarlo sin un PaymentIntent o SetupIntent.CardElement usa un sistema de estilo diferente (prop style) que PaymentElement (Appearance API). No son intercambiables.PaymentElement y CardElement en el mismo proveedor Elements. Elige uno.rules de la Appearance API usan selectores personalizados de Stripe (.Input, .Label, .Tab), no selectores CSS estándar. Consulta la documentación para la lista completa.automatic_payment_methods esté habilitado y que tu Stripe Dashboard tenga otros métodos configurados.@font-face en tu CSS no están disponibles automáticamente dentro de Stripe Elements. Pásalas mediante la opción fonts en Elements.| Enfoque | Ventajas | Desventajas |
|---|---|---|
| PaymentElement | Todos los métodos de pago, un solo componente, moderno | Requiere clientSecret, menos control granular |
| CardElement | Simple, input de tarjeta de una sola línea | Solo tarjetas, API de estilo antigua |
| Campos de tarjeta separados | Control total del layout de cada campo | Más JSX, solo tarjetas |
| Stripe Checkout | Cero código de UI | Sale de tu sitio |
PaymentElement es un componente unificado que admite tarjetas, billeteras, transferencias bancarias y más de 40 métodos de pago en un solo componenteCardElement es un input de una sola línea que solo admite pagos con tarjeta"tabs" -- los métodos de pago se muestran como pestañas seleccionables"accordion" -- los métodos de pago se muestran como secciones expandibles"auto" -- Stripe decide el mejor layouttheme base ("stripe", "night", "flat")variables (colores, fuentes, radio de borde, espaciado)rules usando los selectores personalizados de Stripe (.Input, .Label, .Tab)No. Debes elegir uno u otro dentro de un único proveedor <Elements>. Montar ambos provoca conflictos y comportamiento indefinido.
<PaymentElement
options={{
fields: {
billingDetails: {
name: "auto",
email: "never",
phone: "never",
address: "auto",
},
},
}}
/>Establece un campo en "never" si lo recopilas tú mismo en tu propio formulario.
CardNumberElement, CardExpiryElement, CardCvcElementRevisa la configuración de métodos de pago en tu Stripe Dashboard. automatic_payment_methods muestra métodos que están habilitados en el Dashboard y son aplicables a la moneda de la transacción y la ubicación del cliente.
Stripe Elements se renderizan dentro de iframes y no pueden acceder a fuentes cargadas mediante @font-face en tu CSS. Debes pasar las fuentes explícitamente mediante la opción fonts en el componente <Elements>.
import type { Appearance, LayoutObject } from "@stripe/stripe-js";
const appearance: Appearance = {
theme: "night",
variables: { colorPrimary: "#6366f1" },
};
const layout: LayoutObject = { type: "tabs" };CardElement usa una prop style con estados base, invalid y completePaymentElement usa la Appearance API con theme, variables y rulesAcepta PaymentElementOptions de @stripe/stripe-js, que incluye layout, fields, terms, wallets y otra configuración. Impórtalo para seguridad de tipos al construir configuraciones dinámicas.
Sí. A diferencia de CardElement, PaymentElement requiere un clientSecret de un PaymentIntent o SetupIntent pasado mediante la prop options en <Elements>. Sin él, PaymentElement no se renderizará.
Revisado por Chris St. John·Última actualización: 7 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥