Skip to content

Appmax JS ​

O que é o Appmax JS ​

O appmax.js é uma biblioteca JavaScript desenvolvida pela Appmax para integração segura em páginas de checkout. Ao incluir o script, você não precisa se preocupar com alterações no visual da loja, já que ele atua de forma discreta e eficiente.

Coleta de IP é sempre obrigatória

A coleta de IP via Appmax JS é obrigatória para todas as integrações — inclusive se sua arquitetura estiver em escopo PCI-DSS. Não existe alternativa via API só para esta etapa.

Tokenização server-side exige PCI-DSS

Já a tokenização do cartão pode ser feita de duas formas: pelo próprio Appmax JS (recomendado — o script isola os dados sensíveis do seu servidor) ou diretamente pela sua API, caso sua arquitetura esteja em escopo PCI-DSS. Ao tokenizar pelo backend, o seu servidor toca o número do cartão e o CVV em claro — isso só é permitido dentro do escopo PCI-DSS. Se você não tem certeza, use o caminho via Appmax JS. Veja Tokenização do cartão de crédito.

Como funciona ​

Devido as diretrizes do PCI DSS (Padrão de Segurança de Dados da Indústria de Cartões de Pagamento), é crucial proteger dados sensíveis. O appmax.js foi projetado para:

  • Evitar que dados sensíveis do cartão passem pelos seus servidores.
  • Coletar o IP do cliente para segurança do fluxo de pagamento.

Qual é a sua integração ​

O script atende três cenários, e o que você precisa implementar muda bastante entre eles. Identifique o seu antes de seguir:

CenárioO que você usaLeia
Checkout com tokenização no browser — o caso mais comum<form data-appmax-customer> para o IP + <form data-appmax-checkout> para o cartão, com onIp e onTokenizeColeta do IP e Tokenização
Apenas coleta de IP — você tokeniza pela API, em escopo PCI-DSS<form data-appmax-customer> (pode ser vazio e escondido) + onIp. Não existe form de cartão na páginaColeta do IP
Apple Pay e Google Pay — somam-se ao primeiro cenárioO mesmo do checkout, mais onUpdate e onAuthorize e os seletores do botão de cada carteiraImplementando o botão Apple Pay · Implementando o botão Google Pay

Nos três casos a coleta de IP é obrigatória, e o gatilho é sempre o form[data-appmax-customer].

Como usar ​

Incluir o script da CDN

A URL do script muda conforme o ambiente:

html
<!-- Produção -->
<script src="https://scripts.appmax.com.br/appmax.min.js"></script>
html
<!-- Sandbox -->
<script src="https://scripts.sandboxappmax.com.br/appmax.min.js"></script>

O script é por ambiente — e não é o mesmo arquivo

Os dois endereços servem builds diferentes: a URL da API fica embutida no bundle em tempo de build. Não existe parâmetro nem configuração para apontar o script de um ambiente para o outro.

Misturar o script de um ambiente com o external_id do outro faz a tokenização acontecer contra a API errada, que não conhece aquele merchant. O resultado é 404 Merchant not found, que chega ao seu onError como TOKENIZATION_FAILED com status: 404 — a message é Failed to process payment: Failed to tokenize card., genérica, e quem diz a causa real é o details. Veja Ambientes para a lista completa de URLs.

Inicializar o AppmaxScripts

Passe um objeto de opções, informando só os callbacks dos eventos que te interessam:

