Skip to main content

Requisitos — Remove Recommended Item

Task #194882 · Endpoint DELETE api/payment/v2/{id:guid}/items/{cartItemId:int}​

Task pai: #194882 — [Back] 04 - Remover item recomendado Contexto: US 194871 — MVP Upsell (Pedido) Spec tecnica: a ser criada ADRs relacionados: ADR-005 · ADR-007 · ADR-008 · ADR-009

Status: refinado Sessão de grilling: 2026-06-19


Visão geral​

O cliente que adicionou um item recomendado ao pedido via zzlink pode desistir e removê-lo antes de finalizar o pagamento. O endpoint recebe o identificador do item já presente no cart, valida que é um item originado de uma recomendação, remove-o via Cart API e retorna os valores atualizados do pedido para que o front possa atualizar a tela sem recarregar.

O handler segue o mesmo padrão do AddRecommendedItemCommandHandler: lock distribuído, validação de status do payment, validação de SaleEcommerce, chamada à Cart API, atualização síncrona do Payment.Total e montagem do ResponseDTO.


Papéis​

  • Cliente: pessoa que acessa o zzlink para efetuar o pagamento
  • Checkout service: orquestrador da operação — valida, chama Cart API, atualiza Payment, registra alllogs
  • Cart API: responsável por aplicar soft-delete no item, desvincular a recomendação e recalcular totais do cart (defesa em profundidade)

Requisitos​

RF-01 — Receber e validar o contexto do pedido​

User Story: Como cliente no zzlink, quero remover um item recomendado que adicionei ao meu pedido, para que ele não seja incluído no pagamento que vou realizar.

Acceptance Criteria:

  1. WHEN o cliente chama DELETE api/payment/v2/{id:guid}/items/{cartItemId:int} THEN o sistema SHALL exigir autenticação via [Authorize(AuthenticationSchemes = "PaymentsScheme")] e validação de schema via [AddSchema] (header api-company-target obrigatório). O cartItemId é informado na rota (route param), não no body — o ASP.NET model binding garante que é um int válido via constraint {cartItemId:int}.
  2. WHEN o {id} (GUID do payment) não corresponde a nenhum payment THEN o sistema SHALL retornar 400 com mensagem "Payment não encontrado".
  3. WHEN o payment encontrado tem CartId <= 0 THEN o sistema SHALL retornar 400 com mensagem "Pagamento não possui carrinho associado".
  4. WHEN o payment encontrado tem Status diferente de Created ou CreateTransactionFail THEN o sistema SHALL registrar na alllogs com prefixo [Invalido] ("[Invalido] Tentativa de remover item recomendado {cartItemId} do carrinho {cartId} - {status}") e retornar 400 com mensagem "Não foi possível remover o item recomendado do pedido.".
  5. WHEN o payment encontrado tem Status igual a Created ou CreateTransactionFail THEN o sistema SHALL prosseguir com o processamento.
  6. WHEN o cart vinculado ao payment tem SaleEcommerce == true THEN o sistema SHALL retornar 400 com mensagem "Operação não disponível para este tipo de pedido".

Ordem das validações no handler: (1) payment não encontrado → (2) CartId <= 0 → (3) Status inválido (RF-01.4) → (4) busca cart e valida SaleEcommerce → (5) item em cart.Items (RF-02) → (6) FromRecommendation == true (RF-02). A validação de Status ocorre antes da busca do cart para evitar query desnecessária quando o payment já está em estado inválido.

Defesa em profundidade: A validação de SaleEcommerce ocorre em duas camadas: (1) no checkout service, que busca o CartInfoDTO via GetCartInfoAsync e valida cart.SaleEcommerce; (2) na Cart API, que valida cart.SaleEcommerce no RemoveCartItemHandler. A duplicação é intencional para proteger contra chamadas diretas à Cart API.


RF-02 — Validar o item a remover​

Acceptance Criteria:

  1. WHEN o cartItemId informado não corresponde a nenhum CartItem na coleção cart.Items (via CartItemsInfoDTO) THEN o sistema SHALL retornar 400 com mensagem "Item não encontrado no carrinho".
  2. WHEN o CartItem encontrado tem FromRecommendation == false THEN o sistema SHALL retornar 400 com mensagem "Item não removível".

RF-03 — Chamar a Cart API para remover o item​

Acceptance Criteria:

  1. WHEN todas as validações anteriores passam THEN o sistema SHALL chamar DELETE api/cart/{cartId}/items/{cartItemId} via CartIntegrationService.RemoveRecommendedItemAsync(cartId, cartItemId).
  2. WHEN a Cart API retorna sucesso (HTTP 200) THEN o sistema SHALL prosseguir para a atualização do Payment.Total.
  3. WHEN o CartIntegrationService retorna erros (falha de rede, timeout, exceção, ou erros de negócio propagados da Cart API) THEN o sistema SHALL registrar a falha na alllogs com detalhes e retornar os erros propagados via ReturnErrors(cartResult.Errors).

