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 oPaymentStatusnã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:
- WHEN a requisição chega com
x-api-keyválida para a chave"internal"THEN o sistema SHALL processar a requisição normalmente - WHEN a requisição chega sem
x-api-keyou com valor inválido THEN o sistema SHALL retornar401 - 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:
- WHEN a requisição é
DELETE api/cart/{cartId}/items/{cartItemId}com ApiKey válida THEN o sistema SHALL processar a remoção do item - WHEN
cartIdnão é informado na rota THEN o sistema SHALL retornar400 - WHEN
cartItemIdnão é informado na rota THEN o sistema SHALL retornar400 - O endpoint não possui body — todos os identificadores estão na rota
REQ-03 — Buscar Cart
Critérios de Aceite:
- WHEN o endpoint é chamado com
cartIdTHEN o sistema SHALL buscar oCartModelpelocartIdviaGetByIdAsynccom tracking habilitado - WHEN o cart não é encontrado THEN o sistema SHALL retornar
400com mensagem indicando cart não encontrado - WHEN o cart é encontrado THEN o sistema SHALL prosseguir com a busca do item
GetByIdAsynccom tracking — mesmo padrão doHandle(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:
- WHEN o cart é encontrado THEN o sistema SHALL não validar
CartStatuscomo critério de bloqueio da remoção - A responsabilidade de garantir que o payment não está pago é do checkout service, que verifica
PaymentStatusantes de chamar este endpoint
Decisão de escopo: O
CartStatusemCartModelnão é atualizado de forma confiável após o pagamento — o estado autoritativo de pagamento está emPaymentsModel.Status(PaymentStatus), que pertence ao contexto do checkout service. A Cart API não tem acesso aoPaymentsModel. O padrão de validação dePaymentStatusantes de modificar o cart já está estabelecido no checkout service (ex:EcommerceUpdateCartServicelinha 35 — permite apenasCreatedePending).
REQ-04 — Buscar CartItem
Critérios de Aceite:
- WHEN o cart é encontrado THEN o sistema SHALL buscar o
CartItemsModelcomId == cartItemIdANDDateDeleted == nulldentro da coleçãoCartItemsdo cart carregado - WHEN o item não é encontrado na coleção (inexistente ou pertencente a outro cart) THEN o sistema SHALL retornar
400com mensagem genérica indicando item não encontrado - A busca é restrita à coleção do cart carregado — não é feita busca global por
cartItemId; umcartItemIdválido em outro cart retorna400com a mesma mensagem
Resposta
400para ambos os casos (cart não encontrado e item não encontrado) — consistente com o padrão doPOST 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:
- WHEN o item é encontrado THEN o sistema SHALL verificar se
item.IsFromRecommendation == true - WHEN
item.IsFromRecommendation == falseTHEN o sistema SHALL retornar400com mensagem indicando que o item não é removível por este endpoint - WHEN
item.IsFromRecommendation == trueTHEN o sistema SHALL prosseguir com a remoção
Dependencia bloqueante:
IsFromRecommendation(bool) nao existe hoje emCartItemsModel. 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:
- WHEN o item é encontrado THEN o sistema SHALL chamar
CartModel.RemoveItem(cartItemId), que executaitem.Delete()(soft-delete viaBaseEntity.Delete()— setaDateDeleted) - WHEN o soft-delete é aplicado THEN o sistema SHALL chamar
CartModel.CalculateTotal() - WHEN
CalculateTotal()é executado THEN o sistema SHALL somar apenas itens comDateDeleted == null— o item removido não deve compor o novo total - WHEN os totais são recalculados THEN o sistema SHALL chamar
_cartRepository.Update(cart)seguido deSaveAsync() - WHEN
Update(cart)é chamado THEN o sistema SHALL invalidar automaticamente o cacheCacheKeys.CartById(cart.Id)— comportamento já implementado emCartRepository.Update()linha 79
RemoveItemé um novo método a criar emCartModel(coezzion-db-core). Ele apenas aplicaitem.Delete()— não chamaCalculateTotal()internamente; o service chama separadamente, consistente com o padrão do add-item.
CalculateTotal()requer ajuste para filtrarDateDeleted == nullantes de somar — atualmente itera toda a coleção sem filtro (linha 209 deCartModel.cs).
REQ-06 — Persistir e Retornar
Critérios de Aceite:
- WHEN a persistência é bem-sucedida THEN o sistema SHALL retornar
200sem body - WHEN ocorre erro inesperado na persistência THEN o sistema SHALL retornar
500
Retorno
200sem body — idêntico ao padrão doDELETE api/cartexistente (Handle(DeleteCartCommand)retornaBaseResultsem payload). O checkout service não depende de dados de retorno da Cart API — relê o cart diretamente do banco de leitura viaGetCartInfoAsync(sem cache, conexão read-only).
Resumo de Respostas HTTP
| Código | Situação |
|---|---|
200 | Item removido, totais recalculados e cart persistido com sucesso |
400 | cartId ou cartItemId ausentes na rota |
400 | Cart não encontrado |
400 | Item não encontrado no cart (inexistente ou pertencente a outro cart) |
400 | Item não originado de recomendação (IsFromRecommendation == false) |
401 | ApiKey ausente ou inválida |
500 | Erro inesperado na persistência |
Artefatos a Criar / Modificar
| Artefato | Projeto | Tipo |
|---|---|---|
CartModel.RemoveItem(int cartItemId) | coezzion-db-core | Novo método |
CartModel.CalculateTotal() — filtrar DateDeleted == null | coezzion-db-core | Ajuste |
CartItemsModel.IsFromRecommendation (bool) | coezzion-db-core | Dependência bloqueante (US separada) |
RemoveCartItemCommand | coezzion-service-cart | Novo command |
ICartService.Handle(RemoveCartItemCommand) | coezzion-service-cart | Nova assinatura |
CartService.Handle(RemoveCartItemCommand) | coezzion-service-cart | Nova implementação |
CartController — DELETE api/cart/{cartId}/items/{cartItemId} | coezzion-service-cart | Novo endpoint |
Fora de Escopo
- Validação de
CartStatus/PaymentStatus— oCartStatusnão é atualizado de forma confiável após pagamento; a validação de que o payment não foi pago é responsabilidade do checkout service (viaPaymentStatusdoPaymentsModel) - Remoção física de linha no banco — sempre soft-delete via
BaseEntity.Delete() - Retorno de dados do cart no body da resposta —
200sem body - Suporte a remoção de múltiplos itens por chamada — um item por request
Questões Fechadas (grilling 2026-05-15)
| # | Questão | Decisão |
|---|---|---|
| Q1 | Método de busca do cart (tracking vs. no-tracking)? | GetByIdAsync com tracking — mesmo padrão do DeleteCartCommand |
| Q2 | Soft-delete ou remoção física? | Soft-delete via item.Delete() — padrão do sistema |
| Q3 | RemoveItem encapsula CalculateTotal ou service chama separado? | Service chama separado — consistente com padrão do add-item |
| Q4 | CalculateTotal filtra itens deletados? | Não filtra hoje — ajuste necessário: Where(x => x.DateDeleted == null) |
| Q5 | Risco de propagação read replica após commit Cart API? | Mesmo risco já aceito no add-item; GetCartInfoAsync usa read-only sem cache |
| Q6 | Nome do command? | RemoveCartItemCommand |
| Q7 | Assinatura no ICartService? | Task<BaseResult> Handle(RemoveCartItemCommand command) |
| Q8 | 404 ou 400 para recursos não encontrados? | 400 — consistente com add-item |
| Q9 | Busca do item: global ou restrita ao cart? | Restrita à coleção do cart carregado; qualquer resultado nulo → 400 genérico |
| Q10 | Invalidação de cache necessária? | Coberta por CartRepository.Update() — CacheKeys.CartById(cart.Id) já invalidado automaticamente |
| Q11 | A 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) |
| Q12 | A Cart API deve validar IsFromRecommendation? | Sim — a Cart API valida item.IsFromRecommendation == true; false → 400. Dependência bloqueante: US separada que adiciona a flag em CartItemsModel |