Checkout
Crie links de checkout a partir do carrinho da sua loja, com variante e quantidade.
Visão Geral
A API de Checkout cria o link de pagamento do seu carrinho. O comprador abre o link, paga com Pix, cartão ou boleto, e você recebe o pedido por webhook.
Todo valor monetário da API é em centavos inteiros, em campos terminados em Cents. 15000 é R$ 150,00.
Para produtos do catálogo, use lineItems: o preço vem do catálogo e não do seu payload, então ninguém consegue forjar o valor cobrado.
Endpoints
| Método | Endpoint | Auth | Descrição |
|---|---|---|---|
POST | /api/sdk/checkout/create-link | 🔒 SDK | Criar link de checkout |
GET | /api/sdk/checkout/:token | 🌐 Público | Abre a sessão (é a rota que a página de checkout usa) |
Não use GET /api/sdk/checkout/:token para conferir pagamento. É a rota que abre a página de checkout: enquanto a sessão está pendente, cada chamada conta como um novo início de checkout nos pixels de marketing da loja. E o status completed dela quer dizer pedido criado, não pago. Quem confirma pagamento é o webhook order.paid, ou a API de Pedidos.
Criar Link de Checkout
POST /api/sdk/checkout/create-linkOs itens vão por uma de três formas. Mandar mais de uma, ou nenhuma, devolve 400 VALIDATION_ERROR.
| Forma | Quando usar | Preço |
|---|---|---|
lineItems | Carrinho com produtos do catálogo, com variante e quantidade | Do catálogo |
productIds | Atalho: uma unidade de cada produto, sem variante | Do catálogo |
items | O que não está no catálogo, como uma consultoria | Informado por você |
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
lineItems | array | ❌* | Linhas do carrinho, do catálogo |
lineItems[].productId | string | ✅ | Id do produto |
lineItems[].variantId | string | ❌ | Id da variante. Obrigatório quando o produto tem variantes; omita quando não tem |
lineItems[].quantity | integer | ❌ | Unidades, de 1 a 9999 (padrão: 1) |
productIds | string[] | ❌* | Uma unidade de cada id. Recusa produto com variantes |
items | array | ❌* | Itens fora do catálogo |
items[].title | string | ✅ | Título do item (max 200) |
items[].priceCents | integer | ✅ | Preço unitário em centavos inteiros. 15000 é R$ 150,00 |
items[].quantity | integer | ❌ | Quantidade (padrão: 1, max: 9999) |
items[].image | string | ❌ | URL da imagem |
items[].description | string | ❌ | Descrição (max 1000) |
items[].sku | string | ❌ | SKU do item |
items[].variantTitle | string | ❌ | Rótulo exibido no resumo, como P / Azul |
successUrl | string | ❌ | URL de redirecionamento depois de finalizar |
cancelUrl | string | ❌ | URL de redirecionamento se o comprador desistir |
customer | object | ❌ | Pré-preencher email, name e phone |
currency | string | ❌ | Moeda (padrão: BRL) |
expiresIn | integer | ❌ | Expiração em minutos (5 a 10080, padrão 1440) |
shippingCents | integer | ❌ | Frete em centavos inteiros |
couponCode | string | ❌ | Código de cupom (max 50) |
metadata | object | ❌ | Seus dados (max 4KB). Volta em todo aviso de pedido |
Um mesmo checkout aceita até 50 linhas. É limite do carrinho: o catálogo da loja não tem limite de produtos.
Regras do carrinho (lineItems)
- Produto com variantes: mande o
variantIdda variante escolhida, e ele precisa ser uma variante daquele produto. Sem ele,400 VARIANT_REQUIRED, com os ids válidos emvariantIds. Variante de outro produto devolve400 VARIANT_NOT_FOUND. - Produto sem variantes: vai sem
variantId. - Estoque nunca impede uma venda. O link é criado mesmo com estoque zerado, e o pagamento baixa o estoque mesmo que ele fique negativo.
Exemplo
curl -X POST "https://api.plasmacheckout.com/api/sdk/checkout/create-link" \
-H "X-PLASMA-Public-Key: pk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "X-PLASMA-Secret-Key: sk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"lineItems": [
{ "productId": "cmu1splash0001", "variantId": "cmu1var10ml", "quantity": 2 },
{ "productId": "cmu1caneca0001", "quantity": 1 }
],
"successUrl": "https://meusite.com/obrigado",
"cancelUrl": "https://meusite.com/carrinho",
"metadata": { "pedidoDaLoja": "Y-123" }
}'import { PlasmaSDK } from '@plasmacheckout/sdk';
const plasma = new PlasmaSDK({
apiKey: process.env.PLASMA_PUBLIC_KEY!,
secretKey: process.env.PLASMA_SECRET_KEY!,
});
const { data } = await plasma.checkout.createLink({
lineItems: [
{ productId: 'cmu1splash0001', variantId: 'cmu1var10ml', quantity: 2 },
{ productId: 'cmu1caneca0001', quantity: 1 },
],
successUrl: 'https://meusite.com/obrigado',
cancelUrl: 'https://meusite.com/carrinho',
metadata: { pedidoDaLoja: 'Y-123' },
});
// Guarde data.checkoutToken no seu pedido antes de redirecionar.
console.log(data.checkoutUrl);const response = await fetch(
'https://api.plasmacheckout.com/api/sdk/checkout/create-link',
{
method: 'POST',
headers: {
'X-PLASMA-Public-Key': process.env.PLASMA_PUBLIC_KEY,
'X-PLASMA-Secret-Key': process.env.PLASMA_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
lineItems: [
{ productId: 'cmu1splash0001', variantId: 'cmu1var10ml', quantity: 2 },
{ productId: 'cmu1caneca0001', quantity: 1 },
],
successUrl: 'https://meusite.com/obrigado',
cancelUrl: 'https://meusite.com/carrinho',
}),
}
);
const { data } = await response.json();
console.log(data.checkoutUrl); // URL para redirecionar o compradorResposta (201 Created)
{
"data": {
"id": "cmswltiti0005rv05rpvt7an1",
"checkoutUrl": "https://pay.minhaloja.com.br/c/c08b6109-4c32-46ac-9a8a-bb72b7ec71de",
"checkoutToken": "c08b6109-4c32-46ac-9a8a-bb72b7ec71de",
"status": "pending",
"expiresAt": "2026-10-05T12:00:00.000Z",
"items": [
{ "productId": "cmu1splash0001", "variantId": "cmu1var10ml", "title": "Body Splash Lavanda",
"priceCents": 4990, "quantity": 2, "image": "https://cdn.minhaloja.com/lavanda.jpg", "variantTitle": "10ml" },
{ "productId": "cmu1caneca0001", "variantId": null, "title": "Caneca",
"priceCents": 3990, "quantity": 1, "image": null, "variantTitle": null }
],
"totals": {
"subtotalCents": 13970,
"shippingCents": 0,
"discountCents": 0,
"totalCents": 13970
},
"currency": "BRL",
"customer": null,
"urls": {
"checkout": "https://pay.minhaloja.com.br/c/c08b6109-4c32-46ac-9a8a-bb72b7ec71de",
"success": "https://meusite.com/obrigado",
"cancel": "https://meusite.com/carrinho"
},
"usedFallback": false
},
"message": "Checkout link created successfully"
}O que fazer com a resposta
- Guarde
data.checkoutTokenjunto do pedido na sua base. Todo aviso de pedido traz esse token emdata.checkoutToken: é ele que liga o pagamento a este carrinho. - Redirecione para
data.checkoutUrlexatamente como veio. O Plasma já resolve o domínio próprio da loja e, enquanto o SSL dele não está ativo, usa o domínio de fallback (usedFallback: true). Nunca remonte a URL a partir do token.
Fluxo do Checkout
Criar o link
O seu backend cria o link com as chaves de API e guarda o checkoutToken.
Redirecionar
O comprador vai para o checkoutUrl e paga.
Pedido criado
Saem order.created e checkout.completed. O pedido existe, sem pagamento ainda: um Pix gerado e nunca pago também passa por aqui.
Pagamento
Sai order.paid quando o gateway confirma. É o único aviso que libera o pedido. Se o gateway cancelar ou devolver o pagamento, sai order.cancelled ou order.refunded. Pedido que nunca recebe order.paid não foi pago, como um cartão recusado ou um Pix que ninguém pagou.
Volta para o seu site
O comprador volta para a successUrl, às vezes antes do order.paid. A página de obrigado lê o estado do pedido na sua base.
Expiração
O campo expiresIn é especificado em minutos:
| Valor | Tempo |
|---|---|
| Mínimo | 5 minutos |
| Padrão | 1440 minutos (24 horas) |
| Máximo | 10080 minutos (7 dias) |
O valor é em minutos, não em segundos. 60 = 1 hora, 1440 = 24 horas.
Moedas Suportadas
| Código | Moeda |
|---|---|
BRL | Real Brasileiro |
USD | Dólar Americano |
EUR | Euro |
ARS | Peso Argentino |
CLP | Peso Chileno |
COP | Peso Colombiano |
MXN | Peso Mexicano |
PEN | Sol Peruano |
UYU | Peso Uruguaio |
Integração Frontend
Botão de Compra
function BotaoFinalizar({ carrinho }) {
const [loading, setLoading] = useState(false);
async function finalizar() {
setLoading(true);
// carrinho: [{ productId, variantId, quantity }]
const response = await fetch('/api/criar-checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ lineItems: carrinho }),
});
const { checkoutUrl } = await response.json();
window.location.href = checkoutUrl;
}
return (
<button onClick={finalizar} disabled={loading}>
{loading ? 'Carregando...' : 'Finalizar compra'}
</button>
);
}Backend (Next.js API Route)
import { NextResponse } from 'next/server';
import { PlasmaSDK, PlasmaError } from '@plasmacheckout/sdk';
const plasma = new PlasmaSDK({
apiKey: process.env.PLASMA_PUBLIC_KEY!,
secretKey: process.env.PLASMA_SECRET_KEY!,
});
export async function POST(request: Request) {
const { lineItems } = await request.json();
try {
const { data } = await plasma.checkout.createLink({
lineItems,
successUrl: `${process.env.NEXT_PUBLIC_URL}/obrigado`,
cancelUrl: `${process.env.NEXT_PUBLIC_URL}/carrinho`,
});
await salvarPedidoPendente({ checkoutToken: data.checkoutToken, lineItems });
return NextResponse.json({ checkoutUrl: data.checkoutUrl });
} catch (error) {
if (error instanceof PlasmaError) {
return NextResponse.json({ code: error.code, message: error.message }, { status: error.statusCode });
}
throw error;
}
}Nunca exponha suas API Keys no frontend. Use uma rota no backend para intermediar.
Erros Comuns
| Código | Erro | Solução |
|---|---|---|
400 | VALIDATION_ERROR | Mande só uma forma de itens (lineItems, productIds ou items) |
400 | VARIANT_REQUIRED | O produto tem variantes: mande o variantId (os válidos vêm em variantIds) |
400 | VARIANT_NOT_FOUND | A variante não é daquele produto, ou o produto não tem variantes |
400 | PRODUCTS_NOT_FOUND | Id inexistente, inativo ou de outra loja (lista em missingIds) |
400 | INVALID_LINE_ITEMS | Linha malformada; a mensagem diz qual |
400 | TOO_MANY_ITEMS | Mais de 50 linhas no mesmo checkout |
400 | INVALID_EXPIRATION | expiresIn deve ser entre 5 e 10080 minutos |
400 | INVALID_CURRENCY | Use uma moeda suportada |
400 | INVALID_METADATA | metadata deve ser objeto com no máximo 4KB |
Não existe erro de estoque esgotado: estoque nunca impede uma venda.
Webhooks Disparados
Uma venda feita no checkout gera estes avisos, todos com o pedido inteiro em data (itens com variante, valores em centavos, o seu checkoutToken e o seu metadata):
order.createdecheckout.completed: o pedido nasceu no checkout, ainda sem pagamento.order.paid: o gateway confirmou o pagamento (Pix, cartão ou boleto). Libere o pedido.order.cancelled: o gateway informou o cancelamento do pagamento.order.refunded: o pagamento foi devolvido.
Cartão recusado e Pix que ninguém pagou não geram aviso próprio: o pedido simplesmente nunca recebe order.paid. Para saber o status atual, consulte a API de Pedidos.
Webhooks
Formato do aviso, assinatura e retentativas