Implementando o botão Apple Pay com o Appmax JS
Este guia cobre a parte do fluxo Apple 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
domain_nameprecisa estar configurado antes de qualquer coisa aqui funcionar. - Configuração de domínios para Apple Pay — publicação do arquivo
.well-known, obrigatória em todos os modelos. - 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 Apple Pay — o contrato do endpoint (
payload, mapeamento do Apple Token, modelo integrado × fluxo direto, FAQ).
Pré-requisitos rápidos
- Instalação autorizada com
domain_name(oudomain_names). - Arquivo
.well-knownpublicado no domínio. - Script (
scripts.appmax.com.br/appmax.min.jsem produção) carregado na página.
Sem os três, o botão pode até aparecer, mas a validação do domínio falha na hora de abrir a PaymentSheet.
Renderizando o botão
O appmax.min.js reconhece dois seletores no DOM. Você só precisa de um deles — não dos dois.
| Seletor | Papel |
|---|---|
.appmax-apple-pay-btn | Container que você renderiza vazio. O SDK substitui o innerHTML dele pelo botão oficial da Apple (SVG + estilos inclusos). Caminho recomendado — você não precisa desenhar o botão. |
[data-appmax-apple-pay] | O botão em si. É neste elemento que o SDK registra o click. Use esse atributo direto no seu próprio botão se preferir controlar o markup. |
Caminho mais simples — deixe o SDK desenhar o botão:
<div class="appmax-apple-pay-btn"></div>O SDK injeta algo como:
<button data-appmax-apple-pay class="applepay-button">
<span class="applepay-button__label">Pagar com</span>
<svg class="applepay-button__logo">...</svg>
</button>O botão só aparece se o dispositivo suportar Apple Pay
Fora do Safari (ou em dispositivo sem Apple Pay configurado), ApplePaySession.canMakePayments() retorna false e o SDK não ativa o botão — ele pode ficar vazio ou oculto, dependendo de como você estilizou o container. Isso é esperado, não é bug.
Ordem de carregamento: o botão precisa existir antes do init
O AppmaxScripts.init(...) procura o botão 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 Apple Pay, a consequência prática é:
- Se o container/botão só é montado depois do
init— atrás de uma rota, um passo do checkout, umv-if/condicional — o clique não vai disparar nada. Sem erro, sem log. - Se o componente que chama
initpode re-renderizar (StrictMode, Fast Refresh, um efeito mal dependenciado), lembre queinit()não é idempotente: cada chamada registra um novo listener por cima do anterior.
Regra prática para SPA: garanta que o container do botão já está no DOM no exato momento em que AppmaxScripts.init(...) é chamado — nunca o contrário.
Inicializando com os callbacks do Apple Pay
Para Apple Pay, o init exige externalId, onUpdate e onAuthorize (além de onSuccess/onError, sempre obrigatórios). 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 Apple Pay especificamente.
<div class="appmax-apple-pay-btn"></div>
<script>
// onUpdate: chamado quando a PaymentSheet abre e sempre que o usuário
// muda algo nela. Deve retornar o carrinho atual, com valores numéricos
// em reais — veja "O que o onUpdate deve retornar", abaixo.
const onUpdate = () => ({
total: 129.90,
freight: 15.00,
discount: 10.00,
installments: 1,
products: [{ name: 'Camiseta Preta', price: 62.45, quantity: 2 }],
});
// onAuthorize: chamado quando o pagamento é autorizado pelo usuário.
// Recebe o Apple Token — o cartão tokenizado, pronto para
// POST /v1/payments/apple-pay (veja o link acima para o payload completo).
// Rejeite a Promise para sinalizar falha — veja "Como sinalizar falha",
// abaixo.
const onAuthorize = async (appleToken) => {
await processarPagamento(appleToken);
};
const onSuccess = (data) => { /* ip coletado, se aplicável */ };
const onError = (err) => console.error('Erro no Apple Pay:', err);
window.AppmaxScripts.init(onSuccess, onError, externalId, onUpdate, onAuthorize);
</script>Ordem e nomes dos parâmetros
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 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).
O que o onUpdate deve retornar
O onUpdate é chamado quando a PaymentSheet abre e sempre que o usuário altera algo nela. O objeto devolvido descreve o carrinho, com valores numéricos em reais — o SDK converte esse objeto no formato que a PaymentSheet do Safari consome. Você não monta os lineItems nem formata o total.
const onUpdate = () => ({
total: 129.90,
freight: 15.00,
discount: 10.00,
installments: 1,
products: [
{ name: 'Camiseta Preta', price: 62.45, quantity: 2 },
],
});| Campo | Tipo | Obrigatório | O que aparece na PaymentSheet |
|---|---|---|---|
total | number | Sim | Linha de total. O rótulo é fixo em "Total" e não é configurável. |
products | array | Recomendado | Uma linha por item. Sem ele, a sheet mostra só o total. |
products[].name | string | Sim, se houver products | Rótulo da linha. |
products[].price | number | Sim, se houver products | Preço unitário, em reais. |
products[].quantity | number | Sim, se houver products | Quantidade. O valor exibido é price × quantity. |
freight | number | Não | Linha "Frete". Omitida quando 0 ou ausente. |
discount | number | Não | Linha "Desconto". Omitida quando 0 ou ausente. |
installments | number | Não | Não altera a PaymentSheet. Aceito por compatibilidade. |
Valores são números em reais — não centavos, nem strings
Esta é a causa mais comum de falha na abertura da PaymentSheet.
total: 12990cobra R$ 12.990,00, não R$ 129,90.total: '129.90'(string) faz o SDK lançarError: Error processing payment...antes de a sheet abrir, porque ele aplica.toFixed(2)sobre o valor.
Pelo mesmo motivo, não devolva o objeto já formatado:
// ✗ Errado — este é o formato INTERNO, que o SDK gera a partir do seu retorno
{ total: { label: 'Total', amount: { currency: 'BRL', value: '129.90' } },
displayItems: [ /* ... */ ] }
// ✓ Correto — o carrinho, com números
{ total: 129.90, products: [{ name: 'Camiseta Preta', price: 62.45, quantity: 2 }] }Como sinalizar falha no onAuthorize
O SDK decide o resultado da PaymentSheet pelo estado da Promise que o seu onAuthorize devolve:
| Sua Promise | O que o SDK faz |
|---|---|
| Resolve | completePayment(STATUS_SUCCESS) — a sheet fecha com a confirmação da Apple. |
| Rejeita | completePayment(STATUS_FAILURE) — a sheet informa a falha ao comprador. |
return false não sinaliza falha
Apenas a rejeição da Promise é lida como falha. Um try/catch em volta da chamada ao seu backend — o padrão natural para exibir a mensagem de erro na tela — faz a Promise resolver, e o comprador vê a confirmação da Apple em um pagamento que foi recusado.
Se você precisa tratar o erro, relance:
const onAuthorize = async (appleToken) => {
try {
const res = await fetch('/checkout/apple-pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ order_id: orderId, customer_id: customerId, appleToken }),
});
if (!res.ok) throw new Error('Pagamento recusado');
} catch (err) {
exibirErro(err);
throw err; // obrigatório: sem isto, a sheet fecha como sucesso
}
};Do clique ao pagamento
- O usuário clica no botão → o SDK abre a
ApplePaySession(PaymentSheet do Safari) e valida o merchant automaticamente — você não chama nenhum endpoint para isso. - Se o usuário muda frete, parcelas ou item no meio do caminho, a Apple chama seu
onUpdatepara atualizar o total exibido. - Ao confirmar (Face ID/Touch ID), o SDK chama o seu
onAuthorize(appleToken). - No
onAuthorize, com oappleTokenem mãos: crie o customer, crie a order e efetive o pagamento emPOST /v1/payments/apple-pay— o mapeamento completo deappleToken→ payload está na seção "Processar o pagamento" de Pagamento com Apple Pay.
Troubleshooting rápido
| Sintoma | Causa provável |
|---|---|
| Botão não aparece | Fora do Safari, ou dispositivo sem cartão configurado no Apple Wallet. Confirme com ApplePaySession.canMakePayments(). |
Error processing payment... no onError e a PaymentSheet nunca abre | O retorno do onUpdate está fora do contrato — normalmente valores em centavos, como string, ou o objeto já formatado. Veja "O que o onUpdate deve retornar". |
| Pagamento recusado aparece como aprovado para o comprador | O onAuthorize capturou o erro sem relançar. Veja "Como sinalizar falha no onAuthorize". |
| Clique não faz nada, sem erro | O botão foi montado depois do init (SPA) — veja "Ordem de carregamento" acima. |
init() lança Error: External ID is required... | externalId ausente ou inválido — é uma exceção síncrona, não passa por onError. |
Erro genérico do Safari ao confirmar (ex.: DOMException) | Sessão do merchant inválida — confira se o domínio tem o .well-known publicado (guia) e se o external_id é o mais recente da instalação. |
| Funciona em uma loja e não em outra, mesmo código | Domínio da segunda loja não foi informado na instalação (domain_name) ou não tem o .well-known publicado. |
A sheet nunca abriu, ou abriu e falhou?
Essa é a primeira pergunta a fazer, e ela separa dois mundos que não se misturam:
- Nunca apareceu → o problema está no navegador, antes de qualquer rede: contrato do
onUpdate, ordem de DOM/init, ouexternalIdausente. - Apareceu e falhou no meio → o problema é de validação de merchant:
domain_nameda instalação, arquivo.well-knownouexternal_id.
Não é possível testar o fluxo completo em localhost — veja Testes e Sandbox para os detalhes de ambiente.
Projeto de referência para implementação
O repositório appmaxbrasil/appstore-demo-php contém uma implementação de referência da integração completa de Apple Pay — instalação via App Store, renderização do botão e callbacks do appmax.js no checkout, e a chamada ao endpoint de pagamento — em PHP puro e JavaScript vanilla, sem framework. Use-o como base de estudo e ponto de partida para a própria implementação, não como código de produção.
O passo a passo do Apple Pay do repositório traz um link direto para a linha de código correspondente a cada etapa descrita neste guia.