javascript
window.AppmaxScripts.init({
  externalId: 'SEU_EXTERNAL_ID',
  onIp: ({ ip }) => { customer.ip = ip },
  onTokenize: ({ token }) => completePayment(token),
  onError: (err) => console.error(err.code, err.message, err.stage),
});
OpçãoObrigatórioDescrição
externalIdSim para tokenização e carteirasIdentificador da instalação do app na loja. Ver external-id.
onIpRecomendadoChamado quando o IP do cliente é coletado. Recebe { ip }.
onTokenizeSim para tokenizaçãoChamado quando o cartão é tokenizado. Recebe { token }.
onErrorRecomendadoChamado em qualquer falha, com um objeto estruturado — veja O contrato do onError. É opcional: o script sempre registra a falha no console.error, com o prefixo [Appmax].
onUpdateSim para Apple Pay e Google PayCallback chamado quando a PaymentSheet abre e a cada mudança nela (Apple Pay), ou ao carregar o botão e após cada tentativa (Google Pay). Deve retornar o carrinho atual, com valores numéricos em reais — veja o contrato de retorno. O Google Pay utiliza apenas total.
onAuthorizeSim para Apple Pay e Google PayCallback chamado quando o pagamento é autorizado. O mesmo callback atende às duas carteiras: recebe o Apple Token ou o paymentMethodData do Google Pay. No Apple Pay, rejeite a Promise para sinalizar falha — veja como sinalizar falha; no Google Pay o retorno não é utilizado — veja como o onAuthorize se comporta.
onSuccessNãoCallback único, mantido por compatibilidade. Use onIp e onTokenize em código novo — veja onSuccess: o formato antigo.

onIp e onTokenize substituem o onSuccess no respectivo evento: cada evento chama exatamente um callback, nunca os dois.

Implementando o botão de uma carteira?

onUpdate e onAuthorize são só metade da história — o botão também exige seletores de DOM específicos e uma ordem de carregamento própria. Veja o passo a passo dedicado em Implementando o botão Apple Pay com o Appmax JS e, para o Google Pay, Implementando o botão Google Pay com o Appmax JS.

A forma posicional ​

A assinatura antiga continua funcionando e não será removida:

javascript
window.AppmaxScripts.init(onSuccess, onError, externalId, onUpdate, onAuthorize);

Ela não dá acesso aos callbacks por evento — o onSuccess posicional recebe os dois payloads e você precisa discriminar (veja onSuccess: o formato antigo). Para código novo, prefira o objeto.

Nada muda nesta forma. O onSuccess recebe { ip } na coleta e a string do token na tokenização; o onError recebe a mensagem como string, apenas em falha de tokenização. É o mesmo comportamento de sempre, e ele não será alterado — quem já integrou assim não precisa mexer em nada.

Se você só vai usar a coleta de IP, os campos de tokenização e de carteiras (Apple Pay, Google Pay) podem ser omitidos:

javascript
window.AppmaxScripts.init({ onIp: ({ ip }) => { customer.ip = ip } });

externalId é obrigatório para tokenização pelo Appmax JS

Sem o externalId, o submit de qualquer form com data-appmax-checkout falha com MISSING_EXTERNAL_ID antes de a chamada HTTP ser feita. O valor é o mesmo external_id definido durante a instalação do app na loja — persista no seu banco e renderize no template do checkout. Veja external-id para a referência completa.

O external_id é por ambiente. O id de uma instalação de sandbox não existe no registro de produção: usá-lo lá devolve 404 Merchant not found, que chega no onError como TOKENIZATION_FAILED / status: 404. Ao promover para produção, refaça a instalação e guarde o novo external_id.

Se sua arquitetura está em escopo PCI-DSS e você prefere tokenizar direto pela sua API (sem o script), use o caminho via backend com o access token do merchant (Authorization: Bearer) em vez do externalId — veja Tokenização do cartão de crédito.

init() pode lançar exceção síncrona para carteiras

Se você passar onUpdate e onAuthorize (fluxo Apple Pay ou Google Pay) sem um externalId válido, o init lança Error("External ID is required for Apple Pay use.") de forma síncrona, dentro da própria chamada — não via onError. Em React, isso estoura dentro do useEffect e pode derrubar a árvore inteira se não houver try/catch ao redor da chamada.

O contrato do onError ​

O que o onError recebe depende da forma de init() que você usou:

Forma de init()O que chega no onErrorQuando é chamado
Objeto de opçõesum objeto estruturadoem qualquer etapa: fingerprint, ip, setup, tokenize
Posicional (legada)a string da mensagemapenas em falha de tokenização

A diferença é deliberada: a forma posicional sempre entregou uma string, e mudar isso quebraria integrações em produção. Nas duas formas toda falha continua indo para o console.error com o prefixo [Appmax] — inclusive as etapas que a forma posicional não encaminha.

Campos do objeto estruturado:

CampoConteúdo
codeMISSING_EXTERNAL_ID, IP_FETCH_FAILED, FINGERPRINT_FAILED, FORM_SETUP_FAILED, TOKENIZATION_FAILED, APPLE_PAY_FAILED ou GOOGLE_PAY_FAILED
messageMensagem legível.
stageEm que etapa a falha aconteceu: ip, fingerprint, setup, tokenize, apple_pay ou google_pay.
statusCódigo HTTP, quando a falha veio da API.
detailsCorpo da resposta, quando disponível — é o que diferencia um 404 de um 422.
javascript
window.AppmaxScripts.init({
  externalId: 'SEU_EXTERNAL_ID',
  onTokenize: ({ token }) => completePayment(token),
  onError: (err) => {
    if (err.code === 'TOKENIZATION_FAILED' && err.status === 404) {
      // external_id de outro ambiente, ou instalação não concluída
    }
    console.error(err.code, err.stage, err.status, err.details);
  },
});

Leia o status antes de abrir o Network tab: 404 é external_id inválido para aquele ambiente, 422 é payload, 5xx é indisponibilidade.

A falha de IP também chega aqui

Na forma de objeto, a falha na coleta de IP chega ao onError como IP_FETCH_FAILED / stage: "ip". Na forma posicional ela vai apenas para o console.error, como sempre foi.

onSuccess: o formato antigo ​

Se você usa o onSuccess (posicional ou no objeto), o mesmo callback atende os dois eventos, com payloads diferentes:

QuandoO que chega
Coleta de IPum objeto: { ip: "187.0.0.1" }
Tokenização do cartãoa string do token: "tok_..."

Nesse callback o token não vem embrulhado: data.token é undefined. Para recebê-lo como { token }, use o onTokenize.

javascript
function onSuccess(data) {
  if (data?.ip) {
    customer.ip = data.ip;
    return;
  }

  // aqui data é a string do token
  completePayment(data);
}

Discriminar por tipo (typeof data === 'string') funciona e continuará funcionando.

Este formato é estável

O onSuccess está depreciado, mas não será removido e o payload dele não muda. O script é servido por uma URL única, sem versionamento: alterar o formato deste callback mudaria o comportamento de todas as integrações ativas ao mesmo tempo. Por isso o caminho novo é aditivo — onIp e onTokenize — em vez de uma mudança no onSuccess.

Contrato de DOM: o init não é reativo ​

O AppmaxScripts.init(...) faz querySelector uma única vez, no momento em que é chamado, e não observa mudanças no DOM depois disso. Em uma página tradicional (HTML renderizado no servidor, nada muda depois do load) isso é transparente. Em React, Vue ou qualquer SPA, exige atenção:

  • O gatilho de IP, o form de cartão e os botões do Apple Pay e do Google Pay precisam existir no DOM antes do init rodar. Se o elemento aparece depois — atrás de uma rota, um passo do checkout, um v-if/condicional — o SDK nunca o encontra. Não há erro, não há log: o clique ou a coleta simplesmente não acontecem.
  • Chamar init() de novo substitui o handler, não empilha. Um submit gera uma tokenização, independentemente de quantas vezes o init rodou — então o StrictMode do React (que monta efeitos duas vezes em desenvolvimento) não causa cobrança dupla. Ainda assim, chame init uma vez por carregamento de página: cada chamada refaz a coleta de IP e o fingerprint.
  • O externalId é capturado no init, não lido a cada uso. Trocar o valor depois não muda as requisições seguintes. Se o externalId mudar (troca de loja, troca de ambiente), recarregue a página.

Funcionalidades disponíveis ​

Coleta do IP do cliente ​

A coleta acontece quando o SDK encontra um <form data-appmax-customer> no DOM — não é preciso dar submit em nada nem esperar interação do usuário. Sem esse form no momento do init, o onIp simplesmente não dispara (ver "Contrato de DOM" acima — é uma falha silenciosa, sem erro nenhum).

Pode ser o formulário de dados do comprador que você já tem:

html
<form id="customer-form" data-appmax-customer>
  <div>
    <label for="first-name">Primeiro Nome:</label>
    <input type="text" id="first-name" name="first-name" required />
  </div>
  <div>
    <label for="last-name">Ultimo Nome:</label>
    <input type="text" id="last-name" name="last-name" required />
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required />
  </div>
  <div>
    <label for="phone">Telefone:</label>
    <input type="text" id="phone" name="phone" required />
  </div>
  <button type="submit">Enviar</button>