Mensagem genérica de erro: O CartIntegrationService converte todas as exceções (timeout, conexão, erro HTTP) em BaseResult.Errors com a mensagem "Não foi possível remover o item". Erros de negócio da Cart API (ex: "Operacao nao disponivel para este tipo de pedido", "Item nao originado de recomendacao") são propagados diretamente.

Idempotência fora de escopo: O comportamento idempotente da Cart API (HTTP 200 quando cart/item não existem) é definido e tratado pela Task 194883 — Cart API: Remove Item. As validações prévias deste handler (RF-01, RF-02) garantem que cart e item existem antes da chamada; qualquer divergência pós-chamada é tratada como erro de negócio propagado.


RF-04 — Atualizar o Payment e retornar o DTO consolidado​

Acceptance Criteria:

  1. WHEN a Cart API confirma sucesso THEN o sistema SHALL buscar o cart atualizado via GetCartInfoAsync(cartId) para obter o novo total.
  2. WHEN o Payment.Total é diferente do updatedCart.Total THEN o sistema SHALL atualizar sincronamente o campo Total do PaymentModel com o novo total e persistir via Update + SaveAsync.
  3. WHEN a atualização do Payment.Total falha com exceção THEN o sistema SHALL registrar a falha na alllogs (mensagem truncada em 512 chars) e retornar 400 com mensagem "Erro interno ao atualizar o pagamento.".
  4. WHEN o Payment.Total é atualizado com sucesso THEN o sistema SHALL retornar 200 com o RemoveRecommendedItemResponseDTO contendo:
    • values — Values recalculado com o novo total (construtor new Values(updatedCart))
    • products — List<Product> completa e atualizada dos CartItems (sem o item removido, cada item com cartItemId)
    • paymentMethods — PaymentMethods com parcelas recalculadas para o novo total
    • recommendations — RecommendationsInfoDTO? (wrapper com HoursToExpire e Items filtrados por CartItemId == null; recomendações anteriormente vinculadas ao item removido voltam a ficar disponíveis após o desvinculamento)

Após a remoção, o CartItemRecommendationModel tem CartItemId setado para null pela Cart API (RevertRecommendedItem), o que faz a recomendação reaparecer na lista de disponíveis (BuildAvailableRecommendations filtra por CartItemId == null).


RF-05 — Auditoria (alllogs)​

Acceptance Criteria:

  1. WHEN o handler inicia o processamento após validação de lock THEN o sistema SHALL registrar na alllogs "Tentativa de remover item recomendado {cartItemId} do carrinho {cartId}" com método "Handle-RemoveRecommendedItem".
  2. WHEN a Cart API retorna erros THEN o sistema SHALL registrar na alllogs "Erro ao chamar Cart API para remover item recomendado: {erros_concatenados}" com método "Handle-RemoveRecommendedItem".
  3. WHEN o Payment.Total muda após a remoção THEN o sistema SHALL registrar na alllogs "Valor do pedido mudou: {last} -> {new}" com método "Handle-RemoveRecommendedItem".
  4. WHEN ocorre exceção ao persistir o payment THEN o sistema SHALL registrar na alllogs "Erro ao persistir pagamento após remover item recomendado: {ex.Message_truncado_512}" com método "Handle-RemoveRecommendedItem".

Nota: O log de status inválido ([Invalido]) está definido em RF-01.4 (incorporado à validação de contexto).


RF-06 — Lock distribuído​

Acceptance Criteria:

  1. WHEN o handler inicia o processamento THEN o sistema SHALL adquirir lock distribuído via ILockService.LockAsync com chave "rec-item:{PaymentGuid}" e TTL de 10 segundos.
  2. WHEN o lock não pode ser adquirido (concorrência) THEN o sistema SHALL retornar HTTP 429 com mensagem "Pagamento em processamento.".
  3. WHEN o handler termina (sucesso ou erro) THEN o sistema SHALL liberar o lock via ILockService.ReleaseLockAsync.

Lock key compartilhada com ADD: A chave "rec-item:{PaymentGuid}" é a mesma usada pelo AddRecommendedItemCommandHandler, garantindo exclusão mútua entre operações de adicionar e remover no mesmo payment (evita race condition ADD/REMOVE concorrentes).


Contrato do endpoint​

Request​

DELETE api/payment/v2/{id:guid}/items/{cartItemId:int}
Authorization: Bearer {token PaymentsScheme}
api-company-target: {schema}

