Checkout no iframe
Incorpore o checkout no seu site, com as suas cores, e reaja ao pagamento sem redirecionar.
Em vez de mandar o cliente para a url do link, você pode abrir o checkout dentro da sua página
com um <iframe>. O cliente paga sem sair do seu site, e o checkout avisa a sua página por
postMessage quando carregou, quando muda de altura e quando o pagamento termina.
Veja funcionando
A loja de exemplo storetest.xpayz.us tem o código completo desta
página: cole o id de um link de sandbox, troque as cores e acompanhe os eventos ao vivo.
Como funciona
- 1Crie o linkPOST /links devolve a url do checkout.
- 2Abra no iframeAcrescente ?embed=1 (e suas cores) à url e use como src.
- 3Escute os eventosAjuste a altura no resize e reaja a success e error.
Crie o link
Crie o link no seu servidor, como em qualquer integração, e entregue a url para a página:
curl -X POST "$XPAYZ_URL/links" \
-H "Content-Type: application/json" \
-H "X-Secret-ID: $XPAYZ_SECRET_ID" \
-H "X-Secret-Key: $XPAYZ_SECRET_KEY" \
-d '{ "description": "Pedido #123", "amount": "100.00", "external_ref": "pedido-123" }'{
"id": "b3e1c2d4-5f67-4890-9abc-1234567890ab",
"url": "https://checkout.xpayz.us/p/b3e1c2d4-5f67-4890-9abc-1234567890ab"
}Nunca crie o link pelo navegador: isso exporia a sua X-Secret-Key.
Monte a URL do iframe
Use a url devolvida pela API, sem montar o domínio na mão, e acrescente os parâmetros:
| Parâmetro | Valores | Para quê |
|---|---|---|
embed | 1 | Obrigatório. Ativa o modo iframe: layout sem moldura e envio dos eventos. |
lang | pt, en, es | Idioma do checkout. Padrão no iframe: en. |
primary | hex de 6 dígitos, sem # | Cor dos botões e destaques. Ex.: 7c3aed. |
bg | hex de 6 dígitos, sem # | Cor de fundo. Fundos escuros ativam o tema escuro automaticamente. |
const src = new URL(link.url);
src.searchParams.set('embed', '1');
src.searchParams.set('lang', 'pt');
src.searchParams.set('primary', '7c3aed');
src.searchParams.set('bg', 'ffffff');
// https://checkout.xpayz.us/p/b3e1...?embed=1&lang=pt&primary=7c3aed&bg=ffffffCores inválidas são ignoradas e o checkout usa as cores padrão.
Coloque o iframe na página
<iframe
id="xpayz-checkout"
src="https://checkout.xpayz.us/p/b3e1...?embed=1&lang=pt"
allow="payment"
style="width: 100%; height: 560px; border: 0; transition: height .15s ease"
></iframe>allow="payment"libera os meios de pagamento do navegador dentro do iframe.- Comece com uma altura fixa (
560px). A altura real chega depois, pelo eventoresize.
Escute os eventos
const frame = document.getElementById('xpayz-checkout');
const checkoutOrigin = new URL(frame.src).origin;
window.addEventListener('message', (event) => {
// 1. Só aceite mensagens do iframe do checkout
if (event.origin !== checkoutOrigin) return;
if (event.source !== frame.contentWindow) return;
const data = event.data;
if (!data || data.source !== 'paggo-checkout') return;
switch (data.event) {
case 'resize':
// 2. Acompanhe a altura do conteúdo: sem barra de rolagem nem espaço em branco
if (typeof data.height === 'number') frame.style.height = `${data.height}px`;
break;
case 'ready':
// checkout carregado: esconda o seu loading
break;
case 'success':
// pagamento recebido: mostre a confirmação
break;
case 'error':
// data.code: payment_failed | link_expired | link_paid
break;
}
});Eventos
Toda mensagem é um objeto com source: "paggo-checkout" e um event:
event | Quando | Campos |
|---|---|---|
ready | O checkout carregou. | paymentId |
resize | A altura do conteúdo mudou. Chega várias vezes. | height (px, número) |
success | O pagamento foi recebido. | paymentId, amount, currency |
error | O pagamento não pode ser concluído. | paymentId, code, reason (opcional) |
paymentId é o id do link (o mesmo devolvido em POST /links), não o ID de um pagamento.
amount não vem em links sem valor fixo.
Códigos de error
code | Significado | Sugestão |
|---|---|---|
payment_failed | O pagamento foi recusado ou cancelado. | Para uma nova tentativa, recarregue o iframe (veja abaixo). |
link_expired | O link passou da expiration. | Crie um link novo. |
link_paid | O link já foi pago. | Mostre o pedido como pago ou crie outro link. |
ready, success e error são enviados uma vez por carregamento do iframe. Depois de um
error, se o cliente tentar de novo dentro do mesmo iframe, o success dessa nova tentativa não
chega. Para uma nova tentativa, recarregue o iframe (por exemplo, frame.src = frame.src). O webhook
continua valendo para qualquer tentativa.
{ "source": "paggo-checkout", "event": "ready", "paymentId": "b3e1c2d4-5f67-4890-9abc-1234567890ab" }
{ "source": "paggo-checkout", "event": "resize", "height": 812 }
{ "source": "paggo-checkout", "event": "success", "paymentId": "b3e1c2d4-...", "amount": "100", "currency": "BRL" }
{ "source": "paggo-checkout", "event": "error", "paymentId": "b3e1c2d4-...", "code": "payment_failed" }A altura chega depois
A comunicação com o iframe é assíncrona: no load a altura certa ainda não existe. O checkout mede
o próprio conteúdo e manda resize sempre que ele muda (ao trocar de Pix para cartão, ao mostrar o
QR Code, ao exibir um erro). Por isso:
- Sempre trate o
resize. Sem ele o iframe fica cortado ou com espaço em branco. - Dê uma altura inicial (
560px) para a página não "pular" enquanto a primeira medida não chega. - Não use
height: 100vhnemscrolling="no"sem tratar oresize.
Segurança
success não é confirmação de pagamento
Mensagens do navegador servem para a experiência (mostrar "pagamento recebido"). Para liberar o
pedido, confirme no servidor pelo webhook event.completed ou consultando
os pagamentos do link.
- Valide
event.origineevent.sourceantes de ler a mensagem. O camposource: "paggo-checkout"pode ser enviado por qualquer página, então ele sozinho não prova nada. - Não use valores da mensagem em
innerHTML. UsetextContent.
Exemplo em React
import { useEffect, useRef, useState } from 'react';
type CheckoutEvent =
| { event: 'ready'; paymentId: string }
| { event: 'resize'; height: number }
| { event: 'success'; paymentId: string; amount?: string; currency?: string }
| { event: 'error'; paymentId: string; code: 'payment_failed' | 'link_expired' | 'link_paid'; reason?: string };
export function XPayZCheckout({
url,
lang = 'pt',
primary,
onSuccess,
onError,
}: {
url: string; // a `url` devolvida por POST /links
lang?: 'pt' | 'en' | 'es';
primary?: string; // hex sem #
onSuccess?: (e: Extract<CheckoutEvent, { event: 'success' }>) => void;
onError?: (e: Extract<CheckoutEvent, { event: 'error' }>) => void;
}) {
const frame = useRef<HTMLIFrameElement>(null);
const [height, setHeight] = useState(560);
const src = new URL(url);
src.searchParams.set('embed', '1');
src.searchParams.set('lang', lang);
if (primary) src.searchParams.set('primary', primary);
useEffect(() => {
const origin = src.origin;
function onMessage(event: MessageEvent) {
if (event.origin !== origin || event.source !== frame.current?.contentWindow) return;
const data = event.data as CheckoutEvent & { source?: string };
if (data?.source !== 'paggo-checkout') return;
if (data.event === 'resize') setHeight(data.height);
if (data.event === 'success') onSuccess?.(data);
if (data.event === 'error') onError?.(data);
}
window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
}, [src.origin, onSuccess, onError]);
return (
<iframe
ref={frame}
src={src.toString()}
allow="payment"
style={{ width: '100%', height, border: 0, transition: 'height .15s ease' }}
/>
);
}Testando
Use um link criado no sandbox e os dados de Simular pagamentos dentro
do iframe. Com 4200 0000 0000 0000 você recebe success; com 4200 0000 0000 0001, error com
payment_failed.