</form>

Se a sua página não tem um formulário de comprador — um checkout em etapas, uma SPA que guarda o comprador em estado — use um form vazio e escondido, só como gatilho:

html
<form data-appmax-customer hidden></form>

O SDK injeta um <input hidden> dentro do form[data-appmax-customer]

Ao encontrar esse form, o SDK insere nele um <input type="hidden" id="ip" name="ip" data-appmax-ip> via DOM direto — fora do controle do React/Vue. Em uma página tradicional ele vai junto no submit nativo, e é assim que o IP chega ao seu backend.

O append é idempotente: chamar init() de novo atualiza o valor em vez de anexar um segundo campo.

Nada é injetado no form[data-appmax-checkout] — se você está procurando o campo de IP no formulário de cartão, ele não está lá.

Numa SPA esse nó pode ser descartado no próximo re-render sem aviso nenhum. Não conte com ele: use o IP que chega no onIp, que é a fonte confiável nos dois casos.

Para frameworks como Vue.js, o gatilho precisa estar renderizado antes do init — veja "Contrato de DOM" acima. Recupere o IP no callback:

html
<template>
  <form data-appmax-customer hidden></form>
</template>
javascript
const customer = ref({
  first_name: '',
  last_name: '',
  email: '',
  phone: '',
  ip: ''
})

onMounted(() => {
  const script = document.createElement('script')
  script.src = 'https://scripts.sandboxappmax.com.br/appmax.min.js'
  script.onload = () => {
    if (window.AppmaxScripts) {
      window.AppmaxScripts.init({
        onIp: ({ ip }) => { customer.value.ip = ip },
        onError: (err) => console.error(err.code, err.message)
      })
    }
  }
  document.head.appendChild(script)
})

Precisa de um exemplo completo, com tokenização de cartão? Veja Checkout em SPA (React/Next.js).

Tokenização de pagamento ​

A tokenização é realizada quando um formulário é enviado com o atributo data-appmax-checkout. Os dados sensíveis do cartão são convertidos em um token seguro.

Requer externalId na inicialização

Para tokenizar pelo script, inicialize com o externalId da loja. Sem ele, o submit do form falha com MISSING_EXTERNAL_ID. Veja external-id.

O token chega no callback onTokenize({ token }). Você não precisa tocar em HTTP nem em headers. Se precisar implementar a tokenização manualmente (sem o script), o contrato do endpoint subjacente está em Tokenização do cartão de crédito — inclui o caminho alternativo via backend (Bearer do merchant), permitido apenas em escopo PCI-DSS.

html
<form id="payment-form" method="POST" data-appmax-checkout>
  <div>
    <label for="card-number">Numero do Cartao:</label>
    <input type="text" id="card-number" name="card-number"
           appmax-form-element="number" required />
  </div>
  <div>
    <label for="card-holder-name">Nome do Titular:</label>
    <input type="text" id="card-holder-name" name="card-holder-name"
           appmax-form-element="holder_name" required />
  </div>
  <div>
    <label for="exp-month">Mes de Expiracao:</label>
    <input type="text" id="exp-month" name="exp-month"
           appmax-form-element="expiration_month" required />
  </div>
  <div>
    <label for="exp-year">Ano de Expiracao:</label>
    <input type="text" id="exp-year" name="exp-year"
           appmax-form-element="expiration_year" required />
  </div>
  <div>
    <label for="cvv">CVV:</label>
    <input type="text" id="cvv" name="cvv"
           appmax-form-element="cvv" required />
  </div>
  <button type="submit">Pagar</button>
</form>

Os name são obrigatórios e de valor fixo — é por eles que o SDK lê o formulário, com new FormData(form). O appmax-form-element é um atributo HTML (não uma classe CSS), mantido por compatibilidade, que não é lido pelo SDK e pode ser omitido.

Camponame (obrigatório)appmax-form-element (opcional)
Número do cartão de créditocard-numbernumber
Nome do titular do cartãocard-holder-nameholder_name
Mês de expiraçãoexp-monthexpiration_month
Ano de expiraçãoexp-yearexpiration_year
Código de segurança (CVV)cvvcvv

