PaystationdocsPaystation

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.

Página de um curso com o checkout do Paystation à direita, com os dados da compradora já preenchidos
O checkout dentro da página do parceiro, com a compradora preenchida pela configuração.

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 ​

Arquitetura do checkout embutido A página do parceiro contém o elemento paystation-checkout, que cria um iframe com o checkout. O elemento manda a configuração ao iframe por postMessage e recebe eventos. O checkout fala com a API do Paystation, que notifica o servidor do parceiro por webhook. sualoja.com.br/matriculaSua páginaseu HTML, seu domínio<paystation-checkout>elemento do @quero/paystation-sdkCheckout do Paystationiframe · /embed/{client_token}/{offer}Confirmarcartão · Pix · boletoconfigpostMessageeventospaystation-*API do Paystationpaystation.quero.spacepreço, pedido, cobrançapedido epagamentoSeu servidorPOST /webhooks/paystationconfirma o pagamentowebhookAPI /v1
  • Sua página monta o elemento e passa a configuração: oferta, cupom, método, comprador e tema.
  • O elemento cria o iframe em /embed/{client_token}/{offer}, sem query string, e manda a configuração por postMessage, 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 queOnde
client_token e o id da ofertaNo painel do Paystation, em Clients e Offers.
Domínio liberadoA 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.
HTTPSSua 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ágina
bash
npm install @quero/paystation-sdk

Importe 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 Paystation

No 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ágina
Montar o checkout
js
import { 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)
vue
<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>
tsx
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ágina
js
checkout.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.

checkout.js
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 ​

CampoTipoO que faz
environment'production' | 'homolog', obrigatórioAmbiente do checkout. Veja Ambientes e chaves. Sem ele (e sem baseUrl), o elemento não monta nada e não emite evento.
clientTokenstring, obrigatórioToken do cliente no Paystation.
offerIdstring, obrigatórioId da oferta à venda.
order.externalIdstringId 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.couponstringCupom aplicado ao abrir.
order.affiliateIdstringAfiliado da venda.
buyerobjeto de textoDados do comprador: full_name, email, document, phone_number e os campos personalizados da oferta, pelo nome.
hideFilledFieldsbooleanEsconde os campos que o buyer preencheu.
theme.primary, theme.secondarystringCores em hexadecimal sem #, como 0F766E.
theme.borderRadiusstringRaio das bordas, com unidade: 4px, 0.5rem. Sem unidade, ou em outro formato, é ignorado. Padrão: 0.5rem.
minHeightnumberAltura do iframe antes de o checkout aparecer. Padrão: 600.
baseUrlstringTroca a origem do checkout. Só para desenvolvimento local.
order.saveCard, order.transactionTypeboolean, stringSó 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:

CampoAo reenviar
order.couponUm cupom diferente é aplicado; sem cupom, o anterior sai. Se não mudou, o que o comprador digitou fica.
order.paymentMethodAusente mantém o método atual. Nenhuma troca vale depois que já há um Pix ou boleto gerado.
buyerPreenche só campos vazios. Nunca sobrescreve o que o comprador digitou.
order.externalId, order.affiliateId, themeValem 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.

Checkout com nome, e-mail, CPF e celular preenchidos e o formulário do cartão
buyer preenchido
Checkout sem os campos de comprador, mostrando só país, resumo e pagamento
buyer com hideFilledFields: true

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:

js
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,
}
Checkout só com a escolha do método de pagamento, o preço e o botão de confirmar
Comprador completo com hideFilledFields: true, numa oferta sem resumo

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.

Checkout sem o resumo da compra, com dados do comprador, preço em destaque e formulário do cartão
Show Offer desligado
Checkout sem resumo e sem dados do comprador, com o boleto selecionado
Show Offer desligado, comprador completo, boleto

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.

Checkout com Pix selecionado no tema padrão, botão azul
Tema padrão
O mesmo checkout com botão e seleção em verde-petróleo e bordas mais retas
primary: '0F766E', secondary: '115E59', borderRadius: '4px'

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.

Checkout numa tela de celular, com os campos um embaixo do outro
390 px de largura

Eventos ​

Cada evento chega no elemento como paystation-<nome>, com o detalhe em event.detail.

Eventoevent.detailQuando
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.

Sequência de mensagens entre a página, o elemento e o checkoutSua página<paystation-checkout>Checkout (iframe)config = { … } e appenddefinePaystationCheckout() antescria o iframe /embed/{client}/{offer}invisível até o renderedpaystation:bootingantes da hidrataçãopaystation:readypronto para receber o configpaystation:configuresó para a origem do checkoutpaystation:rendered { height }config aplicado e pintadopaystation-readyo iframe apareceresize, paid, payment-error…durante a comprapaystation-resize, paystation-paid…em event.detail
mensagem entre janelas, por postMessage evento DOM no elemento

Se uma etapa não acontece a tempo, o elemento emite paystation-unavailable:

reasonSignifica
unresponsiveO iframe carregou, mas a página não respondeu em 3 s. Em geral, o domínio não está liberado.
timeoutO checkout não ficou pronto em 10 s, ou em 30 s se já tinha começado a carregar.
not-renderedA configuração foi enviada, mas o checkout não apareceu em 10 s.
errorA 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.

embed_originsvazio
Cabeçalho enviadoframe-ancestors 'self'
  • https://www.sualoja.com.brnenhuma origem liberada
embed_originshttps://www.sualoja.com.br
Cabeçalho enviadoframe-ancestors 'self' https://www.sualoja.com.br
  • https://www.sualoja.com.brorigem exata
  • https://sualoja.com.broutro host, sem o www
  • http://www.sualoja.com.brhttp não é https
embed_originshttps://*.sualoja.com.br
Cabeçalho enviadoframe-ancestors 'self' https://*.sualoja.com.br
  • https://cursos.sualoja.com.brsubdomínio
  • https://preview-42.sualoja.com.brsubdomínio
  • https://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óprio sualoja.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:

text
Framing 'https://paystation-client.quero.space/' violates the following Content Security Policy
directive: "frame-ancestors 'self' …".
Página com um quadro cinza e o ícone de documento quebrado do navegador no lugar do checkout
O navegador recusou o iframe: a origem não está em embed_origins.

Se a sua página tem CSP ​

Se a sua página envia Content-Security-Policy, libere o checkout em frame-src:

http
Content-Security-Policy: frame-src https://paystation-client.quero.space

Em homologação, use https://paystation-client-homolog.quero.space.

Testar em homologação ​

  1. Use environment: 'homolog' e uma oferta do cliente de homologação.
  2. Libere a origem da sua página de testes em embed_origins do cliente em homologação. Para testar na sua máquina, http://localhost:<porta> também vale.
  3. Ouça todos os eventos e confira a sequência ready → resize → paid no console.
js
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 sem www.
  • clientToken e offerId estão certos e a oferta está publicada?
  • Ouça paystation-unavailable: event.detail.reason diz 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 buyer são os do checkout: full_name, email, document, phone_number.
  • buyer não sobrescreve o que o comprador já digitou.

Mudei a configuração e nada aconteceu

  • theme, order.externalId e order.affiliateId só 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 antigoNo config
order_idorder.externalId
payment_methodorder.paymentMethod
couponorder.coupon
affiliate_idorder.affiliateId
color_primary, color_secondary, border_radiustheme.primary, theme.secondary, theme.borderRadius
full_name, email, document, phone_number e campos personalizadosbuyer
hide_fieldshideFilledFields
is_iframe, header, color_force_bgSem equivalente: o elemento define o layout.
save_card, transaction_typeorder.saveCard, order.transactionType, que só valem no upsell.