- Início
- Checkout
- Checkout embutido
Checkout embutido
Coloque o checkout do Paystation dentro de uma página sua com o elemento <paystation-checkout>, configure a compra por código e acompanhe cada etapa por eventos.
O checkout embutido é o elemento <paystation-checkout>, do pacote @quero/paystation-sdk. Você coloca o elemento na sua página e passa a configuração da compra por código. O elemento cria um iframe com o checkout do Paystation e mantém a sua página informada por eventos: quando o checkout aparece, quando a altura muda e quando o pagamento termina.

Quando usar
- O comprador deve pagar sem sair do seu site.
- Você já conhece o comprador e quer preencher os dados dele sem colocá-los na URL.
- Você quer reagir ao pagamento na página, por exemplo levando o comprador a uma tela de boas-vindas.
Para mandar o comprador a uma página do Paystation, use o link hospedado.
Como funciona
- Sua página monta o elemento e passa a configuração: oferta, cupom, método, comprador e tema.
- O elemento cria o
iframeem/embed/{client_token}/{offer}, sem query string, e manda a configuração porpostMessage, só para a origem do checkout. Nada da compra passa pela URL. - O checkout calcula preço e parcelas, coleta os dados de pagamento e cria a cobrança na API do Paystation. Os dados do cartão nunca passam pela sua página.
- Seu servidor recebe o resultado por webhook. É ele que confirma o pagamento; o evento na página serve à interface.
Antes de começar
| O que | Onde |
|---|---|
client_token e o id da oferta | No painel do Paystation, em Clients e Offers. |
| Domínio liberado | A origem da sua página em embed_origins do cliente. Veja Liberar o seu domínio. |
| Acesso ao pacote | @quero/paystation-sdk é um pacote npm de acesso restrito. Peça ao time do Paystation o acesso da sua organização no npm. |
| HTTPS | Sua página em https://. Em produção, o checkout só pode ser embutido por origens https. |
Integrar em quatro passos
1. Instale o pacote
Sua páginanpm install @quero/paystation-sdkImporte de @quero/paystation-sdk/element. Essa entrada traz só o elemento, os eventos e os tipos, sem o resto do SDK.
2. Libere o seu domínio
Painel do PaystationNo cadastro do cliente, em Checkout Config, adicione a origem da sua página a embed_origins. Origem é esquema + host, sem caminho: https://www.sualoja.com.br. Sem isso, o navegador bloqueia o iframe. As regras completas estão em Liberar o seu domínio.
3. Coloque o elemento na página
Sua páginaimport { definePaystationCheckout } from '@quero/paystation-sdk/element'
definePaystationCheckout() // registra <paystation-checkout>; no SSR não faz nada
const checkout = document.createElement('paystation-checkout')
checkout.config = {
environment: 'production', // ou 'homolog'
clientToken: 'SEU_CLIENT_TOKEN',
offerId: 'ID_DA_OFERTA',
order: { externalId: 'PEDIDO-123', paymentMethod: 'pix' },
buyer: { full_name: 'Maria Souza', email: 'maria@exemplo.com' },
}
document.querySelector('#checkout').append(checkout)<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { definePaystationCheckout, type PaystationCheckoutElement } from '@quero/paystation-sdk/element'
const checkout = ref<PaystationCheckoutElement>()
const onPaid = (event: CustomEvent) => console.log(event.detail.transactionId)
onMounted(() => {
definePaystationCheckout()
checkout.value!.config = {
environment: 'production',
clientToken: 'SEU_CLIENT_TOKEN',
offerId: 'ID_DA_OFERTA',
}
})
</script>
<template>
<!-- Marque paystation-* como custom element em vue.compilerOptions.isCustomElement -->
<paystation-checkout ref="checkout" @paystation-paid="onPaid" />
</template>import { useEffect, useRef } from 'react'
import { definePaystationCheckout, type PaystationCheckoutElement } from '@quero/paystation-sdk/element'
export function Checkout({ offerId }: { offerId: string }) {
const ref = useRef<PaystationCheckoutElement>(null)
useEffect(() => {
definePaystationCheckout()
const element = ref.current!
element.config = { environment: 'production', clientToken: 'SEU_CLIENT_TOKEN', offerId }
const onPaid = (event: Event) => console.log((event as CustomEvent).detail.transactionId)
element.addEventListener('paystation-paid', onPaid)
return () => element.removeEventListener('paystation-paid', onPaid)
}, [offerId])
return <paystation-checkout ref={ref} />
}O config é uma propriedade, não um atributo do HTML: os dados do comprador não ficam escritos na página. Você pode atribuí-lo antes ou depois de chamar definePaystationCheckout().
4. Ouça os eventos
Sua páginacheckout.addEventListener('paystation-ready', () => hideSpinner())
checkout.addEventListener('paystation-paid', (event) => {
showWelcome(event.detail.transactionId) // a confirmação vem pelo webhook
})
checkout.addEventListener('paystation-unavailable', (event) => {
showFallback(event.detail.reason)
})A lista completa está em Eventos.
Montar a configuração
Preencha o que você usa e copie o código. Campos vazios ficam fora da configuração.
import { definePaystationCheckout } from '@quero/paystation-sdk/element'
definePaystationCheckout()
const checkout = document.createElement('paystation-checkout')
checkout.config = {
environment: 'homolog',
clientToken: 'SEU_CLIENT_TOKEN',
offerId: 'ID_DA_OFERTA',
}
checkout.addEventListener('paystation-paid', (event) => {
// Confirme o pagamento pelo webhook no seu servidor; isto é só para a interface.
console.log('pago', event.detail.transactionId)
})
document.querySelector('#checkout').append(checkout)Referência do config
| Campo | Tipo | O que faz |
|---|---|---|
environment | 'production' | 'homolog', obrigatório | Ambiente do checkout. Veja Ambientes e chaves. Sem ele (e sem baseUrl), o elemento não monta nada e não emite evento. |
clientToken | string, obrigatório | Token do cliente no Paystation. |
offerId | string, obrigatório | Id da oferta à venda. |
order.externalId | string | Id do pedido no seu sistema, enviado junto com a transação. |
order.paymentMethod | 'card' | 'pix' | 'boleto' | Método selecionado ao abrir. Um método que a oferta não aceita é ignorado. |
order.coupon | string | Cupom aplicado ao abrir. |
order.affiliateId | string | Afiliado da venda. |
buyer | objeto de texto | Dados do comprador: full_name, email, document, phone_number e os campos personalizados da oferta, pelo nome. |
hideFilledFields | boolean | Esconde os campos que o buyer preencheu. |
theme.primary, theme.secondary | string | Cores em hexadecimal sem #, como 0F766E. |
theme.borderRadius | string | Raio das bordas, com unidade: 4px, 0.5rem. Sem unidade, ou em outro formato, é ignorado. Padrão: 0.5rem. |
minHeight | number | Altura do iframe antes de o checkout aparecer. Padrão: 600. |
baseUrl | string | Troca a origem do checkout. Só para desenvolvimento local. |
order.saveCard, order.transactionType | boolean, string | Só têm efeito no fluxo de upsell. Numa compra comum, o cartão é salvo quando a oferta tem upsell, é uma assinatura ou usa parcelamento inteligente. |
Cada texto aceita até 256 caracteres e cada nome de campo, até 64. Uma configuração inválida é ignorada pelo checkout: o único sinal é um paystation-unavailable com reason: 'not-rendered' depois de 10 segundos. Se ele chegar, confira os tipos acima.
Atualizar a configuração
Atribuir checkout.config de novo reenvia a configuração inteira. Mande sempre a configuração completa, não só o que mudou. Depois da primeira:
| Campo | Ao reenviar |
|---|---|
order.coupon | Um cupom diferente é aplicado; sem cupom, o anterior sai. Se não mudou, o que o comprador digitou fica. |
order.paymentMethod | Ausente mantém o método atual. Nenhuma troca vale depois que já há um Pix ou boleto gerado. |
buyer | Preenche só campos vazios. Nunca sobrescreve o que o comprador digitou. |
order.externalId, order.affiliateId, theme | Valem só na primeira configuração. |
Dados do comprador
Com buyer, o comprador chega com os dados preenchidos. Com hideFilledFields: true, os campos preenchidos somem e ele vê só o que falta.


