RapidoPago — Design (plataforma de cardápios digitais)
RapidoPago — Design
Data: 2026-08-07 · Status: aprovado em brainstorm, aguardando plano de implementação
SaaS self-serve multi-tenant de cardápios digitais: lojista cria conta, monta a loja, cadastra produtos e recebe pedidos com pagamento online. Cada loja vive num subdomínio wildcard próprio com SEO forte.
Decisões de produto
| Decisão | Escolha |
|---|---|
| Modelo | SaaS self-serve (qualquer pessoa cria loja) |
| Escopo MVP | Vitrine + carrinho + pedido + pagamento online |
| Modalidades | Delivery (zonas/taxas) + Retirada. Mesa/QR fica pra depois |
| Domínios | {slug}.rapidopago.* (wildcard) no MVP; domínio próprio do lojista = fase 2 (resolução por hostname já pronta) |
| Customização | Temas prontos (3-5 layouts) + tokens de marca (cores, logo, capa, fontes, ordem de seções) |
| Pagamento do pedido | Pix via Woovi + cartão via Stripe Connect Express + "na entrega" opcional. Dinheiro vai direto pra loja, sem application fee |
| Monetização | Assinatura mensal via Stripe Billing (Grátis limitado / Pro com trial) |
| Hosting | VPS dedicada 147.93.1.106 (não é o Hermes), Docker Compose. Credencial em C:\Users\User\.cardapio-vps-credentials |
Pendência aberta: TLD do domínio ainda não comprado (rapidopago.com.br? .com?) — confirmar e registrar na GoDaddy antes do setup DNS. No spec, rapidopago.* = o domínio que for escolhido.
Arquitetura
Monorepo Bun workspaces em C:\Users\User\rapidopago:
apps/web— Next.js 16 (App Router). Serve marketing (rapidopago.*), admin (app.rapidopago.*) e cardápios públicos ({slug}.rapidopago.*) num app só. Middleware lêHost→ resolve tenant → rewrite pra/_sites/{slug}.apps/api— Elysia (Bun). Pedidos, webhooks Stripe/Woovi, SSE do painel de pedidos, gating de plano.packages/db— Drizzle + Postgres (schema único, tenant =store_idem toda query; sem RLS, API é a única camada de acesso).packages/shared— tipos + cálculo de carrinho/preço/taxa (usado no web e na api).
Alternativas descartadas: Astro pro público (3 apps, tokens duplicados, ganho de perf não paga a complexidade — ISR já entrega Lighthouse 90+); tudo em Next API routes (webhooks + SSE + jobs desajeitados; padrão da casa é Elysia).
Modelo de dados
- users — lojista, auth via Better Auth (email+senha; Google depois)
- stores — tenant:
slugúnico (= subdomínio),custom_domain(null até fase 2), nome, WhatsApp, endereço,themejsonb (tema + tokens),hoursjsonb,delivery_configjsonb (zonas/bairros com taxa, pedido mínimo), formas de pagamento aceitas - categories — nome, sort, ativa
- products — nome, descrição, preço, imagem, ativo/esgotado, sort
- option_groups / options — adicionais/variações reutilizáveis (single/multi, min/max, delta de preço)
- orders — código curto, cliente jsonb (nome, WhatsApp, endereço), tipo delivery/retirada, items jsonb snapshot (preço congelado), subtotal, taxa, total,
payment_method,payment_status(awaiting/paid/failed/refunded),order_status(awaiting_payment → received → confirmed → preparing → out_for_delivery|ready → done | canceled) - subscriptions — store ↔ Stripe Billing (customer, subscription, plano, status, trial_ends_at)
- payment_accounts — por loja: Stripe Connect account id + status onboarding; conta/subconta Woovi
Wildcard, roteamento e SEO
DNS/TLS: *.rapidopago.* A record → VPS (GoDaddy API já disponível). Cert wildcard Let's Encrypt DNS-01 (certbot + GoDaddy API, renovação cron). nginx: wildcard vhost → web; /api/* e /webhooks/* → api; /uploads/* estático.
Roteamento: middleware Next por Host. Slugs reservados: app, www, api, admin, mail, etc. Domínio próprio (fase 2) = mesmo lookup por hostname, zero refactor.
SEO por loja:
- ISR com
revalidateTag(store)ao editar — HTML estático no primeiro byte - Metadata dinâmica (
{Loja} — Cardápio | Delivery em {Cidade}), OG image da capa - JSON-LD
Restaurant+hasMenu→MenuSection/MenuItemcom preços sitemap.xml/robots.txtpor subdomínio, canonical por hostname- Página de produto com URL própria (
/{categoria}/{produto-slug}) pra long-tail next/image+ sharp local (WebP/AVIF)
Checkout e pagamentos
- Carrinho client-side (Zustand + localStorage por loja). Checkout: nome + WhatsApp; delivery valida zona/taxa; retirada só dados.
- Pedido criado antes de pagar (
awaiting_payment), expira em ~15min sem confirmação. - Pix (Woovi): charge via API → QR + copia-e-cola → webhook confirma (+ poll leve). Subconta/App ID da loja registrado no onboarding de pagamentos.
- Cartão (Stripe Connect Express): onboarding hosted (KYC no Stripe), destination charge na conta da loja, Payment Element no checkout.
- Assinatura (Stripe Billing): Checkout + Customer Portal, trial no Pro, gating por plano.
- Webhooks (Elysia):
/webhooks/stripee/webhooks/woovi, idempotentes (event id gravado) → atualizampayment_status→ SSE pro painel. Reconciliação por poll 5min nosawaiting_paymentcobre webhook perdido.
Admin (app.rapidopago.*)
Onboarding wizard: conta → nome+slug (preview ao vivo, valida unicidade/reservados) → tema+marca → primeiros produtos → WhatsApp/endereço/horários → publicado. Pagamentos online ativam depois sem travar o wizard.
Gestão: CRUD categorias/produtos com drag-sort, toggle ativo/esgotado, duplicar produto, grupos de opcionais reutilizáveis, upload de imagem (sharp resize/WebP, nginx serve), horários por dia (fora do horário = banner "fechado, abre às X"), zonas de entrega, pedido mínimo.
Customização: temas + tokens com preview ao vivo (mesmo componente do cardápio real). Salvar → revalidateTag(store).
Painel de pedidos: SSE, som + badge em pedido novo, colunas por status com transição de 1 clique, botão WhatsApp do cliente, cancelamento com motivo + estorno via API (Stripe refund / Woovi refund).
Gating: grátis = limites (nº produtos etc.); inadimplente = cardápio continua no ar, admin bloqueia edição.
Erros
- Copy nunca técnica: cliente vê "não conseguimos confirmar seu pagamento, tente de novo"; erro cru só em log de servidor.
- Pedido pago com webhook falho → reconciliação marca pago retroativo.
- Loja inexistente/suspensa → 404 amigável (não vaza existência de slug).
Infra e deploy
- Docker Compose na VPS:
web(Next standalone),api(Elysia/Bun),postgres,redis(cache, pub/sub do SSE, rate limit),nginx. - Primeiro acesso: SSH key, desabilitar password auth, ufw (22/80/443), fail2ban.
- Backup:
pg_dumpdiário + tar de uploads, retenção 7 dias (offsite depois). - Deploy: push
main→ GitHub Action → SSH →docker compose up -d --build.
Testes
- Vitest
packages/shared: cálculo de carrinho com opcionais, taxa por zona, pedido mínimo, expiração. - Vitest
apps/api: webhooks idempotentes (duplicado, fora de ordem), gating de plano. - agent-browser smoke: cardápio → carrinho → checkout Pix mock.
- Pós-MVP: subagent auditor no padrão dos outros projetos.
Decisões pós-launch (2026-08-08)
- Redesign: design system "comanda de cozinha" (shadcn/ui, paleta brasa/carvão/sal/azulejo/mostarda AA, Bricolage + Inter + JetBrains Mono, cards-comanda com serrilha). Agente
design-mastercriado e aplicado (auditoria WCAG AA completa). - Rascunho do cardápio é client-side (jotai): decisão do user — banco guarda SÓ o publicado. Edições vivem em
atomWithStorageno navegador; preview (celular na página de produtos) renderiza direto dos atoms, sem rede; "Publicar alterações" = umPUT /stores/menucom reconcile replace-all (UUIDs gerados no cliente, upserts escopados no tenant). Modelo anterior (snapshotpublished_menuno banco) foi implementado e revertido — colunas dropadas na migration 0002. - Trade-off aceito: rascunho não cruza dispositivos (localStorage).
Fora do MVP (fase 2+)
Domínio próprio do lojista (Caddy on-demand TLS ou certbot por domínio + verificação DNS), pedido em mesa via QR, impressão de pedido, cupons/promoções, relatórios de vendas, app/PWA de gestão, application fee por pedido como alavanca extra.