Cartão de crédito
Receba pagamentos com Cartão de crédito usando o endpoint unificado da LeonaPay.
Visão geral
Use
POST /api/v2/payments com method: CREDIT_CARD. O valor deve ser positivo e a moeda deve corresponder ao mercado.| Campo | Valor |
|---|---|
Método | CREDIT_CARD |
Mercado | Internacional |
Moedas | BRL, MXN, EUR, COP, ARS e USD |
Confirmação | Síncrona ou por webhook |
Criar pagamento Cartão de crédito
Envie uma referência única do seu pedido. Campos específicos do método aparecem abaixo.
Não envie paymentMethodToken para iniciar os campos seguros. Use clientSecret para montar número, validade e CVV separadamente na sua interface. sessionToken e checkoutUrl permanecem apenas para compatibilidade temporária. Antes da captura, a LeonaPay pode corrigir o método conforme o tipo real do cartão, se a configuração da empresa e as condições comerciais permitirem.
curl --request POST \
+ --url https://api.leonapay.com.br/api/v2/payments \
+ --header 'Accept: application/json' \
+ --header 'Content-Type: application/json' \
+ --header 'x-api-key: {your_api_key}' \
+ --header 'idempotency-key: {unique_uuid_per_payment}' \
--data '{
"method": "CREDIT_CARD",
"amount": 125.90,
"currency": "BRL",
"reference": "pedido-123",
"payer": { "name": "Ana Souza", "email": "ana@exemplo.com", "taxId": "12345678909" },
"returnUrl": "https://loja.exemplo.com/pagamento/retorno"
}'Sequência completa da integração
O pagamento com cartão possui duas etapas conectadas. Primeiro, o seu backend cria o pagamento na API da LeonaPay. Depois que a resposta trouxer
nextAction.type: CONFIRM_CARD, o seu frontend usa o clientSecret temporário para montar três campos seguros e separados. Assim, você controla completamente o layout sem que PAN, validade ou CVV passem pelo seu servidor. Para iniciar esse fluxo, não envie paymentMethodToken: ele representa um cartão já tokenizado e não é a credencial temporária dos campos.A chave LeonaPay existente continua somente no servidor. O navegador recebe apenas o clientSecret temporário da sessão. Não é necessário criar ou distribuir uma segunda chave pública da LeonaPay.
| Ordem | Responsável | O que acontece |
|---|---|---|
1 | Seu frontend | O comprador escolhe crédito ou débito e confirma o pedido |
2 | Seu backend | Cria o pagamento com CREDIT_CARD, usando a chave LeonaPay existente somente no servidor |
3 | API LeonaPay | Quando a sessão estiver pronta, retorna ACTION_REQUIRED com data.nextAction; enquanto prepara, pode retornar PENDING |
4 | Seu frontend | Chama OnpayCard.elements(...) e monta cardNumber, cardExpiry e cardCvc |
5 | LeonaPay | Tokeniza cada campo seguro sem expor os dados do cartão à sua aplicação |
6 | Comprador | Preenche o cartão e conclui o desafio 3DS, quando solicitado |
7 | Seu backend | Recebe o webhook assinado e libera o pedido somente após payment.paid |
Preparação assíncrona e consulta da sessão
A criação aguarda a preparação por um período limitado. HTTP 201 com
PENDING e sem data.nextAction significa que o pagamento foi criado, mas os campos ainda não estão prontos. Consulte GET /api/v2/transactions/{transactionId}no seu backend, usando a mesma empresa e ambiente e a permissãotransactions:read. A cada consulta, atualize tambémdata.nextAction, não somente o status. Não crie outra cobrança nem troque a chave de idempotência para obter os campos.Uma repetição idempotente da criação pode reproduzir a resposta inicial. Use a consulta para obter o estado atual. Respeite expiresAt: uma sessão expirada não deve ser reutilizada. checkoutUrl não significa HOSTED quando integrationMode é SECURE_FIELDS. returnUrl deve usar a mesma origem HTTPS do checkout que montará os campos.
| Resposta | Tratamento |
|---|---|
PENDING sem nextAction | Mostrar preparação e consultar a mesma transação com intervalo e limite de tentativas |
ACTION_REQUIRED + SECURE_FIELDS + clientSecret | Montar os campos integrados com data.nextAction.clientSecret |
ACTION_REQUIRED + HOSTED | Usar checkoutUrl; essa sessão não oferece campos integrados |
ACTION_REQUIRED sem nextAction | Não montar campos nem considerar pago; pode haver análise de segurança. Consultar e acompanhar |
AUTHORIZED | Autorizado, mas ainda não liquidado; não liberar o pedido |
PAID | Confirmar no backend antes de liberar o pedido uma única vez |
FAILED ou EXPIRED | Interromper a espera e apresentar o resultado |
Cartões de teste no Sandbox
Use estes números somente nos campos seguros do Sandbox. Informe qualquer nome, validade futura e CVV de três dígitos, exceto quando o cenário indicar outro valor. O resultado é determinístico e nenhum dado é enviado a uma adquirente real.
Números diferentes dos documentados são recusados no Sandbox. Nunca use cartões reais em testes.
| Número | Cenário |
|---|---|
4242424242424242 | Pagamento aprovado |
4000000000003220 | Pagamento aprovado com desafio 3DS |
4000000000000002 | Cartão recusado |
4000000000009995 | Saldo insuficiente |
4000000000000069 | Cartão expirado |
4000000000000127 | CVV inválido |
4000000000003063 | Desafio 3DS recusado |
4000000000000119 | Timeout: pagamento permanece pendente |
clientSecret, sessionToken e paymentMethodToken
paymentMethodToken é uma entrada opcional para integrações previamente habilitadas que já possuem um cartão tokenizado no mesmo ambiente. Ele nunca é retornado pela criação do pagamento. No fluxo recomendado, omita esse campo. A LeonaPay cria a sessão e responde comclientSecret; use-o somente no navegador para construir os campos seguros. sessionToken e checkoutUrl são aliases legados mantidos durante a migração do checkout hospedado.Nunca envie número do cartão, validade ou CVV para POST /api/v2/payments, para seu backend ou para logs. Esses dados existem somente dentro dos campos seguros LeonaPay.
| Campo | Direção | Finalidade |
|---|---|---|
paymentMethodToken | Entrada opcional | Confirma um cartão já tokenizado; não cria nem abre o checkout |
clientSecret | Saída recomendada | Autoriza temporariamente os campos seguros daquela transação |
sessionToken | Alias legado | Mantém compatibilidade com integrações de checkout hospedado |
checkoutUrl | Saída legada | Endereço do checkout hospedado durante o período de migração |
Campos seguros LeonaPay
Monte número, validade e CVV separadamente na sua própria interface. Crie uma única coleção com
OnpayCard.elements, monte cada elemento no contêiner escolhido e finalize comconfirmCardPayment. Todos os elementos precisam usar o mesmo clientSecret.O clientSecret é temporário e autoriza somente aquela confirmação. Não o registre em logs ou analytics. O retorno do navegador não confirma o pagamento: use GET /api/v2/payments/{paymentId} ou aguarde payment.paid no webhook assinado.
<!-- checkout.html: você controla a estrutura e o visual externo -->
<label>Número do cartão <div id="card-number"></div></label>
<div class="card-row">
<label>Validade <div id="card-expiry"></div></label>
<label>CVV <div id="card-cvc"></div></label>
</div>
<button id="pay-card" type="button">Pagar</button>
<p id="card-error" role="alert"></p>
<script src="https://client.leonapay.com.br/sdk/onpay-card.js"></script>
<script src="/checkout.js"></script>
// checkout.js
async function prepararPagamentoDoCartao(dadosDoPedido) {
// Esta rota pertence ao seu backend. É ele que chama POST /api/v2/payments.
const response = await fetch("/api/pagamentos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(dadosDoPedido)
});
let payment = await response.json();
if (!response.ok) throw new Error(payment.onpayDescription || "Falha ao criar pagamento");
const transactionId = payment?.data?.id;
if (!transactionId) throw new Error("Resposta sem identificador de pagamento");
// A rota GET abaixo também pertence ao seu backend: ela consulta
// GET /api/v2/transactions/{transactionId}, com a chave somente no servidor.
for (let attempt = 0; attempt < 30; attempt++) {
if (payment?.data?.nextAction || !["PENDING", "ACTION_REQUIRED"].includes(payment?.data?.status)) break;
await new Promise(resolve => setTimeout(resolve, 1000));
const lookup = await fetch("/api/pagamentos/" + encodeURIComponent(transactionId), { cache: "no-store" });
const latest = await lookup.json();
if (!lookup.ok || latest.success === false) throw new Error(latest.onpayDescription || "Falha ao consultar pagamento");
payment = latest; // Atualize a resposta inteira, incluindo data.nextAction.
}
const action = payment?.data?.nextAction;
if (action?.type !== "CONFIRM_CARD") {
// Mostre o estado atual. Preserve o ID para retomar a consulta, sem novo POST.
// ACTION_REQUIRED isolado pode ser análise de segurança, não campos prontos.
return { transactionId, status: payment?.data?.status };
}
if (action.integrationMode !== "SECURE_FIELDS" || !action.clientSecret) {
throw new Error("Esta sessão não oferece campos integrados; verifique o modo de integração");
}
if (!Number.isFinite(Date.parse(action.expiresAt)) || Date.parse(action.expiresAt) <= Date.now()) {
throw new Error("A sessão de cartão expirou");
}
const elements = OnpayCard.elements({
clientSecret: action.clientSecret,
appearance: {
color: "#111827",
fontFamily: "Inter, sans-serif",
fontSize: "16px"
}
});
elements.create("cardNumber").mount("#card-number");
elements.create("cardExpiry").mount("#card-expiry");
elements.create("cardCvc").mount("#card-cvc");
document.querySelector("#pay-card").addEventListener("click", async () => {
const button = document.querySelector("#pay-card");
const errorNode = document.querySelector("#card-error");
button.disabled = true;
errorNode.textContent = "";
const result = await OnpayCard.confirmCardPayment({
clientSecret: action.clientSecret,
elements
});
if (result.error) {
errorNode.textContent = result.error.message;
button.disabled = false;
return;
}
// Mostre "processando". Seu backend confirma por webhook ou consulta.
console.log("Confirmação enviada", result.payment.id);
});
}
prepararPagamentoDoCartao({
method: "CREDIT_CARD",
amount: 125.90,
currency: "BRL",
reference: "pedido-123",
payer: {
name: "Ana Souza",
email: "ana@exemplo.com",
taxId: "12345678909"
},
returnUrl: "https://loja.exemplo.com/pagamento/retorno"
});Migração do checkout hospedado
Integrações novas devem usar
OnpayCard.elements. O métodoOnpayCard.create continua disponível temporariamente para quem já abre o checkout hospedado, sem interromper pagamentos durante a migração. Você pode atualizar a apresentação sem trocar a chave secreta LeonaPay usada pelo seu backend.| Integração | Estado | Ação recomendada |
|---|---|---|
OnpayCard.elements | Recomendada | Adote campos separados em integrações novas |
OnpayCard.create | Compatibilidade | Migre gradualmente para os campos separados |
Chave LeonaPay do backend | Sem alteração | Continue autenticando POST /api/v2/payments no servidor |
Política de segurança de conteúdo (CSP)
Se sua página usa uma CSP restritiva, autorize o SDK da LeonaPay e os domínios técnicos abaixo. Eles são usados exclusivamente para tokenização e autenticação do cartão; nenhuma chave nativa é exposta.
script-src 'self' https://client.leonapay.com.br https://js.stripe.com;
connect-src 'self' https://client.leonapay.com.br https://api.stripe.com https://r.stripe.com https://m.stripe.network;
frame-src 'self' https://client.leonapay.com.br https://js.stripe.com https://hooks.stripe.com;Crédito e débito: o que muda?
O momento de exibição do formulário, o SDK e o tratamento do 3DS são iguais nos dois fluxos. Envie o método escolhido pelo comprador. Antes da captura, a LeonaPay pode corrigir method conforme o tipo real do cartão: credit corresponde a CREDIT_CARD; debit ou prepaid, a DEBIT_CARD. A correção exige empresa habilitada, método permitido na mesma integração e moeda, limites válidos e valor suficiente para as taxas e os splits existentes. Tipo desconhecido ou condições não permitidas não autorizam a captura. O valor bruto, a moeda, a referência e o PaymentIntent original são preservados.
| Método processado | Tipo real do cartão | Confirmação |
|---|---|---|
CREDIT_CARD | Crédito (funding credit) | Checkout seguro e 3DS quando solicitado |
DEBIT_CARD | Débito ou pré-pago (funding debit ou prepaid) | Checkout seguro e 3DS quando solicitado |
Aviso de correção do método
Quando a correção é registrada, data.method indica o método processado e data.warnings inclui ONP_CARD_METHOD_CORRECTED na consulta GET /api/v2/payments/{paymentId} e nos webhooks assinados de pagamento. A resposta inicial de POST /api/v2/payments pode ainda não conter o aviso, pois a confirmação é assíncrona. O exemplo abaixo mostra um trecho de data de um pagamento corrigido e já confirmado.
O aviso não é uma recusa nem uma confirmação de pagamento. Não reenvie a cobrança corrigida. Use processedMethod apenas em solicitações futuras com esse tipo de cartão e acompanhe o pagamento atual até o status final. AUTHORIZED ainda aguarda captura; libere o pedido somente após PAID.
{
"id": "pay_123",
"reference": "pedido_123",
"status": "PAID",
"method": "DEBIT_CARD",
"warnings": [
{
"code": "ONP_CARD_METHOD_CORRECTED",
"field": "method",
"requestedMethod": "CREDIT_CARD",
"processedMethod": "DEBIT_CARD",
"message": "O método foi ajustado ao tipo do cartão informado. Use DEBIT_CARD nas próximas solicitações com este tipo de cartão. Não reenvie esta cobrança."
}
]
}Fluxo 3DS e confirmação final
O emissor decide se o fluxo será frictionless ou challenge. Durante o desafio, mantenha o pedido aguardando. A fonte definitiva de verdade é o webhook assinado da LeonaPay, não o retorno do navegador.
A LeonaPay valida valor, moeda, empresa, PaymentIntent e tipo real do cartão antes da captura. CREDIT_CARD aceita funding credit; DEBIT_CARD aceita debit ou prepaid.
| Status LeonaPay | Evento | Ação |
|---|---|---|
ACTION_REQUIRED | payment.action_required | Exibir ou continuar a autenticação 3DS |
AUTHORIZED | payment.authorized | Aguardar captura/conclusão |
PAID | payment.paid | Liberar o pedido |
FAILED | payment.failed | Permitir nova tentativa com nova idempotência |
REFUNDED | payment.refunded | Registrar a devolução |
Liquidação e antecipação
Crédito e débito possuem configurações comerciais independentes. A LeonaPay resolve primeiro a regra específica da empresa e, quando ela não existe, usa a configuração global do método. A taxa e o prazo vigentes são gravados como snapshot na criação do pagamento. Somente uma correção segura do método antes da captura recalcula a taxa e registra as condições de liquidação e antecipação do método processado. Após essa decisão, o snapshot permanece imutável; mudanças posteriores nas configurações não alteram o pagamento. settlementDays usa dias corridos; após a confirmação, releaseAt informa quando o valor sai de RELEASING e fica disponível.
Consulte releaseAt em vez de calcular a data no seu sistema. Feriados e finais de semana não suspendem a contagem configurada.
| Método | Configuração | Liquidação |
|---|---|---|
CREDIT_CARD | Taxa, prazo e antecipação próprios | Dias corridos |
DEBIT_CARD | Taxa, prazo e antecipação próprios | Dias corridos |
Ciclo da antecipação
Um recebível elegível nasce como
SCHEDULED. Ao solicitar antecipação, ele fica RESERVED_FOR_ADVANCE e a solicitação permanece UNDER_REVIEW. Se aprovada, o líquido da antecipação é creditado e o recebível muda para ADVANCED; se recusada, volta ao agendamento normal. Repetições devem preservar a mesma chave de idempotency para não criar solicitações duplicadas.| Estado | Significado |
|---|---|
SCHEDULED | Aguardando a data normal de liberação |
RESERVED_FOR_ADVANCE | Reservado enquanto a solicitação é analisada |
ADVANCED | Antecipação aprovada e liquidada |
RELEASED | Liberado pela agenda normal |
Erros e retentativas
Erros de validação retornam
4xx. Indisponibilidade transitória retorna ONP503_CREDIT_CARD_TEMPORARILY_UNAVAILABLE ou ONP503_DEBIT_CARD_TEMPORARILY_UNAVAILABLE. Recusa, falha de 3DS, tipo de cartão desconhecido ou correção não permitida podem resultar em payment.failed. Uma correção aceita aparece em warnings e não exige reenvio. Consulte o pagamento antes de iniciar outra cobrança. Retente falhas transitórias somente quando retryable=true, com a mesma idempotência; após confirmar FAILED, uma nova tentativa do cliente deve usar outro UUID. Em Production, use pelo menos USD 1,00 no teste controlado, pois valores inferiores podem não alcançar o mínimo do adquirente após a conversão para a moeda de liquidação.| Código | Significado |
|---|---|
ONP202_3DS | Confirmação do titular necessária |
ONP422_*_PAYMENT_METHOD_TOKEN_INVALID | Token inválido ou criado em outro ambiente; gere um novo token e use uma nova idempotência |
ONP422_*_AMOUNT_TOO_SMALL | Valor abaixo do mínimo do adquirente; aumente o valor e use uma nova idempotência |
ONP400_RETURN_URL | URL de retorno/3DS ausente |
ONP409_IDEMPOTENCY_CONFLICT | Chave reutilizada com conteúdo diferente |
ONP503_*_TEMPORARILY_UNAVAILABLE | Método temporariamente indisponível |
payment.failed | Emissor, 3DS ou funding recusou a operação |
Erros e retentativas de Cartão de crédito
Toda falha inclui
error.code, error.retryable, error.requestId e um link direto para a referência. Corrija erros ONP400 antes de criar uma nova tentativa. Para ONP502, ONP503 ou ONP429, repita exatamente a mesma requisição com a mesma chave de idempotência após retryAfter.No painel, Integrações → Logs da API · 24h mostra a resposta pública e a tratativa pelo requestId, sem expor a liquidante usada.
| Código | Interpretação | Ação |
|---|---|---|
ONP400_* | Campo, moeda ou regra do método inválida | Corrigir o campo indicado em error.field |
ONP422_CREDIT_CARD_REJECTED | Operação recusada de forma definitiva | Revisar os dados e criar nova tentativa |
ONP502_CREDIT_CARD_INVALID_RESPONSE | Instruções de pagamento incompletas | Retentar com a mesma idempotência |
ONP503_CREDIT_CARD_TEMPORARILY_UNAVAILABLE | Método temporariamente indisponível | Backoff com jitter e mesma idempotência |
Resposta
Uma criação aceita retorna HTTP 202 quando exige confirmação. O status inicial pode ser
ACTION_REQUIRED, PENDING, AUTHORIZED ou PAID.{
"success": true,
"onpayCode": "ONP202_3DS",
"onpayDescription": "Customer confirmation required",
"data": {
"id": "pay_01J5VX8M8W8W6Q",
"reference": "pedido-123",
"status": "ACTION_REQUIRED",
"method": "CREDIT_CARD",
"currency": "BRL",
"amount": 125.90,
"fee": 1.75,
"settlementDays": 7,
"releaseAt": null,
"customer": {
"id": "cus_01J5VY2Q8G4K9D",
"name": "Ana Souza",
"email": null,
"phone": null,
"taxId": "12345678909"
},
"nextAction": {
"type": "CONFIRM_CARD",
"integrationMode": "SECURE_FIELDS",
"clientSecret": "ocs_...",
"sessionToken": "ocs_...",
"checkoutUrl": "https://client.leonapay.com.br/card-session/ocs_...",
"expiresAt": "2026-08-28T18:30:00Z"
},
"paidAt": null,
"createdAt": "2026-08-25T01:04:23.295Z",
"updatedAt": "2026-08-25T01:04:24.738Z"
}
}Ciclo da transação
Não conclua o pedido apenas pelo retorno do navegador. Consulte a API ou espere o evento assinado
payment.paid.| Status | Descrição |
|---|---|
ACTION_REQUIRED | Aguardando confirmação ou autenticação 3DS |
PENDING | Aguardando ação ou confirmação |
AUTHORIZED | Autorizado, aguardando conclusão |
PAID | Pagamento confirmado |
FAILED | Falha no processamento |
EXPIRED | Prazo encerrado |
REFUNDED | Valor estornado |