Os dois valores são diferentes — e isso derruba integrações

Repare que, em cada linha, o name não é igual ao valor do appmax-form-element. Quem assume que são a mesma coisa e escreve name="number", name="holder_name" e assim por diante acerta só o CVV — o único campo em que os dois valores coincidem.

O sintoma é um 422 listando number, holder_name, expiration_month e expiration_year como field is required, com a tela visivelmente preenchida. Trocar ou remover qualquer name faz o campo chegar vazio na tokenização.

Consequência para SPAs: quem lê os campos é o FormData, ou seja, a propriedade value do elemento no DOM — não o estado do framework. Um input controlado funciona, porque o React mantém o DOM sincronizado. O que não funciona é value sem onChange: o React trata o campo como somente leitura, o comprador não consegue digitar e o FormData lê o valor inicial.

Fingerprint do navegador ​

O getFingerprint() devolve os sinais de dispositivo que o script consegue coletar. É uma chamada direta, independente do init(): devolve Promise, não passa por callback e não exige externalId — basta o script carregado.

javascript
const basico   = await AppmaxScripts.getFingerprint();
const completo = await AppmaxScripts.getFingerprint({ meta: true });

Sem argumento, devolve os sinais básicos: userAgent, language, screenWidth, screenHeight, colorDepth, deviceMemory, hardwareConcurrency, touchPoints, timezone e isPrivate.

Com { meta: true }, o mesmo objeto mais um meta com os sinais de detecção — que vem null se a coleta dos sinais falhar:

Campo de metaConteúdo
bot{ result: "notDetected" | "bad", reasons: [...] }
incognitotrue, false ou null
developerToolsHeurística de DevTools aberto.
virtualMachineInferido pelo renderer do WebGL.
privacySettingsBrave, canvas randomizado, resistFingerprinting.
tamperingAnomalias entre APIs que deveriam concordar.
webgl{ vendor, renderer }
extrasmath, voices, mediaDevices, network, css

O retorno fica com você — a Appmax não recebe nada

Esses dados servem à análise de risco da sua operação: o valor fica no browser e só é enviado se você enviar. Não é o token do antifraude da Appmax (payment.Fingerprint.token), que é gerado por outra biblioteca e tem formato e propósito diferentes.

O formato de meta pode mudar — trate os campos de detecção como informativos, não como contrato.

Custo de coleta

Com meta: true a coleta faz WebGL, media devices e vozes, e leva algumas centenas de milissegundos. Dispare no carregamento da página e guarde a Promise; dê await só no submit.

AppCheckout e AppmaxScripts são o mesmo objeto — tanto faz qual você usar.

Página tradicional e SPA: o que muda ​

As escolhas divergem em pontos concretos:

MPA (PHP, HTML)SPA (React/Next, Vue)
Gatilho de IP<form data-appmax-customer> — ver a nota abaixo<form data-appmax-customer hidden> vazio
<input hidden name="ip"> injetadovai no submit nativo, se houverignorar — use o IP do onIp
Quando chamar init()script no <head>, DOM estáveldepois que o externalId chegar do backend, uma vez por carregamento
Form de cartãoestá na página desde o primeiro paintsempre montado, escondido por CSS — nunca atrás de um condicional
preventDefaulto do SDK bastaprecisa do seu também

O gatilho de IP não depende de ser MPA ou SPA

O que decide é como você envia os dados do comprador:

  • Submit nativo do formulário (o browser dá POST na sua rota): marque esse formulário com data-appmax-customer. O SDK injeta <input type="hidden" name="ip"> dentro dele, e o IP viaja junto com o POST — você não escreve uma linha de JavaScript para isso.
  • Envio por JavaScript (fetch/XHR, com preventDefault no submit): um <form data-appmax-customer hidden></form> vazio serve só como gatilho, e você lê o IP no onIp. O formulário de verdade fica separado e sem o atributo.

Uma página PHP ou HTML estático que monta o payload em JS usa o formulário vazio, como qualquer SPA. O inverso também vale.

O preventDefault merece destaque próprio: numa SPA existe uma janela real entre a montagem do componente e o fim do init(), que é assíncrono e espera rede. Um clique nessa janela faz o browser dar POST nativo e sair da aplicação — o sintoma clássico de "clicou em pagar e a página virou um 405". Registre o seu próprio preventDefault no form.

