Pagamentos
Checkout transparente
Receba o cartao na pagina do lojista sem o numero passar pelo Minstrel - o servidor cria o pagamento, o SDK @minstrel/js tokeniza no navegador direto no adquirente e confirma, com cartao salvo mediante consentimento, sandbox, erros e responsabilidades de PCI.
No checkout transparente o portador digita o cartao na sua pagina, sem redirecionamento nem iframe do Minstrel. O que muda em relacao a um formulario comum e o caminho do numero: ele vai do navegador direto para o adquirente, que devolve um token de uso unico. O Minstrel so recebe esse token (e, no maximo, bandeira, BIN e ultimos 4 digitos) - nunca o numero nem o CVV.
Quem integra faz tres coisas:
- o servidor cria o pagamento sem cartao e entrega
paymentIdeclientSecreta pagina; - a pagina usa o SDK
@minstrel/jspara marcar os campos do cartao e chamarconfirmCardPayment; - o servidor acompanha o resultado pelo webhook e libera o pedido.
Como funciona
Servidor do lojista Navegador (pagina do lojista) Minstrel API Adquirente
| | | |
1. POST /v1/payments (sem cartao, chave de API) ------------->| |
|<------ paymentId + clientSecret (requires_payment_method) ----------------|
| | | |
2. entrega os dois a pagina ------>| | |
| | 3. sessao de tokenizacao | |
| |------------------------->|--- AccessToken --->|
| |<-- credencial + script --| |
| | |
| | 4. o script le os campos; o cartao vai DIRETO |
| |---------------------------------------------->|
| |<--------------- token de uso unico -----------|
| | | |
| | 5. confirma com o token | |
| |------------------------->|--- autoriza ------>|
| |<---- pagamento (captured, authorized ou failed)
| | | |
6. webhook payment.captured (ou payment.failed) <-------------| |
- Entre os passos 1 e 5 o pagamento fica em
requires_payment_method("Aguardando cartao" no painel). O prazo e de 24 horas, emactionExpiresAt. - O passo 4 e o unico em que o numero do cartao existe, e acontece entre o navegador e o adquirente. O Minstrel nao esta no caminho.
- Confirmado, o pagamento segue o caminho normal de um cartao: roteamento, fluxo, autorizacao, captura e eventos.
1. Criar o pagamento no servidor
POST /v1/payments sem card e sem paymentMethodId. Dois campos opcionais
entram nesse modo:
| Campo | Para que serve |
|---|---|
customerId | O cliente do pedido. Obrigatorio para o portador poder salvar o cartao (veja Salvar o cartao). |
tokenizationAccountId | A conta de conector (ca_01HQ...) que tokeniza e cobra o cartao. Omitido, vale a unica conta habilitada que tokeniza no navegador; havendo duas ou mais, informe a conta (sem isso a API responde 422 tokenization_account_ambiguous). |
curl https://api.minstrel.com.br/v1/payments \
-H "Authorization: Bearer $MINSTREL_API_KEY" \
-H "Idempotency-Key: pedido-1234" \
-H "Content-Type: application/json" \
-d '{
"amount": 14990,
"currency": "BRL",
"customerId": "cus_01M5...",
"description": "Pedido 1234",
"externalId": "pedido-1234"
}'
O mesmo pedido em Node, no seu servidor:
const response = await fetch('https://api.minstrel.com.br/v1/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MINSTREL_API_KEY}`,
'Idempotency-Key': `pedido-${order.id}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: order.totalInCents,
currency: 'BRL',
customerId: order.customer.minstrelId,
description: `Pedido ${order.id}`,
externalId: String(order.id),
}),
});
if (!response.ok) throw new Error(`Minstrel respondeu ${response.status}`);
const payment = await response.json();
// Entregue so estes dois campos a pagina do portador.
return { paymentId: payment.id, clientSecret: payment.clientSecret };
Resposta (resumida):
{
"object": "payment",
"id": "pay_01M5...",
"status": "requires_payment_method",
"amount": 14990,
"methodType": "card",
"clientSecret": "pay_01M5..._secret_Zk3pQ8...",
"actionExpiresAt": "2026-10-18T14:02:11.000Z"
}
clientSecret(<paymentId>_secret_...) e a credencial que deixa o navegador do portador operar esse pagamento e so ele: ler a situacao, pedir a sessao de tokenizacao e confirmar. Entregue-o a pagina do pedido e a mais ninguem: nao o grave em log, nao o envie por e-mail nem o guarde no seu banco. A chave de API nunca vai para o navegador.- O segredo e derivado do pagamento: vem igual em toda resposta (criacao,
GET /v1/payments/:id, repeticao da mesma requisicao idempotente) enquanto o portador ainda tem o que fazer com ele, isto e, informar o cartao ou concluir uma acao. Depois disso o segredo deixa de vir na resposta, e quem o tiver so consegue ler a situacao publica do pagamento. - O valor e a moeda ficam fixados no servidor. A pagina nao consegue mudar o
amount- e por isso que o pagamento nasce no seu back-end. Idempotency-Keyvale como em qualquer criacao: repetir a chamada devolve o mesmo pagamento (e o mesmoclientSecret).- O painel mostra o pagamento como Aguardando cartao, com o prazo, mas
nunca exibe o
clientSecret.
2. A pagina do portador
Instale o SDK no projeto da sua pagina. Ele e um pacote npm que voce inclui no seu bundle; nao ha script do Minstrel para carregar de um CDN:
npm install @minstrel/js
Os campos do cartao sao seus: voce controla o visual, a mascara e a validacao de interface. Duas regras de HTML:
<form id="checkout">
<input id="holder" type="text" autocomplete="cc-name" placeholder="Nome como esta no cartao" />
<input
id="number"
type="text"
inputmode="numeric"
autocomplete="cc-number"
placeholder="Numero do cartao"
/>
<input id="expiry" type="text" inputmode="numeric" autocomplete="cc-exp" placeholder="MM/AA" />
<input id="cvv" type="text" inputmode="numeric" autocomplete="cc-csc" placeholder="CVV" />
<label id="save-option" hidden>
<input id="save" type="checkbox" />
Salvar este cartao para as proximas compras
</label>
<button type="submit">Pagar R$ 149,90</button>
<p id="error" role="alert"></p>
</form>
- Sem o atributo
namenos campos do cartao. Um campo comnameentra no envio do formulario: se o formulario for enviado ao seu servidor (umsubmitque escapou dopreventDefault, um plugin de analytics que serializa formularios), o numero do cartao chega ao seu back-end e aos seus logs, e o seu escopo de PCI cresce. Semname, o navegador nao o inclui no envio. O SDK nao deixa passar:cardFieldseconfirmCardPaymentrecusam campos comname(MinstrelErrordo tipovalidation, codigocard_field_has_name) - cuidado com bibliotecas de formulario e de componentes que acrescentamnamesozinhas. - Nao copie o valor dos campos para a sua aplicacao. Em React, use campos
nao controlados (nada de
valueeonChangeguardando o numero em estado); em qualquer biblioteca de formularios, deixe os campos do cartao de fora do estado e da validacao que serializa o valor. O SDK nunca le o valor dos campos: quem le e o script do adquirente.
O JavaScript da pagina:
import { loadMinstrel, MinstrelError } from '@minstrel/js';
const minstrel = loadMinstrel({ apiBaseUrl: 'https://api.minstrel.com.br' });
// paymentId e clientSecret vieram do seu servidor (passo 1).
const { paymentId, clientSecret } = await fetch('/api/checkout/pedido-1234').then((r) => r.json());
// Marca os seus campos para o script do adquirente. Nao le nem guarda valores.
const card = minstrel.cardFields({
holderName: document.getElementById('holder'),
number: document.getElementById('number'),
expiry: document.getElementById('expiry'),
cvv: document.getElementById('cvv'),
});
// So ofereca "salvar cartao" se o pagamento aceitar (precisa de customerId).
const info = await minstrel.retrievePayment({ paymentId, clientSecret });
document.getElementById('save-option').hidden = !info.saveCardAllowed;
document.getElementById('checkout').addEventListener('submit', async (event) => {
event.preventDefault();
const error = document.getElementById('error');
error.textContent = '';
try {
const payment = await minstrel.confirmCardPayment({
paymentId,
clientSecret,
card,
saveCard: document.getElementById('save').checked,
});
if (payment.status === 'failed') {
// Recusa do emissor: este pagamento terminou. Veja "Recusa e nova tentativa".
error.textContent = payment.decline?.consumerMessage ?? 'O cartao foi recusado.';
return;
}
if (payment.status === 'requires_action') {
// Desafio do portador (3DS): o SDK ainda nao o conduz nesta versao.
error.textContent = 'Nao foi possivel concluir a autenticacao do cartao.';
return;
}
// captured ou authorized: mostre "recebemos o seu pedido". Quem libera o
// pedido e o seu servidor, pelo webhook (passo 3).
window.location.assign('/pedido/obrigado');
} catch (caught) {
if (!(caught instanceof MinstrelError)) throw caught;
// A mensagem do erro e para voce, nao para o portador. Mostre um texto seu.
if (caught.type === 'validation' && caught.fields?.length) {
error.textContent = caught.fields.map((issue) => issue.message).join(' ');
} else {
error.textContent =
'Nao foi possivel concluir o pagamento. Confira os dados e tente de novo.';
}
console.error(caught.type, caught.code, caught.requestId); // nunca ha dado de cartao aqui
}
});
// Ao desmontar o formulario (troca de tela, componente removido):
// card.destroy();
O que confirmCardPayment faz, em ordem:
- confere a pagina: os quatro campos existem, estao no documento e nao tem
name(se algo estiver errado, falha aqui, antes de gastar uma sessao); - pede ao Minstrel uma sessao de tokenizacao para o pagamento (uma credencial de uso unico do adquirente, gerada a cada tentativa);
- carrega o script do adquirente (na Cielo, o Silent Order Post), que le os
campos marcados por
cardFieldse envia o cartao direto ao adquirente; - recebe o token de uso unico (na Cielo, o
PaymentToken, que vale 20 minutos); - confirma o pagamento no Minstrel com esse token, mais bandeira, BIN e ultimos 4 digitos quando o adquirente os devolve, e os dados do navegador (idioma, tela e fuso, para antifraude e autenticacao), e devolve o pagamento.
Detalhes que poupam suporte:
- Duplo clique em "Pagar" e seguro. Enquanto uma confirmacao do mesmo pagamento esta em andamento, uma segunda chamada devolve a mesma promessa em vez de abrir outra tokenizacao.
retrievePayment({ paymentId, clientSecret })devolve a visao publica do pagamento (status,amount,merchant.name,saveCardAllowed,expiresAte, numa recusa,decline). Use depois de uma queda de rede para saber como a confirmacao terminou (veja Erros).loadMinstrelaceitarequestTimeoutMs(30 s por chamada ao Minstrel),scriptTimeoutMs(15 s para carregar o script do adquirente) etokenizationTimeoutMs(60 s para o adquirente devolver o token).
Salvar o cartao, com consentimento
saveCard: true e o consentimento do portador para guardar o cartao e
cobra-lo no futuro, sem ele presente (recorrencia, parcelas, cobranca
posterior). Por isso:
- ofereca a opcao desmarcada por padrao, com texto claro sobre o uso
("Salvar este cartao para as proximas compras"). Nunca envie
saveCard: truesem o portador ter marcado. Mostre a opcao so quandosaveCardAllowedfortruenoretrievePayment; - o pagamento precisa ter
customerId: o cartao salvo fica na carteira do cliente, esaveCard: truenum pagamento sem cliente e recusado; - com
saveCard: true, a autorizacao pede ao adquirente que guarde o cartao, e o token de uso unico e trocado pelo token permanente do cofre. O cartao aparece emGET /v1/customers/:id/payment-methodse pode ser usado em assinaturas e cobrancas do estabelecimento (MIT); - sem
saveCard, o token e de uso unico e nunca mais pode ser cobrado. O pagamento fica no historico com bandeira e ultimos 4 digitos, nada alem.
Se a autorizacao for recusada, o cartao nao e salvo.
Confirmar pelo servidor
Quem nao usa o SDK (aplicativo nativo, por exemplo, que tokeniza com o SDK do proprio adquirente) confirma pelo servidor, com a chave de API:
curl -X POST https://api.minstrel.com.br/v1/payments/pay_01M5.../confirm \
-H "Authorization: Bearer $MINSTREL_API_KEY" \
-H "Idempotency-Key: confirma-pedido-1234" \
-H "Content-Type: application/json" \
-d '{
"card": { "token": "3f9a...", "brand": "visa", "bin": "411111", "last4": "1111" },
"saveCard": true
}'
card.token e o token de uso unico do adquirente; brand, bin e last4
sao opcionais e servem so para exibicao. O campo device (dados do
navegador, para antifraude e autenticacao) tambem e opcional. A confirmacao
exige o escopo payments:write.
O SDK usa tres rotas publicas, pensadas para o navegador. Elas nao aceitam
chave de API: a credencial e o header Minstrel-Client-Secret, valido para um
unico pagamento. Tem CORS aberto e limite de requisicoes:
| Rota | O que faz |
|---|---|
GET /v1/public/payments/:id | Le a visao publica do pagamento: situacao, valor, nome da loja, saveCardAllowed, expiresAt. |
POST /v1/public/payments/:id/tokenization-session | Cria a sessao de tokenizacao: credencial de uso unico e script do adquirente. |
POST /v1/public/payments/:id/confirm | Confirma com { card: { token, brand?, bin?, last4? }, saveCard?, device? }. |
Os limites de hoje: 60 chamadas por minuto por IP em todas as rotas publicas
somadas e, por pagamento e por hora, 10 sessoes de tokenizacao e 5
confirmacoes. Passou disso, a API responde 429 com Retry-After. O limite
por pagamento so conta depois que o clientSecret confere, e freia quem testa
cartoes em sequencia - um portador legitimo raramente chega perto dele.
Cancelar um pagamento que nao vai mais ser pago
POST /v1/payments/:id/void cancela um pagamento ainda em
requires_payment_method (pedido cancelado, carrinho abandonado). Depois disso
a pagina nao consegue mais confirma-lo. Um pagamento que ninguem confirma ate
actionExpiresAt (24 horas) tambem deixa de aceitar o cartao. O status final
e canceled: nada chegou ao adquirente, entao nao ha autorizacao a desfazer.
curl -X POST https://api.minstrel.com.br/v1/payments/pay_01M5.../void \
-H "Authorization: Bearer $MINSTREL_API_KEY" \
-H "Idempotency-Key: cancela-pedido-1234" \
-H "Content-Type: application/json" \
-d '{ "reason": "Pedido cancelado antes do pagamento" }'
Erros e como tratar
confirmCardPayment e retrievePayment lancam MinstrelError. O campo type
diz onde a falha aconteceu e, com isso, o que fazer. Alem dele o erro traz
code, message (para o desenvolvedor: nunca carrega dado de cartao) e, quando
cabe, fields, requestId, httpStatus e providerCode:
type | O que aconteceu | O que fazer |
|---|---|---|
validation | A pagina esta mal montada (campo ausente, fora do documento ou com name) ou o adquirente apontou campos invalidos. Em fields vem cada campo e o motivo. Nada foi confirmado. | Mostre o motivo ao lado de cada campo e deixe o portador corrigir. O pagamento segue requires_payment_method. |
tokenization | O adquirente nao aceitou os dados, nao respondeu a tempo ou o script nao carregou. providerCode traz o codigo dele. | Deixe o portador tentar de novo. Cada tentativa pede uma sessao nova, no mesmo pagamento. Se persistir, informe o code. |
api | O Minstrel respondeu com erro. code, httpStatus e requestId dizem qual (tabela abaixo). | Trate pelo code. Pagamento vencido, cancelado ou ja confirmado pede um pagamento novo. |
network | Sem resposta do Minstrel (queda, timeout). Nao se sabe se a confirmacao chegou. | Chame retrievePayment antes de pedir ao portador para tentar de novo, para nao cobrar duas vezes. |
Codigos api deste fluxo (o catalogo completo esta em Erros):
code | Quando |
|---|---|
client_secret_invalid | O clientSecret nao confere com o pagamento (ou o pagamento nao existe: a resposta e a mesma). |
payment_not_confirmable | O pagamento nao esta em requires_payment_method (ja confirmado, recusado ou cancelado). |
payment_expired | O prazo de 24 horas terminou. |
no_tokenization_account | Nenhuma conta habilitada tokeniza no navegador, ou a informada em tokenizationAccountId nao serve. |
tokenization_account_ambiguous | Ha mais de uma conta que tokeniza: informe tokenizationAccountId ao criar o pagamento. |
Tres pontos de atencao:
- Recusa nao e excecao. Quando o emissor recusa,
confirmCardPaymentdevolve o pagamento comstatus: "failed". O motivo vem emdecline, com umaconsumerMessagepensada para mostrar ao portador (as regras de recusa valem como nos demais pagamentos). Trate esse caso no fluxo normal, como no exemplo acima. - Recusa e nova tentativa. Nesta versao a recusa encerra o pagamento:
failede final. Para o portador tentar outro cartao, o seu servidor cria um pagamento novo (passo 1) e a pagina recebe um novopaymentIdeclientSecret. Tentar de novo no mesmo pagamento esta nos planos. - O navegador nao e a fonte da verdade. O portador pode fechar a aba antes
de ver o resultado. Libere o pedido pelo evento
payment.captured(ou pela consultaGET /v1/payments/:idno servidor), nunca pelo que a pagina disse.
Sandbox
No sandbox use a conta do plugin Minstrel Sandbox (provedor
minstrel_sandbox). A pagina e o SDK funcionam como em producao, com uma
diferenca: nao ha script de adquirente para carregar, a sessao de tokenizacao
nao traz credencial e o SDK fabrica o token sem tocar nos campos (que continuam
precisando existir e estar marcados por cardFields). Nenhum cartao real e
consultado, e o SDK recusa esse provedor num ambiente de producao.
O resultado da cobranca e escolhido por sandboxScenario, um parametro de
confirmCardPayment (so tem efeito no sandbox):
const payment = await minstrel.confirmCardPayment({
paymentId,
clientSecret,
card,
sandboxScenario: 'decline_insufficient_funds',
});
sandboxScenario | Resultado |
|---|---|
approve | Aprova sempre, ignorando o comportamento configurado na conta. |
requires_action | Pede autenticacao do portador (requires_action). |
decline_insufficient_funds | Recusa 51, saldo insuficiente (reversivel). |
decline_lost_card | Recusa 41, cartao perdido (irreversivel). |
decline_not_permitted | Recusa 57, transacao nao permitida para o cartao. |
decline_issuer_unavailable | Recusa 91, emissor fora do ar (reversivel). |
decline_recurring_suspended | Recusa R0, suspensao de recorrencia (cancela a assinatura). |
Sem sandboxScenario vale o comportamento da conta Minstrel Sandbox e os
atalhos por valor de Primeiros passos
(R$ 100,51 recusa por saldo insuficiente, por exemplo). Com sandboxCard voce
escolhe a bandeira, o BIN e o final exibidos no painel. Assim voce testa o ramo
aprovado, cada recusa e o tratamento de erro da sua pagina sem contratar nada.
Tire o sandboxScenario do codigo que vai para producao.
PCI: o que fica com cada um
O Minstrel nunca recebe o numero do cartao nesse fluxo. A pagina, porem, e sua, e e nela que o portador digita o numero - por isso o seu escopo de PCI DSS existe, e costuma ser o SAQ A-EP (comercio que serve a pagina de pagamento e a deixa carregar o script do adquirente). Em troca, voce tem controle total do visual e da experiencia. Se prefere escopo menor, use o link de pagamento, em que o cartao e digitado na pagina do parceiro.
| Responsabilidade | De quem |
|---|---|
| Servir a pagina de pagamento so por HTTPS e mante-la livre de scripts desnecessarios | Lojista |
Nao colocar name nos campos, nao copiar o valor para estado, log ou analytics | Lojista |
| Mascarar os campos do cartao em ferramentas de gravacao de sessao e de mapa de calor | Lojista |
Politica de seguranca de conteudo (CSP) liberando so o Minstrel e o dominio do script do adquirente (na Cielo, pagador.com.br) | Lojista |
Guardar clientSecret apenas pelo tempo do checkout e nunca em log | Lojista |
Recusar numero de cartao e CVV na API (422 raw_card_data_rejected) e nunca gravar nem logar | Minstrel |
| Receber e guardar so token, bandeira, BIN e ultimos 4 digitos | Minstrel |
| Guardar o cartao no cofre do adquirente (PCI DSS dele) | Adquirente |
Se o numero do cartao chegar mesmo assim a API, ela recusa antes de qualquer validacao ou gravacao - veja Primeiros passos.
Requisitos na Cielo
Para tokenizar no navegador com a Cielo, a conta de conector precisa de:
- Credenciais do Silent Order Post:
sopClientIdesopClientSecret, dois campos opcionais da conta Cielo em Plugins. Sao gerados no site da Cielo, em Gestao API E-commerce, Microsservicos, Silent Order Post. Sem elas, a conta continua autorizando normalmente, mas nao tokeniza no navegador; - Cartao Protegido habilitado na loja (o cofre da Cielo), pedido ao suporte
da Cielo. E o que permite guardar o cartao: sem ele,
saveCardnao devolve o token permanente. Veja Conectores; - Credito e sem autenticacao nesta versao. Debito, 3DS e Data Only (Cielo) entram na proxima etapa. Enquanto isso, o roteamento e o fluxo seguem como nos demais cartoes.
O AccessToken do Silent Order Post e de uso unico e vale cerca de 20 minutos;
o Minstrel gera um novo a cada sessao de tokenizacao. Voce nao lida com ele.
Proximos passos
- Transacoes - status
requires_payment_method, confirmacao e cancelamento - Clientes e grupos - carteira de cartoes salvos
- Assinaturas - cobrar o cartao salvo na recorrencia
- Webhooks - o evento que libera o pedido