Skip to main content

Requisitos — Cart API: Endpoint para Remover Item de Cart Existente

Task: task-194883-cart-api-remove-item.md Contexto: CONTEXT Contexto US: US 194871 — MVP Upsell (Pedido) Pesquisa tecnica: pesquisa-remove-recommended-item.md ADRs: ADR-008

Status: refinado
Sessão de grilling: 2026-05-15


Visão Geral

O checkout service precisa remover um CartItem originado de um CartRecommendedItem aceito anteriormente. A Cart API não possui hoje um endpoint para remover item individual — esta feature cria esse endpoint interno, chamado exclusivamente pelo checkout service via ApiKey "internal".

Papéis

  • Checkout service: caller exclusivo deste endpoint; autenticado via ApiKey "internal"; responsável por validar que o PaymentStatus não está em estado pago/finalizado antes de chamar este endpoint
  • Cart API: localiza o cart e o item, valida que o item é originado de recomendação (IsFromRecommendation), aplica soft-delete, recalcula totais e persiste

Requisitos

REQ-01 — Autenticação

User Story: Como checkout service, quero chamar a Cart API sem JWT, para que eu possa remover itens sem depender de contexto de usuário.

Critérios de Aceite:

  1. WHEN a requisição chega com x-api-key válida para a chave "internal" THEN o sistema SHALL processar a requisição normalmente
  2. WHEN a requisição chega sem x-api-key ou com valor inválido THEN o sistema SHALL retornar 401
  3. IF a requisição contém JWT Bearer THEN o sistema SHALL ignorá-lo — a autenticação é exclusivamente por ApiKey

Padrão de implementação: [AllowAnonymous] + [UseApiKey("internal")] — idêntico ao POST api/cart/{cartId}/items e ao POST api/cart/assistant.


REQ-02 — Contrato do Endpoint

User Story: Como checkout service, quero um endpoint DELETE api/cart/{cartId}/items/{cartItemId}, para que eu possa remover um item de um cart existente.

Critérios de Aceite:

  1. WHEN a requisição é DELETE api/cart/{cartId}/items/{cartItemId} com ApiKey válida THEN o sistema SHALL processar a remoção do item
  2. WHEN cartId não é informado na rota THEN o sistema SHALL retornar 400
  3. WHEN cartItemId não é informado na rota THEN o sistema SHALL retornar 400
  4. O endpoint não possui body — todos os identificadores estão na rota

REQ-03 — Buscar Cart

Critérios de Aceite:

  1. WHEN o endpoint é chamado com cartId THEN o sistema SHALL buscar o CartModel pelo cartId via GetByIdAsync com tracking habilitado
  2. WHEN o cart não é encontrado THEN o sistema SHALL retornar 400 com mensagem indicando cart não encontrado
  3. WHEN o cart é encontrado THEN o sistema SHALL prosseguir com a busca do item

GetByIdAsync com tracking — mesmo padrão do Handle(DeleteCartCommand) — necessário para que o EF rastreie as alterações e persista o soft-delete corretamente.


REQ-03 — Validar Status do Cart

Critérios de Aceite:

  1. WHEN o cart é encontrado THEN o sistema SHALL não validar CartStatus como critério de bloqueio da remoção
  2. A responsabilidade de garantir que o payment não está pago é do checkout service, que verifica PaymentStatus antes de chamar este endpoint

Decisão de escopo: O CartStatus em CartModel não é atualizado de forma confiável após o pagamento — o estado autoritativo de pagamento está em PaymentsModel.Status (PaymentStatus), que pertence ao contexto do checkout service. A Cart API não tem acesso ao PaymentsModel. O padrão de validação de PaymentStatus antes de modificar o cart já está estabelecido no checkout service (ex: EcommerceUpdateCartService linha 35 — permite apenas Created e Pending).


REQ-04 — Buscar CartItem

Critérios de Aceite:

  1. WHEN o cart é encontrado THEN o sistema SHALL buscar o CartItemsModel com Id == cartItemId AND DateDeleted == null dentro da coleção CartItems do cart carregado
  2. WHEN o item não é encontrado na coleção (inexistente ou pertencente a outro cart) THEN o sistema SHALL retornar 400 com mensagem genérica indicando item não encontrado
  3. A busca é restrita à coleção do cart carregado — não é feita busca global por cartItemId; um cartItemId válido em outro cart retorna 400 com a mesma mensagem

Resposta 400 para ambos os casos (cart não encontrado e item não encontrado) — consistente com o padrão do POST api/cart/{cartId}/items.


REQ-04-A — Validar Origem do Item (IsFromRecommendation)

User Story: Como Cart API, quero garantir que apenas itens originados de recomendações possam ser removidos por este endpoint, para que itens adicionados manualmente pelo vendedor não sejam removidos inadvertidamente.

Critérios de Aceite:

  1. WHEN o item é encontrado THEN o sistema SHALL verificar se item.IsFromRecommendation == true
  2. WHEN item.IsFromRecommendation == false THEN o sistema SHALL retornar 400 com mensagem indicando que o item não é removível por este endpoint
  3. WHEN item.IsFromRecommendation == true THEN o sistema SHALL prosseguir com a remoção

Dependencia bloqueante: IsFromRecommendation (bool) nao existe hoje em CartItemsModel. Deve ser adicionado em US separada antes desta task ser implementada. Ver CONTEXT.md — IsFromRecommendation.


