Dashboard do Seller
Endpoints que alimentam o "Studio" do artista: storeStatus (publicar/despublicar loja), stats (métricas), wallet (saldo) e payouts (saques via PIX).
Conceito em 30 segundos
- No momento em que o pedido é criado, a plataforma congela no
OrderItemoplatformFeePercent,artistCommissionPercente oartistEarningsCents(valor absoluto já calculado). Mudar oProductTypedepois não afeta pedidos antigos. - Quando o pedido é pago (
paymentStatus=approved), o backend cria 1SellerPayoutItemporOrderItemcomstatus=held. - Quando o item é entregue (
fulfillmentStatus=delivered), oSellerPayoutItemvirareleasable(sacável). - O admin agrupa vários
SellerPayoutItemreleasableem umSellerPayout(batch), executa o PIX fora do sistema, e marca o batch comopaidcom otransactionId. - Se o item é devolvido antes de ser pago:
SellerPayoutItemvirareversed. Se já foi pago, admin trata manualmente.
1. Store Status (toggle de visibilidade)
O SellerProfile tem um campo storeStatus que controla se a loja é visível publicamente.
| Valor | Descrição |
|---|---|
unpublished | Loja não visível (padrão ao criar) |
published | Loja visível publicamente |
Leitura: retornado em GET /profiles/seller/me como storeStatus.
Atualização:
PATCH /profiles/seller/me
{ "storeStatus": "published" }
:::tip Use como toggle principal
Botão "Publicar loja" / "Despublicar loja" no dashboard deve mudar apenas este campo. O seller pode voltar a unpublished a qualquer momento — é reversível.
:::
2. Stats (métricas agregadas)
GET /sellers/me/stats?from=2026-01-01&to=2026-03-31
Filtra por paid_at — só conta pedidos com paymentStatus=approved.
| Param | Obrigatório | Descrição |
|---|---|---|
from | Não | Data início ISO 8601 |
to | Não | Data fim ISO 8601 |
Response
{
"totalEarningsCents": 125000,
"orderCount": 15,
"itemsSold": 42
}
Admin espelhado: GET /sellers/{sellerProfileId}/stats?from=&to=.
3. Wallet (saldo)
GET /sellers/me/wallet
Derivado das linhas SellerPayoutItem — uma por OrderItem.
Response
{
"totalEarnedCents": 250000,
"heldCents": 40000,
"releasableCents": 50000,
"pendingPayoutCents": 10000,
"paidCents": 150000,
"failedCents": 0,
"availableCents": 50000
}
Pipeline de dinheiro
:::info failedCents é sempre 0
Quando um batch é marcado como failed, os items voltam imediatamente para releasable (com payoutId = null) — eles não entram em um status failed. O campo failedCents existe por compatibilidade, mas você pode ignorar no frontend.
:::
| Campo | Descrição |
|---|---|
totalEarnedCents | Soma histórica de tudo que não foi reversed |
heldCents | Pedido approved mas item ainda não delivered |
releasableCents | Item delivered, ainda não incluído em um batch |
pendingPayoutCents | Item já incluído em SellerPayout com status pending/scheduled |
paidCents | Item em batch marcado como paid |
failedCents | Sempre 0 — reservado. Item de batch failed volta direto para releasable |
availableCents | Alias de releasableCents — valor que pode entrar em novo batch |
Admin espelhado: GET /sellers/{sellerProfileId}/wallet.
4. Configuração de Payout (PIX)
O seller precisa configurar sua chave PIX para que o admin consiga gerar batches em seu nome.
Campos no SellerProfile
| Campo | Tipo | Descrição |
|---|---|---|
pixKey | string (nullable) | Chave PIX |
pixKeyType | string (nullable) | cpf, cnpj, email, phone, random |
Atualizar
PATCH /profiles/seller/me
{
"pixKey": "123.456.789-00",
"pixKeyType": "cpf"
}
Validações:
pixKeyepixKeyTypedevem ser enviados juntos (erro se enviar um sem o outro)pixKeyTypedeve ser um dos 5 valores aceitos
payoutComplete no perfil
O GET /profiles/seller/me retorna payoutComplete: true/false. É true quando todos os critérios são atendidos:
pixKeypreenchidopixKeyTypepreenchidoemaildo usuário preenchidowhatsappdo usuário preenchido
{
"pixKey": "123.456.789-00",
"pixKeyType": "cpf",
"email": "seller@email.com",
"whatsapp": "+5511999999999",
"payoutComplete": true
}
:::tip Use como checklist no frontend
Mostre payoutComplete como um indicador visual ("Pronto para receber" / "Complete seu cadastro") e liste os campos faltantes. Ajuda o seller a saber o que falta antes do primeiro saque.
:::
Snapshot no batch
Quando o admin cria um batch (POST /payouts), os campos pixKey e pixKeyType são gravados como snapshot no SellerPayout. Mudanças posteriores no SellerProfile não afetam batches já criados.
5. Ciclo de vida do Payout Item
A plataforma rastreia o que é devido ao seller através de SellerPayoutItem (uma linha por OrderItem). O valor é copiado de OrderItem.artistEarningsCents — já snapshotado no momento da criação do pedido, com os percentuais do ProductType daquele momento.
:::info Snapshot é imutável
SellerPayoutItem.amountCents é copiado de artistEarningsCents e permanece imutável mesmo que os percentuais do ProductType mudem depois.
:::
heldPedido approved, item não deliveredreleasableautoreversedautoreleasableItem delivered, pronto para batchpaidadminreversedautopaidterminalBatch marcado como pagoreleasableadminreversedterminalItem devolvido/cancelado antes do pagamento:::danger Status possíveis do SellerPayoutItem: held · releasable · paid · reversed
Não existe status failed no item. Quando o admin marca um batch como failed, os items do batch voltam para releasable com payoutId = null — prontos para entrar em um novo batch.
:::
:::warning Reversão após paid não é automática
Se a order for devolvida depois do item ter sido pago, o admin resolve manualmente (log é gerado). O sistema não reverte pagamentos já liquidados por PIX.
:::
Listar payout items do seller
GET /sellers/me/payout-items?status=releasable&skip=0&limit=100
Query opcional status: held, releasable, paid, reversed.
Response
{
"items": [
{
"id": "uuid",
"sellerProfileId": "uuid",
"orderId": "uuid",
"orderItemId": "uuid",
"payoutId": null,
"amountCents": 2500,
"status": "releasable",
"heldAt": "2026-04-10T10:00:00Z",
"releasedAt": "2026-04-15T18:00:00Z",
"paidAt": null,
"reversedAt": null,
"reason": null,
"createdAt": "2026-04-10T10:00:00Z"
}
],
"total": 1,
"skip": 0,
"limit": 100
}
Admin espelhado: GET /sellers/{sellerProfileId}/payout-items?status=releasable.
6. Payouts (batches)
Batch = SellerPayout, um registro agregando N SellerPayoutItems pagos juntos via PIX. Apenas admin cria batches.
Listar batches
GET /sellers/me/payouts?skip=0&limit=100
Response
{
"payouts": [
{
"id": "uuid",
"sellerProfileId": "uuid",
"amountCents": 50000,
"status": "pending",
"periodStart": "2026-04-01",
"periodEnd": "2026-04-08",
"scheduledFor": null,
"pixKey": "seller@email.com",
"pixKeyType": "email",
"transactionId": null,
"failureReason": null,
"paidAt": null,
"createdAt": "2026-04-08T12:00:00Z"
}
],
"total": 1,
"skip": 0,
"limit": 100
}
Status do batch: pending → paid | failed. (scheduled é reservado para uso futuro com cron.)
Admin espelhado: GET /sellers/{sellerProfileId}/payouts.
Admin — criar batch
POST /payouts
{
"sellerProfileId": "uuid",
"payoutItemIds": ["uuid", "uuid"],
"pixKey": "seller@email.com",
"pixKeyType": "email",
"scheduledFor": "2026-04-20"
}
Validações:
- Todos
payoutItemIdsdevem existir, pertencer aosellerProfileId, estar emreleasablee não já vinculados a outro batch pixKey/pixKeyTypenormalmente vêm doSellerProfileatual (snapshot no batch)- Batch só pode conter items de um único seller —
POST /payoutsvalida que todos os items pertencem aosellerProfileIdinformado - Não existe endpoint cross-seller (tipo
GET /payoutslistando batches de todos os sellers). A UI admin deve partir da lista de sellers → entrar no seller → listar payouts daquele seller (GET /sellers/{id}/payouts)
Response: SellerPayoutDetailResponse (batch + items).
Admin — detalhe do batch
GET /payouts/{payoutId}
Retorna batch + items incluídos.
Admin — marcar batch como pago
PATCH /payouts/{payoutId}/mark-paid
{ "transactionId": "E123456789-pix-id" }
Cascata: todos os items do batch → paid, paidAt = now. Batch → paid.
Admin — marcar batch como falho
PATCH /payouts/{payoutId}/mark-failed
{ "failureReason": "Pix recusado pelo banco" }
Cascata: items voltam para releasable (payoutId=null) e podem ser re-batchados. Batch → failed com failureReason gravado.
:::danger Batch paid não pode ser marcado como failed
Se o batch já está em paid, o mark-failed retorna 400 (ValueError). Use mark-failed apenas em batches pending.
:::
7. Endpoints admin (espelhados)
| Endpoint | Descrição |
|---|---|
GET /sellers/{id}/stats?from=&to= | Stats de qualquer seller |
GET /sellers/{id}/wallet | Wallet de qualquer seller |
GET /sellers/{id}/payouts | Batches de qualquer seller |
GET /sellers/{id}/payout-items?status=... | Payout items de qualquer seller |
POST /payouts | Criar batch |
GET /payouts/{id} | Detalhe do batch com items |
PATCH /payouts/{id}/mark-paid | Marcar como pago |
PATCH /payouts/{id}/mark-failed | Marcar como falho |
O {id} é o sellerProfileId (UUID). Todos exigem role admin.
Snapshot de % na criação do pedido
O OrderItem snapshota no momento do pedido:
| Campo snapshot | Fonte |
|---|---|
platformFeePercent | ProductType.platform_fee_percent |
artistCommissionPercent | ProductType.artist_royalty_percent |
artistEarningsCents | Calculado (ver fórmula abaixo) |
unitPriceCents / baseCostCents | SellerProductVariant.price_cents / ProductVariant.base_cost_cents |
Fórmula exata (tudo em centavos, aritmética inteira com floor division):
platform_fee_cents = price_cents * platform_fee_percent // 100
profit_cents = price_cents - base_cost_cents - platform_fee_cents
artist_earnings = profit_cents * artist_commission_percent // 100 (0 se profit <= 0)
:::warning Item com artistEarningsCents ≤ 0 não gera SellerPayoutItem
Se o preço é muito baixo (ex: custo + taxa ≥ preço), o ganho do artista é 0 ou negativo. Nesses casos o backend não cria linha de payout — você não vai ver esse OrderItem no extrato. Útil debugar quando item aparece no pedido mas não no wallet.
:::
:::tip Por que o snapshot importa
Mudar os percentuais do ProductType depois não afeta pedidos antigos nem payouts. Isso protege o seller de ter os ganhos recalculados retroativamente, e mantém o histórico auditável. O SellerPayoutItem.amountCents copia o artistEarningsCents e também permanece imutável.
:::
Payout Hooks (para quem trabalha no backend)
As transições de SellerPayoutItem são disparadas automaticamente por hooks vinculados a mutações de order/shipment. Conhecer a tabela ajuda a entender quem cria/muda cada item e como não duplicar a lógica ao adicionar novos caminhos de mutação.
| Gatilho | Hook | O que faz |
|---|---|---|
update_payment_status(approved) | create_payout_items_for_approved_order | Cria items held para cada OrderItem com artistEarningsCents > 0 |
update_payment_status(refunded / cancelled) | reverse_payout_items_for_order | Marca items como reversed |
_sync_fulfillment_from_shipment → delivered | release_payout_items_for_order | Move items de held → releasable |
update_item_fulfillment(delivered) | release_payout_items_for_order | Idem (caso fulfillment manual por item) |
update_item_fulfillment(returned) | reverse_payout_item | Marca o item específico como reversed |
cancel_order_with_refund | via update_payment_status | Reversão em cascata |
:::info Idempotência
create_payout_items_for_approved_order verifica via constraint UNIQUE(orderItemId) se já existe payout item — protege contra webhook Asaas duplicado. Seguro para retry.
:::
Regras de negócio
- Item com
artistEarningsCents <= 0não geraSellerPayoutItem(nada a pagar — útil debugar quando item aparece no pedido mas não no wallet) - Item já
paidque depois virareturnednão é revertido automaticamente — apenas log de warning; admin resolve manual (ajuste negativo ou recuperação do valor com o seller) - Batch com status
paidnão pode ser marcado comofailed(backend retorna400comValueError) - Batch com items de sellers diferentes: proibido —
POST /payoutsvalida que todos os items pertencem aosellerProfileIdinformado pixKeyno batch pode ser nula — admin preenche se o seller ainda não configurou, mas só deve marcar comopaidapós efetuar o PIX
Limitações conhecidas (MVP)
- Não há Pix-out automático via Asaas — admin executa o PIX manualmente e marca o batch como
paid. Roadmap: funçãoexecute_payout(payout)chamandoasaas.create_transfer(...)com callback SellerPayout.status=scheduledestá reservado mas não usado pelo código — útil para cron de agendamento futuro- Devolução de item já pago exige intervenção manual — no futuro pode-se gerar
SellerPayoutItemcomamountCentsnegativo compensando o saldo OrderShipmenté 1:1 comOrder. Se uma ordem tem items de múltiplos sellers e o shipment viradelivered, todos os items viramreleasablesimultaneamente — correto, mas pressupõe que a entrega foi efetiva para todos- Não existe endpoint cross-seller de batches (tipo
GET /payoutslistando todos). Admin navega de seller → payouts - Frontend não pode disparar
execute_payout— só admin, via painel interno, após efetuar o PIX manualmente
Arquivos principais
| Arquivo | Conteúdo |
|---|---|
app/models/seller_payout.py | SellerPayout, SellerPayoutItem, enums |
app/crud/payout.py | Lifecycle + wallet + admin batch ops |
app/crud/order.py | Hooks que chamam crud/payout |
app/routers/sellers.py | Wallet, list payouts/items (read-only) |
app/routers/payouts.py | Admin: create / mark-paid / mark-failed |
app/schemas/payout.py | Schemas Pydantic |
alembic/versions/x5y6z7a8b9c0_add_seller_payout_items.py | Migration (cria tabela + reset de dados) |