Pular para o conteúdo principal
Use esta página como o guia canônico de integração do Payment Element.

Fluxo de pagamento em resumo

  1. Carregue o script do Payment Element no navegador.
  2. Crie uma instância de Elements com sua chave pública.
  3. Monte o campo de cartão hospedado.
  4. Envie via elements.submit(...).
  5. Seu back-end cria POST /v2/transactions.
  6. O SDK trata 3DS automaticamente ou retorna requires_action.
  7. Seu back-end finaliza o pedido por webhooks e reconciliação.

Exemplo de front-end

<form id="payment-form">
	<div id="card-element"></div>
	<button id="submit-button" type="submit" disabled>Pagar agora</button>
	<p id="payment-message" role="status"></p>
</form>

<script src="https://js.pagou.ai/payments/v3.js"></script>
<script>
	Pagou.setEnvironment("sandbox");

	const form = document.getElementById("payment-form");
	const submitButton = document.getElementById("submit-button");
	const messageEl = document.getElementById("payment-message");
	let isSubmitting = false;

	const elements = Pagou.elements({
		publicKey: "pk_sandbox_sua_chave_publica",
		locale: "pt-BR",
		origin: window.location.origin,
	});

	const card = elements.create("card", {
		theme: "default",
	});

	card.mount("#card-element");

	card.on("ready", () => {
		submitButton.disabled = false;
	});

	card.on("change", (event) => {
		if (event.valid) {
			messageEl.textContent = "";
			return;
		}

		messageEl.textContent = "Complete os dados do cartão.";
	});

	card.on("error", (event) => {
		messageEl.textContent = event.message ?? "Não foi possível carregar os campos seguros do cartão.";
	});

	form.addEventListener("submit", async (event) => {
		event.preventDefault();
		if (isSubmitting) return;

		isSubmitting = true;
		submitButton.disabled = true;
		messageEl.textContent = "Processando pagamento...";

		const result = await elements.submit({
			createTransaction: async (tokenData) => {
				const response = await fetch("/api/pay", {
					method: "POST",
					headers: { "Content-Type": "application/json" },
					body: JSON.stringify({
						token: tokenData.token,
						amount: 2490,
						installments: 1,
						orderId: "order_2001",
					}),
				});

				const payload = await response.json();

				if (!response.ok) {
					throw new Error(payload.detail ?? "Falha ao criar o pagamento.");
				}

				return payload.data ?? payload;
			},
		});

		if (result.status === "error") {
			messageEl.textContent = result.error ?? "O pagamento falhou.";
			isSubmitting = false;
			submitButton.disabled = false;
			return;
		}

		if (result.status === "requires_action" && result.transaction?.next_action) {
			messageEl.textContent = "Autenticação adicional necessária...";

			const challengeResult = await Pagou.handleNextAction(result.transaction.next_action);

			if (
				challengeResult.status === "failed" ||
				challengeResult.status === "canceled" ||
				challengeResult.status === "timed_out"
			) {
				messageEl.textContent = "A autenticação não foi concluída.";
				isSubmitting = false;
				submitButton.disabled = false;
				return;
			}
		}

		messageEl.textContent = "Pagamento enviado. Aguardando confirmação...";
	});
</script>

O que elements.submit() faz

Quando existe um card element montado, elements.submit():
  1. cria uma sessão de Elements se ainda não existir
  2. associa a sessão ao campo de cartão
  3. tokeniza o cartão
  4. chama o seu callback createTransaction com os dados tokenizados
  5. inspeciona o payload da transação devolvido
  6. continua 3DS automaticamente se next_action existir e o auto modal estiver habilitado
  7. caso contrário, retorna requires_action para o seu código chamar Pagou.handleNextAction(...)
A API de Transactions da Pagou é envelope-wrapped, mas elements.submit() espera que o callback createTransaction devolva o objeto da transação em si, com status e next_action no topo.

Exemplo de back-end

app.post("/api/pay", async (req, res) => {
	const { token, amount, installments, orderId } = req.body;

	const response = await fetch("https://api-sandbox.pagou.ai/v2/transactions", {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			Authorization: `Bearer ${process.env.PAGOU_TOKEN}`,
		},
		body: JSON.stringify({
			external_ref: orderId,
			amount,
			currency: "BRL",
			method: "credit_card",
			token,
			installments,
			buyer: {
				name: "Ada Lovelace",
				email: "ada@example.com",
				document: {
					type: "CPF",
					number: "12345678901",
				},
			},
			products: [{ name: "Plan upgrade", price: amount, quantity: 1 }],
		}),
	});

	const payload = await response.json();

	await savePaymentAttempt({
		orderId,
		transactionId: String(payload.id ?? payload.data?.id ?? ""),
		status: payload.status ?? payload.data?.status ?? "unknown",
	});

	res.json(payload);
});

Regras do contrato de back-end

Seu back-end deve:
  • criar a transação com suas credenciais secretas
  • devolver um shape que o front-end consiga usar diretamente
  • preservar id, status e next_action
  • armazenar o ID da transação para reconciliação
Não remova o objeto next_action antes de o navegador recebê-lo.

Tratamento de resultados

Trate o resultado imediato do navegador como resultado de integração, não como decisão de liberação.
Resultado no navegadorSignificadoO que fazer
errorsubmit ou tokenização falhoumostrar UI de nova tentativa
requires_actioncontinuação manual é necessáriachamar Pagou.handleNextAction(...)
estado de transação como pending ou authorizedo fluxo avançou, mas pode não ser finalmostrar UI de pendência
succeeded no challenge handlingo challenge terminouainda espere um estado final confirmado pelo back-end
failed, canceled, timed_out no challenge handlingo challenge não foi concluído com sucessopermitir nova tentativa e reconciliar se preciso

Modelo de liberação

Seu back-end deve ser o dono da decisão final do pagamento. Regra recomendada:
  • o navegador inicia o pagamento
  • o webhook confirma o estado final seguro para o negócio
  • a reconciliação cobre atrasos ou incertezas de webhook

Padrão de webhook

export async function handlePaymentWebhook(event) {
	const transaction = await pagouClient.transactions.retrieve(String(event.data.id), {
		requestId: `webhook_${event.id}`,
	});

	switch (transaction.data.status) {
		case "captured":
		case "paid":
			await markOrderAsPaid(transaction.data.external_ref);
			break;
		case "refused":
		case "canceled":
		case "expired":
			await markOrderAsFailed(transaction.data.external_ref);
			break;
		default:
			await keepOrderPending(transaction.data.external_ref);
	}
}

Checklist de produção

  • desabilite submit duplicado enquanto o fluxo estiver em andamento
  • use um external_ref estável por tentativa de pedido
  • armazene o ID da transação da Pagou
  • preserve next_action para o navegador
  • libere pedido apenas por estados finais confirmados pelo back-end
  • reconcilie resultados incertos com GET /v2/transactions/{id}

Páginas relacionadas