Sem body — RESTful: DELETE de sub-recurso identificado pela rota. Semanticamente correto para DELETE. O ASP.NET model binding valida que {cartItemId} é int via route constraint; se não-numérico, a request é rejeitada antes de chegar ao handler.

Response 200​

{
"values": {
"products": { "value": 0.0, "description": "R$ 0,00", "amount": 0 },
"discount": { "value": 0.0, "description": "R$ 0,00" },
"discountPix": { "value": 0.0, "description": "R$ 0,00" },
"bonus": { "value": 0.0, "description": "R$ 0,00" },
"subtotal": { "value": 0.0, "description": "R$ 0,00" },
"subtotalWhenPix": { "value": 0.0, "description": "R$ 0,00" },
"shipment": { "value": 0.0, "description": "R$ 0,00" },
"total": { "value": 0.0, "description": "R$ 0,00" },
"totalWhenPix": { "value": 0.0, "description": "R$ 0,00" }
},
"products": [
{
"name": "string",
"size": "string",
"amount": 0,
"fullPrice": 0.0,
"price": 0.0,
"total": 0.0,
"url": "string",
"fromRecommendation": false,
"cartItemId": 0
}
],
"paymentMethods": {
"creditCard": true,
"pix": { "operator": 0, "key": "string", "discountPercentage": null, "discountValue": null },
"boleto": false,
"installments": [
{ "installment": "1", "value": 0.0, "description": "1x de R$ 0,00" }
],
"isTwoCreditCardsAllowed": false
},
"recommendations": {
"hoursToExpire": "0:00",
"items": [
{
"recommendedItemId": 0,
"productId": 0,
"sku": "string",
"productName": "string",
"description": "string",
"thumbnail": "string",
"images": [],
"fullPrice": 0.0,
"discountValue": 0.0,
"discountType": 0,
"size": "string"
}
]
}
}

Responses 401​

CenárioMensagem
Header api-company-target ausente ou vazio"Schema invalid"
Token JWT inválido ou ausente(401 padrão do ASP.NET)

Responses 429​

CenárioMensagem
Lock não adquirido (concorrência)"Pagamento em processamento."

Responses 400​

CenárioMensagem
Payment não encontrado"Payment não encontrado"
Payment sem carrinho associado"Pagamento não possui carrinho associado"
Status do payment inválido"Não foi possível remover o item recomendado do pedido."
Cart com SaleEcommerce == true"Operação não disponível para este tipo de pedido"
cartItemId não encontrado no cart"Item não encontrado no carrinho"
Item com FromRecommendation == false"Item não removível"
Erro da Cart API (propagado)Mensagens da Cart API ou "Não foi possível remover o item"
Erro ao persistir Payment.Total"Erro interno ao atualizar o pagamento."

Dependências​

DependênciaReferênciaStatus
Cart API — endpoint DELETE api/cart/{cartId}/items/{cartItemId}Task 194883 — Cart API: Remove ItemImplementado
Flag FromRecommendation em CartItemsModelCartItemsModel.cs:57 (FromRecommendation bool, coluna "FromRecommendation")Disponível
cartItemId exposto no Product DTOADR-008 · GetInfoBase.cs (Product.CartItemId)Implementado
CartIntegrationService.RemoveRecommendedItemAsyncA ser implementado (novo método)Pendente
RemoveRecommendedItemCommandA ser implementado (novo command)Pendente
RemoveRecommendedItemCommandHandlerA ser implementado (novo handler)Pendente
RemoveRecommendedItemResponseDTOA ser implementado (mesmo shape do AddRecommendedItemResponseDTO)Pendente
Lock distribuído (ILockService)Já utilizado em AddRecommendedItemCommandHandlerDisponível
LogQueueService + LogQueueMessageEventJá utilizado em AddRecommendedItemCommandHandlerDisponível

Sem RemoveRecommendedItemValidator: Não será criado validator FluentValidation. A validação de cartItemId é feita no handler (busca em cart.Items por FirstOrDefault(i => i.Id == command.CartItemId)). O ASP.NET model binding já garante que cartItemId é um int válido via route constraint {cartItemId:int}. Se não-numérico, a request é rejeitada antes de chegar ao handler. Esta decisão elimina complexidade desnecessária (decorre do design RESTful com route param).


Fora de escopo​

  • Lógica interna da Cart API (soft-delete, desvinculação de recomendação, recálculo de totais) — responsabilidade da Task 194883
  • Adição de item recomendado — Task 194880 — Add Recommended Item
  • Validação de janela de validade (1h) — não se aplica ao remove; o item já foi aceito e está no cart

Questões abertas​

Nenhuma.