Integrar o Payment Fragment
Do script na página ao pagamento confirmado, em nove passos no navegador e no seu servidor, com os valores do Modo de teste.
Nove passos, na ordem em que o código roda: cinco no navegador, um no seu servidor e três de volta no navegador. Os trechos usam o Modo de teste (pk_test_... e sk_test_...); no Modo real, as mesmas chamadas com as chaves do Modo real.
A verdade do pagamento é o seu servidor
A sua página nunca vê a chave secreta (sk_): ela mora só no seu servidor. O que o navegador recebe serve para a tela; quem libera o pedido é o seu servidor, pelo webhook ou lendo o pagamento com a sk_.
Carregue o script
Na página de pagamento, pela tag, sempre do host do Assiny Gateway:
<script src="https://sdk-gateway-js.assiny.com.br/v1/assiny.js"></script>O script define a função global Assiny. Ele não usa eval nem nada que exija Trusted Types, então funciona em página com require-trusted-types-for 'script'. Se a sua página tem Content-Security-Policy, libere o script e os quatro iframes do SDK, somando às diretivas que você já tem:
script-src 'self' https://sdk-gateway-js.assiny.com.br;
frame-src https://sdk-gateway-card.assiny.com.br https://sdk-gateway-controller.assiny.com.br https://sdk-gateway-risk.assiny.com.br https://sdk-gateway-overlay.assiny.com.brCrie a instância
const assiny = Assiny('pk_test_...', { locale: 'pt-BR' });Com a chave publicável: a pk_test_ no Modo de teste, a do Modo real no Modo real. A chave publicável do Modo real só funciona em página https: (fora de localhost e 127.0.0.1). locale é 'pt-BR' (o padrão) ou 'en': o idioma dos textos do fragment e das mensagens de erro. Uma chave sk_ aqui é recusada, com aviso no console.
Configure o pagamento
const fragments = assiny.fragments({
mode: 'payment',
amount: 12990, // R$ 129,90, em centavos
currency: 'brl',
appearance: { theme: 'assiny' },
});amount é o total em centavos, um inteiro maior que zero. Ele serve para desenhar e dividir o pagamento; o valor que vale é o que o seu servidor manda no passo 6. Se o total mudar antes do clique (cupom, frete), chame fragments.update({ amount }). A appearance entra só aqui: Aparência.
Monte o fragment
<div id="pagamento"></div>const payment = fragments.create('payment', {
paymentMethodTypes: ['card', 'pix'],
combine: 'auto',
});
payment.on('loaderror', event => console.error(event.error.code));
payment.mount('#pagamento');O seletor precisa casar com exatamente um elemento. O fragment ocupa a largura dele e a altura acompanha o conteúdo. Opções e eventos: AssinyGateway Payment Fragment.
Crie os métodos de pagamento no clique
pagar.addEventListener('click', async () => {
const { parts, error } = await assiny.createPaymentMethod({
fragments,
params: {
billing_details: {
name: 'Maria Souza',
email: '[email protected]',
address: { country: 'BR' },
},
},
});
if (error) {
// A message já vem no idioma do locale; error.part diz qual Parte falhou.
mostrarErro(error.message);
return;
}
const resposta = await fetch('/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ parts }),
});
const pagamento = await resposta.json();
// Passos 7 e 8.
});O fragment confere os campos e cria um método de pagamento (pm_) por Parte: uma no cartão ou no PIX, duas no Combinar. A promessa resolve { parts: [{ method, amount, payment_method }] }, na ordem das Partes, ou { error }; ela nunca rejeita. Mande parts ao seu servidor (aqui, a rota /checkout é da sua loja).
O que você manda em billing_details vale como veio, para todas as Partes; o que falta entra dos campos Nome no cartão e País do fragment, quando ligados e preenchidos. Chaves aceitas: name, email, phone, tax_id com tax_id_type e address (line1, line2, city, state, postal_code, country); outra chave é descartada com aviso no console. Na política padrão, o pagador precisa de email no cartão e no PIX e de name no cartão; sem eles, a criação do método de pagamento ou o confirm recusa com payer_field_missing.
Crie e confirme o pagamento no servidor
Com a chave secreta, dois pedidos à API:
POST https://api-gateway.assiny.com.br/v1/payment_intents, comAuthorization: Bearer sk_test_...,Content-Type: application/jsone o corpo:amount: o total do pedido em centavos, calculado no seu servidor; a soma das Partes tem de ser igual a ele.currency:'brl'.billing_details: o Comprador (name,email,tax_idcomtax_id_type,phone,address).parts: as Partes que vieram do navegador, cada uma{ method, amount, payment_method }.combined_payment_methods: { enabled: true }(opcional, recomendado com duas Partes): liga a conferência de que a política da Conta aceita o Combinar na moeda.payment_method_options: { pix: { expires_after_seconds } }(opcional): o prazo do PIX em segundos; no Combinar (cartão + PIX), vale o prazo da política da Conta para o Combinar, não o enviado (PIX e a tela do QR).customer(opcional): ocus_do Comprador.
POST https://api-gateway.assiny.com.br/v1/payment_intents/{id}/confirm, com oidque voltou e sem corpo. O confirm não tem parâmetro: qualquer chave no corpo é 400parameter_invalid.
Devolva ao navegador só id, status, client_secret e last_payment_error.
# 1. Crie o pagamento com as Partes que o navegador mandou.
curl https://api-gateway.assiny.com.br/v1/payment_intents \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 12990,
"currency": "brl",
"billing_details": { "name": "Maria Souza", "email": "[email protected]" },
"parts": [
{ "method": "card", "amount": 12990, "payment_method": "pm_..." }
]
}'
# 2. Confirme com o id que voltou, sem corpo.
curl -X POST https://api-gateway.assiny.com.br/v1/payment_intents/pi_.../confirm \
-H "Authorization: Bearer sk_test_..."No curl, os dois pedidos à mão, com um pm_ e um pi_ de exemplo; nas outras linguagens, uma função checkout que recebe as Partes, faz os dois pedidos e devolve o status HTTP e o corpo para responder ao navegador.
Uma recusa volta com o status HTTP e o corpo { error: { type, code, message, param, request_id } } (param só quando a recusa é de um campo), e os trechos a devolvem como veio: 401 api_key_invalid (chave ausente ou inválida), 403 permission_denied, 400 com o motivo em code, como parts_sum_mismatch (a soma das Partes diferente de amount).
Siga o próximo passo
let resultado = pagamento;
if (pagamento.status === 'requires_action' || pagamento.status === 'processing') {
const { paymentIntent, error } = await assiny.handleNextAction({
clientSecret: pagamento.client_secret,
});
if (error) {
mostrarErro(error.message);
return;
}
resultado = paymentIntent;
}Com requires_action (o PIX), a tela do QR do PIX abre em tela cheia sobre a sua página e o SDK lê o pagamento a cada 3 s até o fim; com processing, o SDK só lê, sem desenhar nada. A promessa resolve { paymentIntent } com status, parts e last_payment_error, ou { error }. O detalhe está em PIX e a tela do QR.
Trate o resultado
switch (resultado.status) {
case 'succeeded':
mostrarConfirmacao();
break;
case 'requires_payment_method':
mostrarErro(resultado.last_payment_error?.message);
break;
case 'requires_action':
// O Comprador fechou a tela do QR; handleNextAction de novo a reabre.
break;
default:
// canceled ou requires_confirmation.
mostrarErro('Não foi possível concluir o pagamento.');
}succeeded: pago. Mostre a confirmação; o pedido é liberado pelo seu servidor.requires_payment_method: o cartão foi recusado ou o PIX venceu;last_payment_errordiz o motivo. Para tentar de novo, repita a partir do passo 5.requires_actiondepois dohandleNextAction: o Comprador fechou a tela do QR (Esc ou Fechar) sem pagar; chamarhandleNextActionde novo reabre o mesmo QR.canceled: o pagamento foi cancelado.requires_confirmation: o pagamento foi criado e ainda não confirmado (passo 6).{ error }em qualquer passo:error.messageno idioma dolocale. A lista completa está na Referência do SDK.
Teste no Modo de teste
Com as chaves pk_test_... e sk_test_..., nada é cobrado. Use uma validade no futuro e o CVC com o número de dígitos da bandeira (4 na Amex, 3 nas demais).
Cartões aprovados:
| Número | Bandeira |
|---|---|
4242 4242 4242 4242 | Visa |
5555 5555 5555 4444 | Mastercard |
4000 0007 6000 0002 | Visa (Brasil) |
3782 822463 10005 | Amex |
Cartões recusados, com o motivo que o Modo de teste simula e o decline_code que volta:
| Número | Motivo | decline_code |
|---|---|---|
4000 0000 0000 0002 | generic_decline | 05 |
4000 0000 0000 9995 | insufficient_funds | 51 |
4000 0000 0000 9987 | lost_card | 41 |
4000 0000 0000 9979 | stolen_card | 43 |
4000 0000 0000 0069 | expired_card | 54 |
4000 0000 0000 0127 | incorrect_cvc | 835 |
4000 0000 0000 0119 | processing_error | 96 |
4000 0000 0000 6975 | card_velocity_exceeded | 61 |
Na recusa, o confirm responde status: 'requires_payment_method' e last_payment_error com type: 'card_error', code: 'card_declined' e o decline_code da tabela; o motivo é só a descrição do que o Modo de teste simula e não volta na resposta. Para forçar a recusa com qualquer cartão, ponha REFUSED<código> no nome do portador (o billing_details.name ou o campo Nome no cartão), como Maria REFUSED51: o código volta em last_payment_error.decline_code; um código que o Modo de teste não conhece é ignorado.
PIX: na faixa de valor que a política da sua Conta aceita (na política padrão, a partir de R$ 3,50), a Parte PIX de teste é sempre paga sozinha em cerca de 5 s, e a tela do QR mostra o pagamento confirmado.