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:
- Instalação do aplicativo — o
external_idda instalação é o que oinit()recebe. Diferente do Apple Pay, não hádomain_namea configurar. - Appmax JS — assinatura completa do
init, coleta de IP e tokenização de cartão (este guia assume que você já leu a seção "Como usar" de lá). - Pagamento com Google Pay — o contrato do endpoint (payload, respostas, testes, FAQ).
- Implementando o botão Apple Pay com o Appmax JS — se a loja oferece as duas carteiras, o que muda quando elas convivem.
Pré-requisitos
- Instalação autorizada, com o
external_idem mãos. - Script do Appmax JS carregado na página, no build do ambiente certo — a URL muda entre sandbox e produção, veja Ambientes.
- 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.
| Seletor | Papel |
|---|---|
[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. |
<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, umv-if/condicional — o iframe nunca é criado. Sem erro, sem log. - O
onUpdateé chamado no carregamento do iframe. Se o carrinho mudar depois (cupom, frete), chameinit()de novo ou recarregue a página — veja "O que oonUpdatedeve 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.
<!-- 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.
const onUpdate = () => ({
total: 150.90,
});| Campo | Tipo | Obrigatório | O que o Google Pay faz com ele |
|---|---|---|---|
total | number | Sim | Valor exibido na folha do Google, em BRL. |
products, freight, discount, installments | — | Não | Ignorados 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.
| Carteira | Quando o onAuthorize roda | O que o retorno faz |
|---|---|---|
| Apple Pay | Com a PaymentSheet aberta | Resolve → sucesso na sheet; rejeita → falha na sheet. Veja Como sinalizar falha. |
| Google Pay | Com a folha já fechada | Nada. 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.
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
- O usuário clica no botão → o SDK abre a folha do Google dentro do iframe da Appmax, com o
totaldo últimoonUpdate. Você não chama nenhum endpoint para isso. - Ao confirmar, o Google devolve o
paymentMethodDatae a folha fecha. O SDK chama o seuonAuthorize(paymentMethodData)e, em seguida, lê oonUpdatede novo. - No
onAuthorize, com o objeto em mãos: crie o customer, crie a order e efetive o pagamento emPOST /v1/payments/google-pay— o payload completo está na seção "Processar o pagamento" de Pagamento com Google Pay. - 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
| Sintoma | Causa provável |
|---|---|
| Botão não aparece | Apple 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 console | Checkout dentro de um iframe sem allow="payment". |
| A folha abre com valor errado | total 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 loja | data-appmax-merchant-name ausente no [data-appmax-checkout]; o SDK caiu no <title> ou no host. |
400 Invalid Google Pay token no endpoint | paymentMethodData 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.