Skip to content

Implementando o botão Google Pay com o Appmax JS ​

Este guia cobre a parte do fluxo Google Pay que roda no navegador, via appmax.min.js: como o botão é renderizado, o contrato de DOM que o script exige e como conectar os callbacks até o pagamento. Para o restante do fluxo, veja:

Pré-requisitos

  1. Instalação autorizada, com o external_id em mãos.
  2. Script do Appmax JS carregado na página, no build do ambiente certo — a URL muda entre sandbox e produção, veja Ambientes.
  3. Página servida por HTTPS.

Não há domínio a cadastrar nem arquivo .well-known a publicar: a folha do Google abre em um iframe da Appmax, cujo domínio já está aprovado.


Renderizando o botão ​

O appmax.min.js reconhece uma âncora para o Google Pay e dois atributos opcionais ao redor dela.

SeletorPapel
[data-appmax-google-pay]Âncora. O SDK insere um iframe com o botão oficial do Google imediatamente antes dela e a oculta. O conteúdo da âncora nunca é exibido — serve apenas para marcar a posição e a altura.
[data-appmax-google-pay-wrapper]Opcional. Ocultado junto com a âncora quando a carteira não está disponível — útil para esconder um rótulo ou separador.
data-appmax-merchant-name (em [data-appmax-checkout])Nome exibido ao comprador na folha do Google. Na ausência, o SDK utiliza o <title> da página ou o host, e registra um aviso no console.
html
<div data-appmax-checkout data-appmax-merchant-name="Nome da loja">
  <div data-appmax-google-pay-wrapper>
    <button data-appmax-google-pay style="height: 48px; width: 100%"></button>
  </div>
</div>

A altura do botão vem da âncora

O iframe herda a altura da âncora, com mínimo de 48 px. Uma âncora sem altura produz um botão cortado — o SDK garante o mínimo, mas não adivinha o tamanho que o seu layout pede. Defina a altura na âncora.

Quando o botão aparece

O botão é exibido em Chrome, Firefox, Safari, Edge, Opera e UC Browser, em desktop e em dispositivos móveis, para um comprador autenticado em uma conta Google com cartão salvo.

Onde o Apple Pay está disponível (Safari em macOS ou iOS com cartão no Apple Wallet), o SDK oculta o Google Pay e exibe o Apple Pay em seu lugar. A loja não escolhe qual carteira aparece — a seleção é automática. Se o navegador não suporta a Google Pay API, a âncora e o wrapper permanecem ocultos. Isso é esperado, não é bug.

Checkout dentro de iframe

Se o seu checkout já roda dentro de um iframe, esse iframe precisa do atributo allow="payment". O SDK cria o iframe do botão com allow="payment <origem>", mas o navegador só honra a permissão se cada iframe acima dele também a conceder. Sem isso a folha não abre, e o erro não indica a causa.


Ordem de carregamento: o botão precisa existir antes do init ​

O AppmaxScripts.init(...) procura a âncora uma única vez, no momento em que roda, e não observa mudanças no DOM depois disso — o mesmo contrato descrito em "Contrato de DOM" na página do Appmax JS. Para o botão do Google Pay, a consequência prática é:

  • Se a âncora só é montada depois do init — atrás de uma rota, um passo do checkout, um v-if/condicional — o iframe nunca é criado. Sem erro, sem log.
  • O onUpdate é chamado no carregamento do iframe. Se o carrinho mudar depois (cupom, frete), chame init() de novo ou recarregue a página — veja "O que o onUpdate deve retornar".

Regra prática para SPA: garanta que a âncora já está no DOM no exato momento em que AppmaxScripts.init(...) é chamado — nunca o contrário. ​

Inicializando com os callbacks do Google Pay ​

Para Google Pay, o init exige externalId, onUpdate e onAuthorize (além de onIp/onError, sempre recomendados). A referência completa de cada parâmetro está em Appmax JS → Inicializar o AppmaxScripts; aqui o foco é como esses três se conectam ao fluxo de Google Pay especificamente.

html
<!-- Gatilho da coleta de IP: obrigatório, e precisa existir antes do init -->
<form data-appmax-customer hidden></form>

<div data-appmax-checkout data-appmax-merchant-name="Nome da loja">
  <div data-appmax-google-pay-wrapper>
    <button data-appmax-google-pay style="height: 48px; width: 100%"></button>
  </div>
