🌐 Validação Externa do Carrinho
📋 Visão Geral
O módulo de validação externa do carrinho permite integrar serviços externos ao fluxo de manipulação do carrinho, habilitando regras de negócio personalizadas que vão além das validações nativas da plataforma.
O pipeline opera em duas etapas independentes, cada uma configurada por uma URL de serviço externo via Settings:
| Etapa | Setting | Modo | Descrição |
|---|---|---|---|
| Etapa 1 — Validação 🛡️ | cart/external/validation-url | Hard-fail | Valida se o produto pode entrar no carrinho. Rejeições bloqueiam a operação. |
| Etapa 2 — Valores 💰 | cart/external/values-url | Soft-fail | Injeta descontos e acréscimos (impostos, taxas) nos itens do carrinho. Falhas são registradas como warning. |
---
🛡️ Etapa 1 — Validação de Entrada de Produtos
⏱️ Quando é chamada
A cada operação de adição (add-product) ou atualização de quantidade (update-product) de um item no carrinho.
🔄 Fluxo
1. O estado atual do carrinho é capturado em um snapshot antes da modificação
2. A quantidade solicitada é aplicada no mapa de produtos em memória
3. O serviço externo é chamado com o payload completo do carrinho
4. ✅ Se aprovado (status: ok): o fluxo continua normalmente
5. ❌ Se rejeitado (HTTP 4xx): o carrinho é restaurado ao estado anterior (rollback) e o erro é propagado
📤 Contrato de Requisição
O serviço externo recebe um POST com o seguinte payload:
1{2 "domain": "sua-loja.com.br",3 "action": "add-product",4 "cart": {5 "id": "abc123",6 "requestedProductMap": [7 {8 "productId": 50,9 "quantity": 3,10 "code": null,11 "upsellId": null,12 "productPairOfferId": null13 }14 ],15 "customer": { ... },16 "addresses": [ ... ],17 "orders": [18 {19 "orderId": 1,20 "storage": { ... },21 "items": [22 {23 "itemId": 1,24 "quantity": 3,25 "unitValue": 70.00,26 "product": {27 "productId": 50,28 "name": "Camiseta Algodão Premium",29 "sku": "TSHIRT-WHITE-G",30 "brandName": "Marca X",31 "active": true,32 "stock": 10033 }34 }35 ]36 }37 ]38 }39}| Campo | Descrição |
|---|---|
domain | Domínio do canal de venda |
action | Ação que disparou a validação: add-product ou update-product |
cart | Objeto completo do carrinho incluindo cliente, endereços, pedidos e produtos |
cart.requestedProductMap | Mapa de produtos com as quantidades atualizadas (fonte de verdade) |
cart.orders | Pedidos com itens, armazém e dados completos de produto |
✅ Contrato de Resposta — Sucesso
1{2 "status": "ok"3}O serviço deve retornar HTTP 200 com "status": "ok" para aprovar a operação.
❌ Contrato de Resposta — Rejeição de Negócio
1{2 "code": "QUANTITY_LIMIT_EXCEEDED",3 "message": "A quantidade máxima permitida por produto é 2."4}O serviço deve retornar HTTP 4xx (recomendado 422) com:
| Campo | Obrigatório | Descrição | ||
|---|---|---|---|---|
code | ✅ Sim | Código identificador do erro (aparece como identifier no array de erros do carrinho) | ||
error \ | message \ | errorMessage | ✅ Sim (ao menos um) | Mensagem descritiva do erro (aparece como description no array de erros do carrinho) |
1{2 "error": "UAPPI_STORE_CART_EXTERNAL_VALIDATION_REJECTED",3 "entity": "cart",4 "identifier": "QUANTITY_LIMIT_EXCEEDED",5 "description": "A quantidade máxima permitida por produto é 2.",6 "type": "error"7}⚠️ Erros de Infraestrutura Externa
Falhas de comunicação (timeout, conexão, servidor) são tratadas como hard-fail e também bloqueiam a operação:
| Código Interno | HTTP | Cenário |
|---|---|---|
UAPPI_STORE_CART_EXTERNAL_VALIDATION_TIMEOUT | 504 | O serviço não respondeu no tempo limite (5s) |
UAPPI_STORE_CART_EXTERNAL_VALIDATION_UNAVAILABLE | 502 | Erro de conexão ou erro 5xx no serviço |
UAPPI_STORE_CART_EXTERNAL_VALIDATION_INVALID_CONTRACT | 502 | HTTP 200 mas o corpo não contém "status": "ok" |
💰 Etapa 2 — Injeção de Valores Personalizados
⏱️ Quando é chamada
Automaticamente durante o refresh de pedidos do carrinho, após a aplicação de promoções e regras de valores customizados. É executada a cada operação que recalcula o carrinho (adição, atualização, cotação de frete, etc).
🔄 Fluxo
1. O cache do payload é invalidado para garantir quantidades atualizadas 2. O serviço externo é chamado com o payload completo do carrinho 3. Os valores retornados (descontos e acréscimos) são aplicados aos itens via DMC promocional 4. ⚠️ Em caso de falha: o erro é registrado como warning — o carrinho continua sem os valores externos
📤 Contrato de Requisição
O serviço externo recebe um POST com o mesmo payload da Etapa 1, porém sem o campo action:
1{2 "domain": "sua-loja.com.br",3 "cart": {4 "id": "abc123",5 "requestedProductMap": [ ... ],6 "customer": { ... },7 "addresses": [ ... ],8 "orders": [ ... ]9 }10}✅ Contrato de Resposta — Sucesso
1{2 "status": "ok",3 "items": [4 {5 "orderId": 1,6 "productId": 50,7 "additionType": "tax",8 "addition": {9 "type": "fixed",10 "value": 150.00,11 "label": "Imposto adicional (R$ 50,00/un)"12 },13 "discountType": null,14 "discount": null15 }16 ],17 "errors": []18}📊 Campos de items
Cada item no array items pode conter desconto, acréscimo ou ambos:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
productId | integer | ✅ Sim | ID do produto |
orderId | integer | ❌ Não | ID do pedido (quando omitido, aplica ao produto em todos os pedidos) |
discount | object | ❌ Não | Desconto a ser aplicado ao item |
discount.type | string | ✅ Sim | Tipo do cálculo: fixed (valor absoluto) ou percentage (percentual do subtotal) |
discount.value | float | ✅ Sim | Valor do desconto. Para fixed: valor em reais. Para percentage: percentual (ex: 10 = 10%) |
discountType | string | ❌ Não | Tipo da entidade de desconto. Deve ser custom-value-rule. Fallback automático se omitido |
addition | object | ❌ Não | Acréscimo a ser aplicado ao item |
addition.type | string | ✅ Sim | Tipo do cálculo: fixed ou percentage |
addition.value | float | ✅ Sim | Valor do acréscimo |
addition.label | string | ❌ Não | Descrição do acréscimo exibida ao usuário |
additionType | string | ❌ Não | Tipo da entidade de acréscimo. Valores aceitos: commission, service-fee, gift-packaging, fee, tax, custom-value-rule |
📊 Campos de errors
O array errors permite que o serviço externo propague mensagens de erro ou aviso para o carrinho:
| Campo | Tipo | Descrição |
|---|---|---|
error | string | Código do erro |
entity | string | Entidade relacionada (ex: cart, product) |
identifier | string | Identificador específico (ex: ID do produto) |
description | string | Mensagem descritiva |
type | string | error ou warning |
🧮 Regras de Cálculo
- Descontos são limitados ao subtotal do item — nunca geram valores negativos
- Acréscimos não possuem limite superior
- Valores com
type: "percentage"são calculados como:subtotal * value / 100 - Valores com
type: "fixed"são aplicados diretamente - Itens com
value <= 0são ignorados cart.customer.tradeNameecart.customer.nameCorporateName— nome mascaradocart.customer.email— email mascaradocart.customer.phone— telefone mascaradocart.customer.documents.*.document— documento mascaradocart.addresses.*.recipient— nome do destinatário mascarado- Deve aceitar requisições
POSTcomContent-Type: application/json - Deve responder em até 5 segundos (timeout de conexão e resposta)
- Deve retornar
Content-Type: application/json - Etapa 1: deve retornar
{"status": "ok"}para aprovar ou HTTP 4xx para rejeitar - Etapa 2: deve retornar
{"status": "ok", "items": [...], "errors": [...]}com os valores a aplicar - A Etapa 1 (validação) opera em modo hard-fail: qualquer falha bloqueia a operação e restaura o carrinho
- A Etapa 2 (valores) opera em modo soft-fail: falhas são registradas como warning sem bloquear o fluxo
- O
requestedProductMapno payload sempre contém as quantidades atualizadas (fonte de verdade), mesmo quando osordersainda refletem o estado anterior do banco - Os valores aplicados pela Etapa 2 são persistidos na criação do pedido como
additionsdo tipo configurado pelo serviço externo - Ambas as etapas compartilham a mesma infraestrutura de logging e classificação de erros
---
📝 Logs de Auditoria
Todas as chamadas externas (sucesso e falha) são registradas na tabela cart_external_validation_log com:
| Campo | Descrição |
|---|---|
cart_id | ID do carrinho |
trade_policy_id | Política comercial |
sales_channel_id | Canal de venda |
stage | Etapa: validation ou values |
url | URL chamada |
http_status | Código HTTP retornado |
request_time_ms | Tempo de resposta em milissegundos |
request | Payload enviado (dados sensíveis mascarados) |
response | Corpo da resposta |
error_type | Classificação: connection, server, timeout, business, contract |
🔒 Mascaramento de Dados
Os seguintes campos são mascarados antes da persistência do log:
---
⚙️ Configuração
As URLs são configuradas via Settings da loja. Quando vazias, as etapas são ignoradas:
| Setting | Descrição |
|---|---|
cart/external/validation-url | URL do serviço de validação de entrada de produtos (Etapa 1) |
cart/external/values-url | URL do serviço de injeção de valores personalizados (Etapa 2) |
📌 Requisitos do Serviço Externo
---
📌 Notas e Considerações
📋 Listagem de Logs de Validação Externa do Carrinho
/carts/external-validation-logs
Endpoint responsável por listar logs técnicos de validação externa do carrinho
📝 Descrição
Retorna uma listagem paginada dos logs técnicos gerados pelas chamadas de validação externa do carrinho (Etapa 1 — Validação e Etapa 2 — Valores).
O endpoint é voltado ao painel administrativo e permite auditoria operacional por etapa (validation ou values), tipo de erro, status HTTP retornado pelo provedor externo e tempo de resposta.
🔐 Permissão
UserAuth— autenticação do usuário administrativoCART_READ— escopo de permissão para leitura de carrinhos
📝 Parâmetros
🏷️ Headers Obrigatórios
| Header | Valor | Descrição |
|---|---|---|
Authorization | Bearer {token} | Token de autenticação de usuário |
🔍 Campos de Condição Disponíveis
| Campo | Tipo | Descrição |
|---|---|---|
id | integer | Filtrar pelo ID do log |
cartId | string | Filtrar pelo ID do carrinho |
tradePolicyId | integer | Filtrar pela política comercial |
salesChannelId | integer | Filtrar pelo canal de venda |
stage | string | Filtrar pela etapa (validation ou values) |
url | string | Filtrar pela URL chamada no provedor externo |
errorType | string | Filtrar por tipo de erro (contract, connection, timeout, business, server) |
httpStatus | integer | Filtrar pelo status HTTP retornado pelo provedor externo |
requestTimeMs | integer | Filtrar pelo tempo de resposta em milissegundos |
request | string | Filtrar pelo payload registrado |
response | string | Filtrar pela resposta registrada |
createdAt | string | Filtrar pela data de criação do log |
↕️ Campos de Ordenação Disponíveis
| Campo | Descrição |
|---|---|
id | Ordenar pelo ID do log |
stage | Ordenar pela etapa |
errorType | Ordenar pelo tipo de erro |
httpStatus | Ordenar pelo status HTTP |
requestTimeMs | Ordenar pelo tempo de resposta |
createdAt | Ordenar pela data de criação |
💡 Exemplo de Requisição
1curl --request GET \2 --url '{{MAIN_URL}}/carts/external-validation-logs?limit=20&offset=0&condition[stage]=validation&order[0][field]=createdAt&order[0][direction]=desc' \3 --header 'Authorization: Bearer SEU_TOKEN_AQUI'✅ Resposta de Sucesso
HTTP Status: 200 OK
1{2 "status": true,3 "data": {4 "registers": [5 {6 "id": 42,7 "cartId": "ac66ca1d61577b216979169",8 "tradePolicyId": 1,9 "salesChannelId": 1,10 "stage": "validation",11 "url": "https://validator.exemplo.com/cart/validate",12 "errorType": "business",13 "httpStatus": 422,14 "requestTimeMs": 187,15 "request": {16 "domain": "loja.exemplo.com.br",17 "action": "add-product",18 "cart": {19 "id": "ac66ca1d61577b216979169",20 "requestedProductMap": [21 {22 "productId": 50,23 "quantity": 524 }25 ],26 "customer": {27 "nameCorporateName": "J<strong><em> S</strong></em>",28 "email": "jo<em></em>*@exemplo.com",29 "phone": "+55<strong></strong><strong></strong>21"30 }31 }32 },33 "response": {34 "code": "QUANTITY_LIMIT_EXCEEDED",35 "message": "A quantidade máxima permitida por produto é 3."36 },37 "createdAt": "2026-09-03 14:30:00"38 },39 {40 "id": 41,41 "cartId": "ac66ca1d61577b216979169",42 "tradePolicyId": 1,43 "salesChannelId": 1,44 "stage": "values",45 "url": "https://tax.exemplo.com/cart/values",46 "errorType": "timeout",47 "httpStatus": null,48 "requestTimeMs": 5003,49 "request": {50 "domain": "loja.exemplo.com.br",51 "cart": {52 "id": "ac66ca1d61577b216979169",53 "requestedProductMap": [54 {55 "productId": 50,56 "quantity": 257 }58 ]59 }60 },61 "response": null,62 "createdAt": "2026-09-03 14:28:15"63 }64 ],65 "pagination": {66 "limit": 20,67 "offset": 0,68 "pages": 1,69 "registers": 270 }71 }72}📊 Campos da Resposta
| Campo | Descrição |
|---|---|
id | ID do log |
cartId | ID do carrinho que gerou o log |
tradePolicyId | Política comercial |
salesChannelId | Canal de venda |
stage | Etapa da validação: validation (Etapa 1) ou values (Etapa 2) |
url | URL do serviço externo chamado |
errorType | Tipo do erro: connection, server, timeout, business, contract |
httpStatus | Código HTTP retornado (pode ser null em erros de conexão/timeout) |
requestTimeMs | Tempo de resposta em milissegundos |
request | Payload enviado ao serviço externo (dados sensíveis mascarados) |
response | Corpo da resposta recebida (pode ser null) |
createdAt | Data e hora do registro |
⚠️ Possíveis erros:
| Código Interno | Código HTTP | Descrição |
|---|---|---|
| Sem código interno específico | 401 | Token ausente, inválido ou expirado |
| Sem código interno específico | 403 | Usuário autenticado sem escopo CART_READ |