Checkout
Como criar uma venda com POST /sales, mapear produtos e variações, aplicar desconto e consultar a venda.
Cada checkout é uma venda iniciada para um comprador (CPF). Você cria a venda com POST /sales e recebe um link único para o comprador concluir a compra.
Criar o checkout
curl -X POST https://app-staging.principia.services/checkout/v4/sales \
-H "Api-Token: SEU_TOKEN" \
-H "Content-Type: application/json" \
-d @venda.json{
"cpf": "48096807544",
"externalSaleId": "aef2828a-e9eb-4714-b0b5-aa3edf15f31d",
"checkoutPriceInCents": 100000,
"products": [
{
"productSku": "01946620-fba3-74c7-8473-38337b3f08b1",
"productName": "Light Saber 101",
"variantSku": "01946621-1706-7cc4-8e7e-67dbd3d9ab6e",
"variantName": "Light Saber 101",
"variantPriceInCents": 100000
}
],
"buyer": {
"name": "Darth Vader",
"phone": "71999999999",
"email": "darth.vader@empire.com",
"zipCode": "04548004",
"street": "Av. Dr. Cardoso de Melo",
"number": "1340",
"district": "Vila Olimpia",
"city": "São Paulo",
"complement": "Cj 11"
}
}Campos
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
cpf | texto | sim | CPF do aluno para quem é a venda. Precisa ser um CPF válido. |
externalSaleId | texto | não | ID da venda no seu sistema, para vínculo. Recomendamos até 40 caracteres. |
checkoutPriceInCents | inteiro | sim | Preço total exibido no checkout, em centavos. Deve ser menor ou igual à soma dos produtos e ficar dentro dos limites de valor configurados para a sua conta. |
upfrontInCents | inteiro | não | Valor da entrada, em centavos. Veja Valor da entrada. |
returnUrl | texto | não | Aceito e guardado, mas não é usado hoje: o comprador não é redirecionado para essa página ao concluir o checkout. Para redirecionar, veja Embutir o checkout. |
products | lista | sim | Produtos e variações da venda (veja abaixo). Precisa ter ao menos um item. |
buyer | objeto | sim | Dados do comprador. O objeto é obrigatório, mas os campos dentro dele são opcionais. |
Cada item de products (todos obrigatórios):
| Campo | Tipo | Descrição |
|---|---|---|
productSku | texto | ID do produto no seu sistema. Recomendamos até 40 caracteres. |
productName | texto (8 a 99) | Nome do produto. Usado só quando o produto ainda não existe. Veja as regras em Produtos e variações. |
variantSku | texto | ID da variação no seu sistema. Recomendamos até 40 caracteres. |
variantName | texto | Nome da variação. Recomendamos até 99 caracteres. |
variantPriceInCents | inteiro | Preço da variação no checkout, em centavos. |
Campos de buyer:
| Campo | Tipo | Descrição |
|---|---|---|
name | texto | Nome completo. |
phone | texto | Telefone com DDD, sem DDI e sem formatação. Todo telefone é tratado como +55 (Brasil). |
email | texto | E-mail. |
zipCode | texto | CEP, só números. |
street | texto | Logradouro. |
number | texto | Número. |
district | texto | Bairro. |
city | texto | Cidade. |
complement | texto | Complemento. |
Tamanhos são recomendações
A API não valida o tamanho dos campos de buyer, do externalSaleId nem dos SKUs. Use os limites sugeridos acima e dados bem formados: é isso que aparece preenchido para o comprador no checkout, e ele pode corrigir o que estiver errado. A exceção é o productName, que tem regras próprias (veja abaixo).
Valor da entrada
upfrontInCents define a entrada paga pelo comprador. As regras:
- O mínimo é o maior entre o percentual de entrada configurado para a sua conta (aplicado sobre a soma dos
variantPriceInCents) e R$ 30,00. - A entrada precisa ser menor que a soma dos
variantPriceInCents. - Sem o campo, o PrincipiaPay usa o mínimo.
Um valor abaixo do mínimo, ou igual ou maior que a soma dos produtos, devolve 400 (veja Erros).
Resposta
{
"checkoutUrl": "https://pay-staging.principia.net/checkout/web-development-school?classes=44099&saleId=31397dea-d328-4135-9619-a6ed4ca7713f",
"externalSaleId": "aef2828a-e9eb-4714-b0b5-aa3edf15f31d",
"saleId": 1582050
}checkoutUrl: redirecione o comprador para essa URL (ou embuta o checkout na sua página).externalSaleId: o seu ID, se enviado.saleId: ID da venda no PrincipiaPay. Guarde para consultar a venda e casar com os eventos de webhook.
Produtos e variações
Todo checkout precisa de um Produto e de uma Variação. O produto é a entidade pai e a variação é a filha. No painel do parceiro, o produto aparece como Curso e a variação como Turma.
Você não precisa cadastrar nada antes: ao criar a venda, o PrincipiaPay encontra ou cadastra produtos e variações pelos seus SKUs:
Produto (curso): é identificado só pelo productSku. Se já existir um produto com esse SKU, ele é usado; senão, é criado com o productName enviado.
Variação (turma): é identificada pelo variantSku e pelo variantPriceInCents, dentro do produto. Se já existir uma variação com esse SKU e esse preço, ela é usada; senão, é criada uma nova com o variantName enviado.
Nomes não são atualizados
Enviar outro productName ou variantName para um SKU que já existe não muda o nome cadastrado nem cria cadastro novo: o checkout continua mostrando o nome antigo. Mudar o variantPriceInCents cria uma variação nova com o preço novo; a variação antiga continua cadastrada. Para renomear, use o painel do parceiro.
A mesma venda pode ter mais de um produto, e também mais de uma variação do mesmo produto (uma entrada em products para cada variação).
Regras para cadastrar produto e variação
Quando o SKU ainda não existe, o cadastro automático aplica estas regras. Se alguma falhar, a venda não é criada:
- Nome do produto (
productName): ao menos 8 caracteres, com ao menos uma letra (a-z), e no máximo 99 caracteres (o tamanho do cadastro; um nome maior faz a venda falhar). - Nome único por conta: não pode existir outro produto seu com o mesmo nome, sem diferenciar maiúsculas de minúsculas. Dois
productSkudiferentes com o mesmoproductNamefazem a segunda venda falhar comCurso já existe.. Use um nome distinto para cada SKU. - Preço da variação (
variantPriceInCents): dentro dos limites de valor configurados para a sua conta.
As regras de nome e de preço devolvem 400 com a mensagem em português, no formato { "error": { "name", "message" } }:
{
"error": {
"name": "BodyPropertyError",
"message": "Curso já existe."
}
}No checkout, o comprador vê o nome do produto e o da variação:

Como mapear o seu catálogo
Seu sistema pode ter outra estrutura. A variação é o cadastro que define o preço base; considere isso ao mapear.
Produtos com ofertas. Seu sistema tem produtos (pai) e cada produto tem várias ofertas, que definem o preço. Mapeie produto para Produto e oferta para Variação. Se a oferta não tiver descrição visível ao comprador, repita o nome do produto na variação.
"products": [
{
"productSku": "ABC",
"productName": "Light Saber 101",
"variantSku": "123",
"variantName": "Light Saber 101",
"variantPriceInCents": 100000
}
]Só produtos. Seu sistema tem apenas o produto, que já define o preço. Mapeie 1 para 1: o mesmo produto vira Produto e Variação.
"products": [
{
"productSku": "ABC",
"productName": "Light Saber 101",
"variantSku": "ABC",
"variantName": "Light Saber 101",
"variantPriceInCents": 100000
}
]Dados do comprador
Envie o que tiver em buyer: os dados enviados já aparecem preenchidos no checkout. O que faltar é pedido ao comprador durante o checkout.
O CPF não muda
O comprador pode revisar e alterar os dados pessoais no checkout, mas o CPF vinculado à venda não pode ser alterado.
Embutir o checkout (iframe)
Você pode abrir a checkoutUrl dentro de um <iframe> na sua página. Dentro do iframe, o checkout esconde o rodapé e parte do cabeçalho do PrincipiaPay. Acrescente estes parâmetros à checkoutUrl para ajustar o comportamento:
| Parâmetro | Funciona fora do iframe? | O que faz |
|---|---|---|
redirectUrl | não | URL (codificada com encodeURIComponent) para onde a janela principal (a sua página, não o iframe) é levada cerca de 5 segundos depois de a compra dar certo. |
support | não | Com support=true, mostra um botão flutuante de WhatsApp do suporte do PrincipiaPay (só em telas médias e grandes). |
hideBoletoAsUpfront | não | Com hideBoletoAsUpfront=true, esconde o bloco do boleto bancário na página de pagamento da entrada (fatura). |
userData | sim | JSON (codificado com encodeURIComponent) com dados para pré-preencher o formulário, por exemplo CPF, fullName, email, phone e zipcode. Dados que o PrincipiaPay já tem da venda prevalecem. |
https://pay.principia.net/checkout/sua-empresa?classes=44099&saleId=...&redirectUrl=https%3A%2F%2Fsuaempresa.com.br%2FobrigadoQuando o redirecionamento acontece:
- Cartão de crédito: depois do pagamento aprovado ou em análise.
- Pix e boleto: não redireciona enquanto o pagamento está pendente. No Pix, o checkout confere o pagamento periodicamente e redireciona quando ele é confirmado com a página ainda aberta.
- Página de pagamento da entrada (fatura): redireciona quando a fatura aparece como paga.
O redirecionamento não confirma a venda
O comprador pode fechar a página antes dos 5 segundos, e boleto e Pix costumam ser pagos depois. Use os eventos de webhook para saber se a venda foi paga.
Venda com desconto
Para dar desconto, informe em checkoutPriceInCents o valor já descontado. Ele precisa ser menor ou igual à soma dos variantPriceInCents e continuar dentro dos limites de valor configurados para a sua conta.
Exemplo com 20% de desconto: dois produtos, de R$ 1.000,00 e R$ 500,00 (total de R$ 1.500,00), vendidos por R$ 1.200,00.
{
"cpf": "48096807544",
"checkoutPriceInCents": 120000,
"externalSaleId": "aef2828a-e9eb-4714-b0b5-aa3edf15f31d",
"products": [
{
"productSku": "01946620-fba3-74c7-8473-38337b3f08b1",
"productName": "Light Saber 101",
"variantSku": "01946621-1706-7cc4-8e7e-67dbd3d9ab6e",
"variantName": "Light Saber 101",
"variantPriceInCents": 100000
},
{
"productSku": "d3878e08-a625-4d11-9e09-1d8d8bce92d9",
"productName": "Jedi Explorer",
"variantSku": "009df0d89-3907-42b0-85db-9df7d775cd6e",
"variantName": "Beginner Level",
"variantPriceInCents": 50000
}
],
"buyer": { "name": "Darth Vader", "email": "darth.vader@empire.com" }
}O checkout mostra o valor de cada produto e o percentual de desconto:

Consultar a venda
| Rota | Quando usar |
|---|---|
GET /sales/purchase/{saleId} | Você tem o saleId devolvido na criação. |
GET /sales/external-purchase/{externalSaleId} | Você tem só o seu ID. Como o mesmo externalSaleId pode estar em mais de uma venda, a resposta é uma lista com as até 50 vendas mais recentes com esse ID. A ordem dos itens não é garantida: ordene pelo saleId ou pelo que fizer sentido no seu lado. |
As duas devolvem uma lista de vendas (a consulta por saleId devolve uma lista com um item). Se nenhuma venda da sua conta for encontrada, a resposta é 400 (não 404):
{
"name": "ValidationError",
"errors": [{ "name": "SALE-001", "message": "Sale not found" }]
}SKU em maiúsculo na resposta
Na resposta, os produtos vêm com productSKU e variantSKU (SKU em maiúsculo), diferente do corpo de criação (productSku e variantSku).
Com venda encontrada, o formato é este:
[
{
"checkoutUrl": "https://pay-staging.principia.net/checkout/web-development-school?classes=44099&saleId=31397dea-d328-4135-9619-a6ed4ca7713f",
"externalSaleId": "aef2828a-e9eb-4714-b0b5-aa3edf15f31d",
"status": "Efetivado",
"saleId": 1582050,
"cpf": "48096807544",
"checkoutPriceInCents": 120000,
"products": [
{
"productSKU": "01946620-fba3-74c7-8473-38337b3f08b1",
"productName": "Light Saber 101",
"variantSKU": "01946621-1706-7cc4-8e7e-67dbd3d9ab6e",
"variantName": "Light Saber 101",
"variantPriceInCents": 100000
}
],
"buyer": {
"name": "Darth Vader",
"phone": "71999999999",
"email": "darth.vader@empire.com",
"zipCode": "04548004",
"street": "Av. Dr. Cardoso de Melo",
"number": "1340",
"district": "Vila Olimpia",
"city": "São Paulo",
"complement": "Cj 11"
}
}
]Os valores de status estão em Status. O status pode vir null.