</div>

<script>
  // onUpdate: chamado ao carregar o iframe e após cada tentativa concluída.
  // Deve retornar o carrinho atual, com valores numéricos em reais — veja
  // "O que o onUpdate deve retornar", abaixo. O Google Pay usa só `total`.
  const onUpdate = () => ({
    total: 150.90,
    freight: 0,
    discount: 0,
    installments: 1,
    products: [{ name: 'Produto', price: 150.90, quantity: 1 }],
  });

  // onAuthorize: chamado quando o comprador confirma na folha do Google.
  // Recebe o paymentMethodData já desembrulhado — o objeto inteiro que vai em
  // payment_data.google_pay.payment_data do POST /v1/payments/google-pay.
  // A folha já fechou neste ponto: trate aprovação e recusa na sua tela —
  // veja "Como o onAuthorize se comporta", abaixo.
  const onAuthorize = async (paymentMethodData) => {
    await processGooglePayPayment(paymentMethodData);
  };

  // A coleta de IP é obrigatória em toda integração, e só acontece se
  // houver um form[data-appmax-customer] no DOM — o form vazio acima serve.
  const checkoutData = {
    customer: {}
  };

  const onIp = ({ ip }) => {
    checkoutData.customer.ip = ip;
  };
  const onError = ({ code, message, stage }) => console.error('Erro no Google Pay:', code, stage, message);

  window.AppmaxScripts.init({ externalId, onIp, onError, onUpdate, onAuthorize });
</script>

Se você usar a forma posicional, a ordem importa

init(onSuccess, onError, externalId, onUpdate, onAuthorize) — nessa ordem. Trocar onUpdate e onAuthorize de posição, ou escrever onAutorize, faz o SDK tratar o parâmetro errado como função de callback. O objeto de opções não tem esse risco, e é a forma recomendada.

A referência completa do init — as duas formas, todos os callbacks e o contrato do onError — vive em Appmax JS. Esta página cobre só o que é específico do Google Pay.

O externalId é o mesmo external_id retornado com HTTP 200 na etapa de instalação do aplicativo — sem ele, init lança exceção síncrona (veja o aviso em Appmax JS). AppCheckout.init(...) é um alias de window.AppmaxScripts.init(...).

Erros chegam no onError como { code: 'GOOGLE_PAY_FAILED', stage: 'google_pay', message }. Diferente da tokenização, aqui não há status nem details: a falha vem da folha do Google, não de uma resposta da API — veja O contrato do onError.


O que o onUpdate deve retornar ​

O onUpdate é chamado ao carregar o iframe e após cada tentativa concluída — autorização ou erro —, nunca no clique. O objeto devolvido é o mesmo contrato do Apple Pay: o carrinho, com valores numéricos em reais. Para o Google Pay, apenas total é utilizado; os demais campos são aceitos por compatibilidade com o Apple Pay e com a tokenização.

javascript
const onUpdate = () => ({
  total: 150.90,
});
CampoTipoObrigatórioO que o Google Pay faz com ele
totalnumberSimValor exibido na folha do Google, em BRL.
products, freight, discount, installments—NãoIgnorados pelo Google Pay. Preencha se a mesma página também oferece Apple Pay, que os exibe na PaymentSheet.

Valores são números em reais — não centavos, nem strings

O onUpdate é um contrato do Appmax JS, não da API. O SDK aplica .toFixed(2) sobre o total e envia o resultado ao Google como uma string com duas casas decimais, por exemplo, "150.90".

total: 15090 exibe R$ 15.090,00 na folha, e não R$ 150,90.

total: '150,90' (string com vírgula) vira NaN, que o SDK converte para 0 — a folha recebe 0.00, não o valor pretendido. Uma string com ponto ('150.90') é convertida e funciona, mas o contrato é número: não dependa disso.

Isso não conflita com a regra geral da API, em que valores monetários são inteiros em centavos.

O pedido continua sendo criado em centavos por meio de POST /v1/orders. Já o corpo de POST /v1/payments/google-pay não possui um campo de valor: o montante cobrado é sempre o valor definido no pedido.

A moeda utilizada é sempre BRL (Real brasileiro).

O total exibido é o da última chamada