REQ-05 — Remover Item e Recalcular Totais

User Story: Como Cart API, quero aplicar soft-delete no item e recalcular os totais do cart, para que o estado do cart permaneça consistente após a remoção.

Critérios de Aceite:

  1. WHEN o item é encontrado THEN o sistema SHALL chamar CartModel.RemoveItem(cartItemId), que executa item.Delete() (soft-delete via BaseEntity.Delete() — seta DateDeleted)
  2. WHEN o soft-delete é aplicado THEN o sistema SHALL chamar CartModel.CalculateTotal()
  3. WHEN CalculateTotal() é executado THEN o sistema SHALL somar apenas itens com DateDeleted == null — o item removido não deve compor o novo total
  4. WHEN os totais são recalculados THEN o sistema SHALL chamar _cartRepository.Update(cart) seguido de SaveAsync()
  5. WHEN Update(cart) é chamado THEN o sistema SHALL invalidar automaticamente o cache CacheKeys.CartById(cart.Id) — comportamento já implementado em CartRepository.Update() linha 79

RemoveItem é um novo método a criar em CartModel (coezzion-db-core). Ele apenas aplica item.Delete()não chama CalculateTotal() internamente; o service chama separadamente, consistente com o padrão do add-item.
CalculateTotal() requer ajuste para filtrar DateDeleted == null antes de somar — atualmente itera toda a coleção sem filtro (linha 209 de CartModel.cs).


REQ-06 — Persistir e Retornar

Critérios de Aceite:

  1. WHEN a persistência é bem-sucedida THEN o sistema SHALL retornar 200 sem body
  2. WHEN ocorre erro inesperado na persistência THEN o sistema SHALL retornar 500

Retorno 200 sem body — idêntico ao padrão do DELETE api/cart existente (Handle(DeleteCartCommand) retorna BaseResult sem payload). O checkout service não depende de dados de retorno da Cart API — relê o cart diretamente do banco de leitura via GetCartInfoAsync (sem cache, conexão read-only).


Resumo de Respostas HTTP

CódigoSituação
200Item removido, totais recalculados e cart persistido com sucesso
400cartId ou cartItemId ausentes na rota
400Cart não encontrado
400Item não encontrado no cart (inexistente ou pertencente a outro cart)
400Item não originado de recomendação (IsFromRecommendation == false)
401ApiKey ausente ou inválida
500Erro inesperado na persistência

Artefatos a Criar / Modificar

ArtefatoProjetoTipo
CartModel.RemoveItem(int cartItemId)coezzion-db-coreNovo método
CartModel.CalculateTotal() — filtrar DateDeleted == nullcoezzion-db-coreAjuste
CartItemsModel.IsFromRecommendation (bool)coezzion-db-coreDependência bloqueante (US separada)
RemoveCartItemCommandcoezzion-service-cartNovo command
ICartService.Handle(RemoveCartItemCommand)coezzion-service-cartNova assinatura
CartService.Handle(RemoveCartItemCommand)coezzion-service-cartNova implementação
CartControllerDELETE api/cart/{cartId}/items/{cartItemId}coezzion-service-cartNovo endpoint

Fora de Escopo

  • Validação de CartStatus/PaymentStatus — o CartStatus não é atualizado de forma confiável após pagamento; a validação de que o payment não foi pago é responsabilidade do checkout service (via PaymentStatus do PaymentsModel)
  • Remoção física de linha no banco — sempre soft-delete via BaseEntity.Delete()
  • Retorno de dados do cart no body da resposta — 200 sem body
  • Suporte a remoção de múltiplos itens por chamada — um item por request

Questões Fechadas (grilling 2026-05-15)

#QuestãoDecisão
Q1Método de busca do cart (tracking vs. no-tracking)?GetByIdAsync com tracking — mesmo padrão do DeleteCartCommand
Q2Soft-delete ou remoção física?Soft-delete via item.Delete() — padrão do sistema
Q3RemoveItem encapsula CalculateTotal ou service chama separado?Service chama separado — consistente com padrão do add-item
Q4CalculateTotal filtra itens deletados?Não filtra hoje — ajuste necessário: Where(x => x.DateDeleted == null)
Q5Risco de propagação read replica após commit Cart API?Mesmo risco já aceito no add-item; GetCartInfoAsync usa read-only sem cache
Q6Nome do command?RemoveCartItemCommand
Q7Assinatura no ICartService?Task<BaseResult> Handle(RemoveCartItemCommand command)
Q8404 ou 400 para recursos não encontrados?400 — consistente com add-item
Q9Busca do item: global ou restrita ao cart?Restrita à coleção do cart carregado; qualquer resultado nulo → 400 genérico
Q10Invalidação de cache necessária?Coberta por CartRepository.Update()CacheKeys.CartById(cart.Id) já invalidado automaticamente
Q11A Cart API deve validar se o cart foi pago?Não — CartStatus não é atualizado de forma confiável após pagamento; responsabilidade do checkout service via PaymentStatus (Created ou Pending permitidos)
Q12A Cart API deve validar IsFromRecommendation?Sim — a Cart API valida item.IsFromRecommendation == true; false400. Dependência bloqueante: US separada que adiciona a flag em CartItemsModel