Quando você manda todos os dados do comprador (full_name, email, document e phone_number, mais os campos personalizados da oferta), a seção "Dados para compra" some inteira e o comprador vê só o pagamento:
checkout.config = {
environment: 'production',
clientToken: 'SEU_CLIENT_TOKEN',
offerId: 'ID_DA_OFERTA',
buyer: { full_name: 'Maria Souza', email: 'maria@exemplo.com', document: '52998224725', phone_number: '11987654321' },
hideFilledFields: true,
}
Sem o resumo da compra
Quando a página já mostra o produto, como numa matrícula do Quero Bolsa, desligue Show Offer na oferta. O checkout deixa de mostrar o bloco "Detalhes da compra" e traz o preço junto dos métodos de pagamento. É uma configuração da oferta, não do config.


Os dados vão por postMessage, não pela URL: não ficam no histórico do navegador nem em logs de servidor. Mesmo assim, mande só o necessário.
Tema
theme aplica as suas cores e o raio das bordas. O resto do layout é do checkout.


Altura e celular
O elemento acompanha a altura do checkout: não é preciso definir a altura do iframe. Ele ocupa a largura do container e se adapta a telas estreitas.

Eventos
Cada evento chega no elemento como paystation-<nome>, com o detalhe em event.detail.
| Evento | event.detail | Quando |
|---|---|---|
ready | {} | O checkout apareceu, já com a configuração aplicada. |
resize | { height } | A altura do checkout mudou. O elemento já se ajusta. |
method-changed | { method } | O comprador trocou o método de pagamento. |
coupon-applied | { code? } | Um cupom foi aplicado. |
user-data-filled | { buyer } | O comprador preencheu os dados dele. |
confirm-clicked | { method? } | O comprador clicou em confirmar. |
payment-generated | { method?, coupon? } | Um Pix ou boleto foi gerado. |
payment-error | { refused, reason?, method? } | O pagamento falhou. refused: true é recusa da adquirente. |
paid | { transactionId? } | O pagamento foi aprovado. |
error | { statusCode?, message? } | A página do checkout deu erro, por exemplo uma oferta fora de venda. |
unavailable | { reason, statusCode? } | O checkout não pôde ser mostrado. Veja abaixo. |
O evento não confirma o pagamento
paystation-paid acontece no navegador e pode ser forjado ou se perder se o comprador fechar a aba. Libere o produto pelo webhook transaction_paid, no seu servidor.
Ciclo de vida
O elemento só mostra o checkout depois que a página embutida confirma que aplicou a configuração.
postMessage evento DOM no elementoSe uma etapa não acontece a tempo, o elemento emite paystation-unavailable:
reason | Significa |
|---|---|
unresponsive | O iframe carregou, mas a página não respondeu em 3 s. Em geral, o domínio não está liberado. |
timeout | O checkout não ficou pronto em 10 s, ou em 30 s se já tinha começado a carregar. |
not-rendered | A configuração foi enviada, mas o checkout não apareceu em 10 s. |
error | A página respondeu com erro antes de aparecer. statusCode diz qual. |
Os prazos só correm com a aba visível e recomeçam inteiros quando ela volta: o navegador desacelera a aba escondida e, no celular, a congela. Quem sai da aba antes de o checkout aparecer não recebe paystation-unavailable por isso. Não monte um prazo próprio por tempo de relógio.
unavailable fica até você agir. Atribuir checkout.config de novo, ou tirar e recolocar o elemento na página, monta um iframe novo: é assim que se tenta de novo.
Liberar o seu domínio
O checkout responde com Content-Security-Policy: frame-ancestors, montado a partir de embed_origins do cliente. O navegador só desenha o iframe dentro de uma página cuja origem está na lista.
vazioframe-ancestors 'self'https://www.sualoja.com.brnenhuma origem liberada
https://www.sualoja.com.brframe-ancestors 'self' https://www.sualoja.com.brhttps://www.sualoja.com.brorigem exatahttps://sualoja.com.broutro host, sem o wwwhttp://www.sualoja.com.brhttp não é https
https://*.sualoja.com.brframe-ancestors 'self' https://*.sualoja.com.brhttps://cursos.sualoja.com.brsubdomíniohttps://preview-42.sualoja.com.brsubdomíniohttps://sualoja.com.bro curinga não cobre o próprio domínio
- Origem exata:
https://www.sualoja.com.br. Sem caminho, sem barra no fim. - Curinga de subdomínio:
https://*.sualoja.com.br. Cobre os subdomínios, como previews de deploy, mas não o própriosualoja.com.br. O domínio precisa ter ao menos três partes e não pode ser um domínio compartilhado, como*.a.run.app. - Desenvolvimento local: origens
http://localhost:<porta>valem em homologação, nunca em produção. - Lista vazia: ninguém além do próprio Paystation embute o checkout.
A página de erro do checkout pode ser embutida por qualquer site, para que a sua página receba o evento error em vez de esperar o tempo esgotar.
Num domínio que não está na lista, o navegador mostra um quadro vazio no lugar do checkout, o elemento emite paystation-unavailable com reason: 'unresponsive' depois de uns 3 segundos, e o console registra o bloqueio:
Framing 'https://paystation-client.quero.space/' violates the following Content Security Policy
directive: "frame-ancestors 'self' …".
Se a sua página tem CSP
Se a sua página envia Content-Security-Policy, libere o checkout em frame-src:
Content-Security-Policy: frame-src https://paystation-client.quero.spaceEm homologação, use https://paystation-client-homolog.quero.space.
Testar em homologação
- Use
environment: 'homolog'e uma oferta do cliente de homologação. - Libere a origem da sua página de testes em
embed_originsdo cliente em homologação. Para testar na sua máquina,http://localhost:<porta>também vale. - Ouça todos os eventos e confira a sequência
ready→resize→paidno console.
const names = ['ready', 'resize', 'paid', 'payment-generated', 'payment-error', 'method-changed',
'coupon-applied', 'confirm-clicked', 'user-data-filled', 'error', 'unavailable']
for (const name of names) {
checkout.addEventListener(`paystation-${name}`, (event) => console.log(name, event.detail))
}Problemas comuns
O espaço do checkout fica vazio
- A origem da página está em
embed_origins? Compare esquema, host e porta, com e semwww. clientTokeneofferIdestão certos e a oferta está publicada?- Ouça
paystation-unavailable:event.detail.reasondiz em que etapa parou. - A sua página tem CSP? Libere o checkout em
frame-src.
O checkout aparece, mas o comprador não vem preenchido
- Os nomes em
buyersão os do checkout:full_name,email,document,phone_number. buyernão sobrescreve o que o comprador já digitou.
Mudei a configuração e nada aconteceu
theme,order.externalIdeorder.affiliateIdsó valem na primeira configuração.- Reenvie a configuração completa. Um cupom que faltar é removido; o método e o comprador ausentes ficam como estão (veja Atualizar a configuração).
Vindo do iframe por URL
O modelo antigo montava a URL do iframe com parâmetros. Dentro de um iframe, a página do checkout não lê mais esses parâmetros. Cada um tem um lugar no config:
| Parâmetro antigo | No config |
|---|---|
order_id | order.externalId |
payment_method | order.paymentMethod |
coupon | order.coupon |
affiliate_id | order.affiliateId |
color_primary, color_secondary, border_radius | theme.primary, theme.secondary, theme.borderRadius |
full_name, email, document, phone_number e campos personalizados | buyer |
hide_fields | hideFilledFields |
is_iframe, header, color_force_bg | Sem equivalente: o elemento define o layout. |
save_card, transaction_type | order.saveCard, order.transactionType, que só valem no upsell. |