Para um exemplo completo em React/Next.js, veja Checkout em SPA.

Diagnóstico: sintoma e causa ​

Sintomaerr.codeCausa provável
O submit não faz nada — sem erro, sem requisição—O form não existia no DOM quando o init() rodou (ver "Contrato de DOM"); ou há um elemento .appmax-ip na página, que encerra a inicialização antes de o listener do cartão ser registrado — remova-o e use <form data-appmax-customer> para o IP
Tokeniza com 201 e o pagamento nunca é disparado—Handler lendo data.token num payload que não é objeto. Use onTokenize
Todos os campos do cartão chegam vaziosTOKENIZATION_FAILEDOs name do form foram renomeados ou removidos
422 listando number, holder_name, expiration_month, expiration_year como field is required, com a tela preenchidaTOKENIZATION_FAILEDOs name receberam o valor do appmax-form-element. Passa só o cvv, o único campo em que os dois coincidem
Failed to process payment: Failed to tokenize card. no onError, com status: 404TOKENIZATION_FAILED (status: 404)Três origens possíveis: external_id de outro ambiente, script de outro ambiente (ver o passo 1), ou um external_id que não é o da instalação — veja external-id
O submit falha antes de qualquer requisiçãoMISSING_EXTERNAL_IDinit() sem externalId
O IP nunca chega no callbackIP_FETCH_FAILEDFalha de rede na coleta; ou não há form[data-appmax-customer] no DOM (aí não há erro nenhum)
O browser navega para fora da página / 405 ao pagar—O clique caiu antes do fim do init(), que é assíncrono. Ponha o seu próprio preventDefault no form

A coluna err.code vale para o init() com objeto de opções. Na forma posicional o onError recebe só a mensagem, e apenas em falha de tokenização — as demais etapas aparecem no console.error.

Qual bundle esta página carregou?

Enquanto o script não expõe um número de versão, dá para conferir no console se a página está com o bundle atual:

javascript
document.querySelector('[data-appmax-checkout]').dataset.appmaxBound
// "true"     → bundle atual
// undefined  → bundle anterior (provavelmente em cache)

O atributo só é escrito pelo bundle atual. É a primeira coisa a checar quando o comportamento não bate com esta página.

Versão do script e cache ​

O script é publicado sempre no mesmo endereço, sem versão no caminho da URL. Duas consequências práticas:

  • O cache é de aproximadamente 4 horas. Uma correção publicada pela Appmax pode levar até esse tempo para chegar ao navegador do comprador, e não há como forçar. Em desenvolvimento, recarregue ignorando o cache.
  • Não há como fixar uma versão. Se o comportamento observado não corresponde ao que está documentado aqui, confira o marcador dataset.appmaxBound acima antes de investigar o seu código.

Mudanças recentes do script, para quem está migrando de um bundle antigo:

O que mudouAntesAgora
Token no callbackstring crua no onSuccessinalterado no onSuccess; onTokenize({ token }) é o caminho novo
Errosconsole.error e mensagens genéricasinalterado na forma posicional; { code, message, stage, status?, details? } na forma de objeto
Callbackssó o onSuccess polimórficoonIp e onTokenize dedicados, e init() aceita objeto de opções
Submit do cartãodois listeners por init() — um clique gerava duas tokenizaçõesum clique, uma tokenização
Coleta de IPdois GET /ip, dois callbacks e dois <input name="ip"> injetadosuma vez de cada, com append idempotente
Falha de fingerprintabortava a inicialização, deixando o form sem listenernão bloqueia o bind, e notifica FINGERPRINT_FAILED

Teste interativo ​

Use o playground abaixo para testar as funcionalidades da CDN diretamente no browser — sem precisar instalar nada.

Playground AppmaxJS
Coleta de IP
Chama GET /ip no endpoint usado internamente pelo appmax.min.js
Fingerprint do navegador
Sinais do dispositivo que o script consegue coletar. O valor fica no seu browser e só é enviado se você enviar — não é o token do antifraude da Appmax.
Tokenização de cartãoSandbox
Use o cartão de teste 4000000000000010 no sandbox