Pedidos / Carrinho — Campos, configs e credenciais
Tabela orders (campos relevantes no checkout)
Seção intitulada “Tabela orders (campos relevantes no checkout)”| Coluna | Uso no carrinho |
|---|---|
user / user_code |
Associado |
name_associate / email |
Snapshot |
items (JSONB) |
Linhas do carrinho (sem linha “Frete” sintética se delivery_price separado) |
total |
Total final |
delivery_price |
Frete cotado (sempre persistir o valor simulado) |
discount |
Desconto manual (R$) — campo do carrinho |
donation |
Doação (R$) — campo do carrinho (reduz o total; sem cupons) |
custom_payment |
JSON pagamento personalizado (soma junto ao desconto no total) |
status |
Default create: Aguardando pagamento |
address |
Snapshot do endereço de entrega |
prescriber / prescriber_code |
Prescritor |
info / tags |
Observações / tags manuais — não derivadas do frete |
kunk_user |
Operador logado |
payment_date |
Se total ≤ 0 no create |
tracking_code / tracking_code_date |
Preenchidos na geração de etiqueta (Pedidos) |
dce |
Snapshot da declaração — gravado no create-label, não no create do pedido |
Campos não usados no checkout
Seção intitulada “Campos não usados no checkout”| Campo | Motivo |
|---|---|
coupon_id / cupons |
Não disponível (doação manual permanece) |
no_commission |
Não disponível |
bvid |
Não usado neste produto |
Campos novos de frete
Seção intitulada “Campos novos de frete”| Coluna | Tipo | Descrição |
|---|---|---|
freight_carrier |
VARCHAR(32) nullable |
loggi | melhorenvio |
freight_option |
JSONB nullable |
Snapshot da modalidade escolhida |
Campo de frete faz parte do schema de instalação.
Não setar tag "correio" automaticamente a partir do frete.
Shape de orders.freight_option
Seção intitulada “Shape de orders.freight_option”Snapshot da modalidade escolhida. Identifica provider/serviço sem depender de tags.
{ "option_key": "melhorenvio:1:1", "provider": "melhorenvio", "company_id": 1, "company_name": "Correios", "service_id": 1, "service_name": "PAC", "freight_type": null, "service_label": "Correios PAC", "price": 12.1, "eta_days": 9}Exemplo Loggi:
{ "option_key": "loggi:FREIGHT_TYPE_ECONOMIC", "provider": "loggi", "company_name": "Loggi", "freight_type": "FREIGHT_TYPE_ECONOMIC", "service_label": "Loggi Econômico", "external_service_id": "DLVR-DROF-DOOR-STAN-01", "price": 18.5, "eta_days": 5}Complementos: freight_carrier (loggi | melhorenvio). Sem auto-tag pelo frete.
Seção intitulada “Complementos: freight_carrier (loggi | melhorenvio). Sem auto-tag pelo frete.”Shape de item do carrinho (orders.items)
Seção intitulada “Shape de item do carrinho (orders.items)”amount = preço unitário (histórico; nome confuso). quantity = quantidade.
{ "amount": 89.9, "concentration": null, "unity": "un", "quantity": 2, "code": "OLEO-01", "name": "Óleo 3000mg", "id": 12, "description": "", "category": "Óleos"}Subtotal da linha = amount × quantity (ex.: 179,80).
Mapeamento: buildOrderItemsFromCheckout anteriores (amount ← price do checkout).
Total do pedido no server: Σ (amount × quantity) — sem re-lookup de catálogo.
system = store (Loja)
Seção intitulada “system = store (Loja)”Configs da loja / carrinho em system_configs.
| Key | value_type |
Default | Descrição |
|---|---|---|---|
store.freight.apply_to_total |
boolean |
true |
Se true, o valor do frete cotado entra no total do carrinho. Se false, simula mas não soma (comportamento). |
store.freight.default_option |
json |
null |
Favorito: provider + transportadora + modalidade (ver abaixo) |
store.freight.package |
json |
— (obrigatório no admin) | Dimensões/peso da caixa — cotação e etiqueta |
store.ship_from |
json |
— (obrigatório no admin) | Remetente / quem envia os pedidos |
store.freight.content_declaration |
json |
— (obrigatório para etiqueta) | Declaração de conteúdo compartilhada (Loggi + Melhor Envio) |
store.freight.loggi.external_service_ids |
json |
[] |
SISUs Loggi (homologação) enviados na cotação |
store.freight.melhorenvio.enabled_service_ids |
json |
null |
IDs de serviço ME a cotar; null = todos do catálogo / calculate |
Declaração de conteúdo é uma key na Loja, usada por todos os módulos de entrega — não duplicar em
modules.loggi.*/modules.melhorenvio.*.
store.freight.default_option (favorito da loja)
Seção intitulada “store.freight.default_option (favorito da loja)”Opção pré-selecionada no carrinho sempre que a cotação a devolver. Exemplos:
Melhor Envio > Correios > PAC
{ "option_key": "melhorenvio:1:1", "provider": "melhorenvio", "company_id": 1, "company_name": "Correios", "service_id": 1, "service_name": "PAC", "label": "Melhor Envio > Correios > PAC"}Loggi > econômico
{ "option_key": "loggi:FREIGHT_TYPE_ECONOMIC", "provider": "loggi", "freight_type": "FREIGHT_TYPE_ECONOMIC", "company_name": "Loggi", "service_name": "Econômico", "label": "Loggi > Econômico"}Melhor Envio > Azul > Express
{ "option_key": "melhorenvio:9:15", "provider": "melhorenvio", "company_id": 9, "company_name": "Azul Cargo Express", "service_id": 15, "service_name": "Expresso", "label": "Melhor Envio > Azul > Express"}Resolução no carrinho
Seção intitulada “Resolução no carrinho”- Cotar Loggi + Melhor Envio (providers com
use_for_quote) - Montar lista unificada de
options[] - Se
default_option.option_keyexistir na lista → pré-selecionar - Senão, match parcial (mesmo
provider+freight_typeoucompany_id+service_id) - Senão →
cheapestentre opções ready - Operador pode mudar; ação Definir como padrão (qualquer role com acesso ao carrinho) grava
store.freight.default_option
store.freight.default_carrier (string simples) fica obsoleto — substituído por default_option.
store.freight.package — dimensões da caixa (admin Loja)
Seção intitulada “store.freight.package — dimensões da caixa (admin Loja)”Obrigatório. Sem valor no admin → cotação e etiqueta retornam CONFIG_INCOMPLETE.
Proibido hardcode de peso/dims no código (nem 290 g, nem 500 g, nem medidas “de exemplo” usadas em runtime).
Shape (valores inseridos pela associação):
{ "weight_g": null, "length_cm": null, "width_cm": null, "height_cm": null}| Campo | Regra |
|---|---|
weight_g |
> 0, informado no admin |
length_cm, width_cm, height_cm |
> 0, informados no admin |
Seed da key: is_required=true, allow_hardcoded=false, value vazio / nulls até o admin salvar.
Opcional: store.freight.label_package — só create-label; se vazio, usa package.
dims_etiqueta = label_package ?? package.
Dimensões e peso vêm somente da config do admin.
store.ship_from — quem envia (admin Loja)
Seção intitulada “store.ship_from — quem envia (admin Loja)”Obrigatório. Origem do frete (cidade, CNPJ, telefone, CEP) vem da config do admin.
Incompleto → CONFIG_INCOMPLETE.
{ "name": "", "phone": "", "federal_tax_id": "", "street": "", "number": "", "complement": "", "neighborhood": "", "city": "", "state": "", "cep": "", "country": "Brasil"}Completude mínima: name, phone, federal_tax_id, street, number, neighborhood, city, state, cep (8 dígitos). country pode defaultar a Brasil só como placeholder de formulário se vazio no save — preferível exigir no admin também.
Princípio: zero hardcode de entrega
Seção intitulada “Princípio: zero hardcode de entrega”| Dado | Onde | Hardcode no código? |
|---|---|---|
| Remetente | store.ship_from |
não |
| Caixa / peso / dims | store.freight.package |
não |
| Override etiqueta | store.freight.label_package |
não (opcional) |
| Declaração de conteúdo | store.freight.content_declaration |
não |
| SISUs Loggi | store.freight.loggi.external_service_ids |
não |
| Serviços ME habilitados | store.freight.melhorenvio.enabled_service_ids |
não (null = todos do catálogo da API) |
| Secrets API | system_api_credentials / env |
não no source |
store.freight.content_declaration — declaração compartilhada (admin Loja)
Seção intitulada “store.freight.content_declaration — declaração compartilhada (admin Loja)”Uma declaração para Loggi e Melhor Envio. Obrigatória para etiqueta; valor inserido no admin (sem default numérico/texto no código).
{ "description": "", "total_value": null}| Campo | Uso Loggi | Uso Melhor Envio |
|---|---|---|
description |
contentDeclaration.description |
Conteúdo do volume quando aplicável |
total_value |
contentDeclaration.totalValue + goodsValue |
insurance / insurance_value |
Regras: obrigatório para etiqueta; cotação usa total_value; sem aleatório; snapshot dce no create-label; UI em /servicos-externos/envio.
Seed: is_required=true, allow_hardcoded=false, value vazio até o admin salvar.
system = modules (flags de papel)
Seção intitulada “system = modules (flags de papel)”Além do enable no Admin (modules.*.enabled), cada serviço externo tem flags de uso:
| Key | Default sugerido | Descrição |
|---|---|---|
modules.loggi.enabled |
false |
Módulo ativo |
modules.loggi.use_for_quote |
false |
Aparece na simulação do carrinho |
modules.loggi.use_for_label |
false |
Pode gerar etiqueta |
modules.melhorenvio.enabled |
false |
Módulo ativo |
modules.melhorenvio.use_for_quote |
false |
Cotação no carrinho |
modules.melhorenvio.use_for_label |
false |
Default off |
modules.freight.label_provider |
loggi |
Quem gera etiqueta quando ambos poderiam |
Regra: se use_for_label de um serviço for false, a UI de Pedidos não oferece criar etiqueta por aquele provider.
Se nenhum label provider ativo → ocultar ações de etiqueta.
Não existe
modules.{service}.content_declaration— conteúdo fica emstore.freight.content_declaration.
Tabela system_api_credentials
Seção intitulada “Tabela system_api_credentials”Nova tabela relacionada a system_configs / módulos — só secrets e metadados de conexão. Valores sensíveis sempre criptografados at-rest com CONFIG_ENCRYPT_KEY (AES-256-GCM, mesmo padrão de system_configs.is_sensitive).
CREATE TABLE IF NOT EXISTS system_api_credentials ( id SERIAL PRIMARY KEY, service VARCHAR(64) NOT NULL, -- 'loggi' | 'melhorenvio' | … field_key VARCHAR(128) NOT NULL, -- 'client_id' | 'client_secret' | 'company_id' | … encrypted_value TEXT, -- ciphertext; NULL = usar env env_fallback VARCHAR(128), -- nome da env (ex. LOGGI_CLIENT_SECRET) is_secret BOOLEAN NOT NULL DEFAULT true, description TEXT, last_tested_at TIMESTAMPTZ, last_test_ok BOOLEAN, date_created TIMESTAMPTZ NOT NULL DEFAULT NOW, date_updated TIMESTAMPTZ, UNIQUE (service, field_key));Campos por serviço
Seção intitulada “Campos por serviço”field_key |
Secret? | Env fallback | Obrigatório |
|---|---|---|---|
client_id |
sim | LOGGI_CLIENT_ID |
sim |
client_secret |
sim | LOGGI_CLIENT_SECRET |
sim |
company_id |
não* | LOGGI_COMPANY_ID |
sim |
api_base_url |
não | LOGGI_URL_API |
não (default prod) |
token_url |
não | LOGGI_TOKEN_URL |
não |
* company_id não é “senha”, mas tratar como write-once na UI se preferir; pode ser is_secret=false e ainda assim não misturar com /config/public.
Docs oficiais: Loggi API — OAuth client credentials; cotação /quotations; etiquetas /labels; envios async-shipments (usado antes).
Melhor Envio
Seção intitulada “Melhor Envio”field_key |
Secret? | Env fallback | Obrigatório |
|---|---|---|---|
client_id |
sim | MELHOR_ENVIO_CLIENT_ID |
sim |
client_secret |
sim | MELHOR_ENVIO_CLIENT_SECRET |
sim |
redirect_uri |
não | MELHOR_ENVIO_REDIRECT_URI |
sim (OAuth) |
api_base_url |
não | MELHOR_ENVIO_API_URL |
não (prod/sandbox) |
access_token |
sim | — | obtido via OAuth (não digitado no assistente inicial) |
refresh_token |
sim | — | obtido via OAuth |
Docs oficiais: Autenticação OAuth2 · Cálculo de fretes · scopes shipping-calculate, shipping-generate, etc.
Tokens OAuth do Melhor Envio não ficam em arquivo JSON . Ficam em system_api_credentials criptografados.
Resolução de credencial (server)
Seção intitulada “Resolução de credencial (server)”resolveCredential(service, field_key): 1. row.encrypted_value → decrypt → return 2. process.env[row.env_fallback] → return (+ source=env) 3. throw CREDENTIAL_MISSINGResposta ao frontend nunca inclui o valor:
{ "field_key": "client_secret", "is_secret": true, "has_value": true, "source": "db", "env_fallback": "LOGGI_CLIENT_SECRET", "env_present": false, "last_tested_at": "2026-07-11T…", "last_test_ok": true}source: db | env | empty.
Totais no front (contrato)
Seção intitulada “Totais no front (contrato)”type CartTotals = { products: number; // Σ amount × quantity delivery_price: number; delivery_applied: number; discount: number; // manual + custom payments donation: number; total: number;};Config store.freight.apply_to_total via GET /config/public?system=store (não sensível) ou endpoint dedicado do carrinho.
