Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Use useStripe e useElements de @stripe/react-stripe-js para acessar instâncias do Stripe em seus componentes. Crie hooks personalizados para encapsular padrões de pagamento comuns e reduzir o boilerplate.
Hooks integrados:
"use client";
import { useStripe, useElements } from "@stripe/react-stripe-js";
function PaymentForm() {
// Acesse a instância do Stripe.js
const stripe = useStripe();
// Acesse a instância do Elements (gerencia os Elements montados)
const elements = useElements();
// Ambos retornam null até que o Stripe.js carregue
if (!stripe || !elements) return <div>Carregando...</div>;
// Agora você pode usar stripe.confirmPayment, elements.submit(), etc.
}Hook personalizado para status de pagamento:
// hooks/use-payment-status.ts
"use client";
import { useState, useCallback } from "react";
type PaymentStatus = "idle" | "processing" | "succeeded" | "failed";
interface UsePaymentStatusReturn {
status: PaymentStatus;
error: string | null;
setProcessing: () => void;
setSucceeded: () => void;
setFailed: (message: string) => void;
reset: () => void;
}
export function usePaymentStatus(): UsePaymentStatusReturn {
const [status, setStatus] = useState<PaymentStatus>("idle");
const [error, setError] = useState<string | null>(null);
const setProcessing = useCallback(() => {
setStatus("processing");
setError(null);
}, []);
const setSucceeded = useCallback(() => {
setStatus("succeeded");
setError(null);
}, []);
const setFailed = useCallback((message: string) => {
setStatus("failed");
setError(message);
}, []);
const reset = useCallback(() => {
setStatus("idle");
setError(null);
}, []);
return { status, error, setProcessing, setSucceeded, setFailed, reset };
}Hook personalizado para o fluxo completo de checkout:
// hooks/use-checkout.ts
"use client";
import { useCallback } from "react";
import { useStripe, useElements } from "@stripe/react-stripe-js";
import { usePaymentStatus } from "./use-payment-status";
interface UseCheckoutOptions {
returnUrl: string;
onSuccess?: () => void;
}
export function useCheckout({ returnUrl, onSuccess }: UseCheckoutOptions) {
const stripe = useStripe();
const elements = useElements();
const { status, error, setProcessing, setSucceeded, setFailed, reset } =
usePaymentStatus();
const handlePayment = useCallback(async () => {
if (!stripe || !elements) return;
setProcessing();
const { error: submitError } = await elements.submit();
if (submitError) {
setFailed(submitError.message ?? "Validation failed");
return;
}
const { error: confirmError, paymentIntent } =
await stripe.confirmPayment({
elements,
confirmParams: { return_url: returnUrl },
redirect: "if_required",
});
if (confirmError) {
setFailed(confirmError.message ?? "Payment failed");
} else if (paymentIntent?.status === "succeeded") {
setSucceeded();
onSuccess?.();
}
}, [stripe, elements, returnUrl, onSuccess, setProcessing, setFailed, setSucceeded]);
return {
handlePayment,
status,
error,
isReady: !!stripe && !!elements,
isProcessing: status === "processing",
reset,
};
}// app/checkout/checkout-form.tsx
"use client";
import { type FormEvent } from "react";
import { PaymentElement } from "@stripe/react-stripe-js";
import { useCheckout } from "@/hooks/use-checkout";
export function CheckoutForm({ amount }: { amount: number }) {
const {
handlePayment,
status,
error,
isReady,
isProcessing,
} = useCheckout({
returnUrl: `${window.location.origin}/success`,
onSuccess: () => {
console.log("Payment succeeded!");
},
});
function onSubmit(e: FormEvent) {
e.preventDefault();
handlePayment();
}
if (status === "succeeded") {
return (
<div className="text-center p-8">
<h2 className="text-2xl font-bold text-green-600">
Pagamento bem-sucedido!
</h2>
<p className="text-gray-600 mt-2">
Obrigado pela sua compra.
</p>
</div>
);
}
return (
<form onSubmit={onSubmit} className="max-w-md mx-auto p-6 space-y-6">
<PaymentElement />
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<p className="text-red-700 text-sm">{error}</p>
</div>
)}
<button
type="submit"
disabled={!isReady || isProcessing}
className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium
hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed
transition-colors"
>
{isProcessing
? "Processando..."
: `Pagar $${(amount / 100).toFixed(2)}`}
</button>
</form>
);
}Hook personalizado para status de assinatura:
// hooks/use-subscription-status.ts
"use client";
import { useState, useEffect } from "react";
interface SubscriptionInfo {
plan: string;
status: string;
currentPeriodEnd: string | null;
}
export function useSubscriptionStatus() {
const [subscription, setSubscription] = useState<SubscriptionInfo | null>(
null
);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchStatus() {
try {
const res = await fetch("/api/subscription/status");
if (res.ok) {
const data = await res.json();
setSubscription(data);
}
} catch {
console.error("Falha ao buscar status da assinatura");
} finally {
setLoading(false);
}
}
fetchStatus();
}, []);
const isActive =
subscription?.status === "active" ||
subscription?.status === "trialing";
const isPastDue = subscription?.status === "past_due";
const isCanceled = subscription?.status === "canceled";
return { subscription, loading, isActive, isPastDue, isCanceled };
}Uso em um painel:
// app/dashboard/page.tsx
"use client";
import { useSubscriptionStatus } from "@/hooks/use-subscription-status";
export default function DashboardPage() {
const { subscription, loading, isActive, isPastDue } =
useSubscriptionStatus();
if (loading) return <div>Carregando...</div>;
return (
<div>
{isPastDue && (
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-4">
<p className="text-yellow-800">
Seu pagamento está em atraso. Por favor, atualize seu método de pagamento.
</p>
</div>
)}
{isActive ? (
<h1>Bem-vindo de volta! Você está no plano {subscription?.plan}.</h1>
) : (
<h1>Faça o upgrade para acessar recursos premium.</h1>
)}
</div>
);
}useStripe() retorna a instância Stripe do provedor Elements mais próximo. Retorna null até que o Stripe.js termine de carregar assincronamente.useElements() retorna a instância Elements que gerencia todos os Elements do Stripe montados (PaymentElement, CardElement, etc.). Também retorna null até que esteja pronto.<Elements>. Chamá-los fora gera um erro.useCheckout compõem os hooks integrados com gerenciamento de estado para criar padrões de pagamento reutilizáveis. Isso mantém os componentes de formulário focados na apresentação.redirect: "if_required" em confirmPayment permite que você lide com o resultado no lado do cliente para pagamentos com cartão, enquanto ainda suporta redirecionamentos 3D Secure quando necessário.Hook com retentativa automática:
// hooks/use-payment-retry.ts
"use client";
import { useState, useCallback } from "react";
import { useStripe, useElements } from "@stripe/react-stripe-js";
export function usePaymentRetry(maxRetries = 2) {
const stripe = useStripe();
const elements = useElements();
const [retryCount, setRetryCount] = useState(0);
const confirmWithRetry = useCallback(
async (returnUrl: string) => {
if (!stripe || !elements) return { error: "Não pronto" };
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const { error, paymentIntent } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: returnUrl },
redirect: "if_required",
});
if (!error) return { paymentIntent };
if (error.type !== "api_error" || attempt === maxRetries) {
return { error: error.message };
}
setRetryCount(attempt + 1);
await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)));
}
return { error: "Número máximo de retentativas excedido" };
},
[stripe, elements, maxRetries]
);
return { confirmWithRetry, retryCount };
}useStripe() retorna Stripe | null (de @stripe/stripe-js).useElements() retorna StripeElements | null.import type { Stripe, StripeElements } from "@stripe/stripe-js";
// Os hooks integrados retornam tipos anuláveis
const stripe: Stripe | null = useStripe();
const elements: StripeElements | null = useElements();useStripe e useElements retornam null durante o carregamento inicial. Sempre verifique se são nulos antes de usá-los em manipuladores de eventos.<Elements>. Usá-los fora gerará um erro em tempo de execução.stripe ou elements e os passe para closures que executam mais tarde. A referência pode estar desatualizada. Sempre acesse-os dentro do corpo do callback.<Elements>. Este é um requisito que se propaga para cima através da composição.onSuccess em hooks personalizados deve ser envolvido em useCallback pelo consumidor para evitar a recriação do callback interno do hook a cada renderização.| Abordagem | Prós | Contras |
|---|---|---|
| Hooks integrados (useStripe, useElements) | API simples e oficial | Verboso em cada componente |
| Hooks wrapper personalizados | Reutilizável, encapsula lógica | Abstração extra para manter |
| Padrão render-prop | Funciona com componentes de classe | Padrão verboso e desatualizado |
| Stripe.js direto (sem React) | Sem dependência do React | Gerenciamento manual do DOM |
Ambos retornam null. Você deve sempre verificar se são nulos antes de usá-los em manipuladores de eventos ou lógica de UI. Exiba um estado de carregamento enquanto eles forem nulos.
Eles leem as instâncias Stripe e Elements do Contexto React fornecido por <Elements>. Chamá-los fora gera um erro em tempo de execução. Este requisito se propaga para quaisquer hooks personalizados que os envolvam.
"idle" | "processing" | "succeeded" | "failed"setProcessing, setSucceeded, setFailed, reset) para atualizar o estadoEle combina useStripe, useElements e usePaymentStatus em um único hook que retorna handlePayment, status, error, isReady e isProcessing. Um componente de formulário pode chamar handlePayment() ao enviar sem gerenciar a lógica do Stripe diretamente.
Ele instrui confirmPayment a redirecionar apenas para métodos de pagamento que o exijam (como 3D Secure). Para pagamentos simples com cartão, o resultado é tratado no lado do cliente, permitindo que você exiba uma mensagem de sucesso sem um redirecionamento de página.
As referências podem estar desatualizadas quando o closure for executado. Sempre acesse stripe e elements dentro do corpo do callback, ou certifique-se de que o closure capture a referência mais recente através de um useCallback com as dependências corretas.
Sem useCallback, a função onSuccess é recriada a cada renderização, fazendo com que o callback interno handlePayment de useCheckout também seja recriado a cada renderização (já que onSuccess está em sua lista de dependências).
const { subscription, loading, isActive, isPastDue, isCanceled } =
useSubscriptionStatus();Ele busca dados de assinatura de /api/subscription/status ao montar e deriva flags booleanas (isActive, isPastDue, isCanceled) do campo de status.
useStripe() e useElements() retornam null até que o script do CDN carregue. Em condições de rede ruins, isso pode levar vários segundos. Sempre exiba um estado de carregamento significativo, não uma tela em branco.
import type { Stripe, StripeElements } from "@stripe/stripe-js";
const stripe: Stripe | null = useStripe();
const elements: StripeElements | null = useElements();Ambos são anuláveis até que o Stripe.js inicialize.
maxRetries tentativas em falhas api_error1000 * (attempt + 1) ms)card_error) retornam imediatamenteRevisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