O SDK lê o onUpdate ao carregar o iframe e de novo após cada autorização ou erro — não quando o comprador apenas fecha a folha. Se o carrinho mudar (cupom aplicado, frete recalculado), chame init() novamente ou recarregue a página antes de o comprador clicar.


Como o onAuthorize se comporta ​

Diferente do Apple Pay, o SDK não usa o resultado da Promise do onAuthorize para decidir nada: a folha do Google já está fechada quando o callback é invocado, e o valor retornado (ou a rejeição) é ignorado.

CarteiraQuando o onAuthorize rodaO que o retorno faz
Apple PayCom a PaymentSheet abertaResolve → sucesso na sheet; rejeita → falha na sheet. Veja Como sinalizar falha.
Google PayCom a folha já fechadaNada. A aprovação ou recusa é tratada na sua interface, a partir da resposta de POST /v1/payments/google-pay.

Na prática: envie o paymentMethodData ao seu backend, aguarde a resposta do endpoint e mostre o resultado na sua tela. Um try/catch aqui é apenas a sua decisão de UI, sem efeito colateral na folha.

javascript
const onAuthorize = async (paymentMethodData) => {
  try {
    const res = await fetch('/checkout/google-pay', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ order_id: orderId, customer_id: customerId, paymentMethodData }),
    });
    if (!res.ok) throw new Error('Pagamento recusado');
    showSuccess(); // o backend respondeu só o status — nada sensível chega aqui
  } catch (err) {
    showError(err); // a folha já fechou; a mensagem é sua
  }
};

Repasse o paymentMethodData inteiro

O objeto chega já desembrulhado e deve ir ao backend sem alteração. Não aplique JSON.parse ao tokenizationData.token (é uma string, e o parse é feito pela Appmax) e não remova campos de info — a Appmax os utiliza para validar a transação e compor o comprovante. A forma do objeto e o mapeamento para o payload estão em Pagamento com Google Pay.

Integração via backend

O seu endpoint (/checkout/google-pay, no exemplo) deve devolver ao front-end apenas o resultado necessário para a interface: status da operação (aprovado ou recusado) e, opcionalmente, uma mensagem. Dados como credenciais, pay_reference, holder_document_number e respostas completas da API devem permanecer no backend.


Do clique ao pagamento ​

  1. O usuário clica no botão → o SDK abre a folha do Google dentro do iframe da Appmax, com o total do último onUpdate. Você não chama nenhum endpoint para isso.
  2. Ao confirmar, o Google devolve o paymentMethodData e a folha fecha. O SDK chama o seu onAuthorize(paymentMethodData) e, em seguida, lê o onUpdate de novo.
  3. No onAuthorize, com o objeto em mãos: crie o customer, crie a order e efetive o pagamento em POST /v1/payments/google-pay — o payload completo está na seção "Processar o pagamento" de Pagamento com Google Pay.
  4. Se o comprador fechar a folha sem confirmar, nada é chamado: trate como desistência e deixe o botão disponível para um novo clique.

Troubleshooting ​

SintomaCausa provável
Botão não apareceApple Pay disponível no aparelho (a precedência oculta o Google Pay), navegador sem suporte à Google Pay API, ou âncora montada depois do init (SPA).
Botão aparece cortado ou vazioÂncora sem altura. Defina height na âncora; o mínimo aplicado pelo SDK é 48 px.
A folha não abre e nada aparece no consoleCheckout dentro de um iframe sem allow="payment".
A folha abre com valor erradototal em centavos ou como string no onUpdate, ou carrinho alterado sem novo init() — veja "O que o onUpdate deve retornar".
Folha exibe o nome errado da lojadata-appmax-merchant-name ausente no [data-appmax-checkout]; o SDK caiu no <title> ou no host.
400 Invalid Google Pay token no endpointpaymentMethodData alterado antes do envio (JSON.parse no token ou campos removidos). Repasse o objeto como chegou.
init() lança Error: External ID is required...externalId ausente ou inválido. É uma exceção síncrona, que não passa por onError — envolva a chamada em try/catch.

Em sandbox você não precisa de cartão real

Assim que a folha abre, o Google exibe sua própria lista de cartões de teste, em qualquer navegador suportado — inclusive desktop. Use script, API e credenciais de sandbox em conjunto; os detalhes estão em Testes e Sandbox na página do endpoint.