Skip to main content

Design Document (v2 — revisado)

Versão: 2.0 — revisão fundamentada na análise das entidades reais (Core.OrgDB, Link.OrgDB) e do código do coezzion-service-checkout e coezzion-service-cart. Substitui: desing-doc.md (v1). Data da revisão: 2026-06-15.


Overview

Este design define a arquitetura e componentes necessários para implementar o endpoint POST api/payment/v2/{id}/add-recommended-item no coezzion-service-checkout. O endpoint permite que um cliente no zzlink adicione um item recomendado pelo vendedor ao seu pedido Store antes de finalizar o pagamento.

O fluxo principal segue o padrão CQRS existente no projeto: Controller → Command → Handler → Services/Repositories, usando ZZMediator para orquestração. O handler coordena validações locais, lock distribuído, chamada à API interna do coezzion-service-cart e atualização do pagamento, retornando um DTO consolidado com os dados atualizados do pedido.

Escopo de tipo de pagamento

  • Atende: pagamentos Store (PaymentsModel de Link.OrgDB.Entities).
  • Não atende: pagamentos Ecommerce (PaymentsEcommModel) — rejeitados na validação.

Decisões de Design

  1. Command (não Query): a operação modifica estado (adiciona item ao carrinho via API interna e atualiza Payment.Total), portanto é modelada como ICommand<BaseResult>.

  2. Handler único com lock distribuído: toda a orquestração (validação, chamada externa, persistência, montagem de resposta) ocorre no handler, seguindo o padrão de CreatePaymentCommandHandler. Obrigatório uso de ILockService.LockAsync/ReleaseLockAsync em try/finally para evitar race condition sobre o mesmo pagamento — padrão universal em handlers que mutam PaymentsModel.

  3. Serviço dedicado para a API interna de carrinho: a chamada HTTP para adicionar o item é encapsulada em ICartIntegrationService, usando HttpClient registrado no DI com Polly retry (mesmo padrão de EcommerceBaseService, porém apontando para o coezzion-service-cart). O endpoint alvo será criado em outra tarefa (ver seção Dependências Externas).

  4. Reutilização de DTOs existentes: Values, Product, PaymentMethods e Installments do GetInfoBase (Checkout.Domain/DTO/GetInfo/GetInfoBase.cs) são reutilizados; apenas AddRecommendedItemResponseDTO e RecommendedItemDTO são novos.

  5. Acesso a CartItemRecommendation via extensão de ICartRepository: a entidade CartItemRecommendationModel já é navigation de CartModel (Cart.CartItemRecommendations). Não criar repositório dedicado — estender ICartRepository com um método que faça .Include(x => x.CartItemRecommendations), reaproveitando a infraestrutura canônica de acesso a cart no checkout.

  6. Validação Store vs Ecommerce via Cart.SaleEcommerce: PaymentsModel (Link.OrgDB.Entities) não possui propriedade Type/PaymentType. A validação de tipo é feita indiretamente: carrega-se o CartModel via PaymentsModel.CartId e verifica-se Cart.SaleEcommerce == false (Store). Se SaleEcommerce == true, rejeitar com "Operação não disponível para este tipo de pedido".


Architecture

Diagrama de Fluxo

Camadas e Responsabilidades

CamadaComponenteResponsabilidade
APIPaymentControllerRecebe request, seta TransactionId, despacha command via mediator, converte BaseResult em HTTP
APIAddRecommendedItemCommandDTO do command com PaymentGuid e RecommendedItemId
APIAddRecommendedItemValidatorFluentValidation do command (roda automático no pipeline do ZZMediatR)
APIAddRecommendedItemCommandHandlerOrquestração: validações, lock, chamada Cart API, atualização, resposta
DomainICartRepository (extensão)Novo método GetCartWithRecommendationsAsync(int cartId)
DomainICartIntegrationService (novo)Contrato para chamada HTTP ao coezzion-service-cart
DomainAddRecommendedItemResponseDTODTO de resposta
DomainRecommendedItemDTODTO de item recomendado
InfrastructureCartRepository (extensão)Implementa GetCartWithRecommendationsAsync com .Include(CartItemRecommendations)
InfrastructureCartIntegrationService (novo)HttpClient + Polly para coezzion-service-cart

Components and Interfaces

Novos Arquivos

ArquivoCamadaTipo
src/Checkout.API/Application/Messages/Commands/RecommendedItemCommands/AddRecommendedItemCommand.csAPICommand
src/Checkout.API/Application/Messages/Commands/RecommendedItemCommands/AddRecommendedItemCommandHandler.csAPIHandler
src/Checkout.API/Application/Validators/RecommendedItem/AddRecommendedItemValidator.csAPIValidator
src/Checkout.Domain/DTO/RecommendedItem/AddRecommendedItemResponseDTO.csDomainDTO
src/Checkout.Domain/DTO/RecommendedItem/RecommendedItemDTO.csDomainDTO
src/Checkout.Domain/Interfaces/Services/Cart/ICartIntegrationService.csDomainInterface
src/Checkout.Infraestructure/Integrations/Cart/CartIntegrationService.csInfrastructureService

Arquivos Modificados

ArquivoModificação
src/Checkout.API/Controllers/PaymentController.csNovo endpoint AddRecommendedItem
src/Checkout.Domain/Interfaces/Repositories/Read/ICartRepository.csNovo método GetCartWithRecommendationsAsync
src/Checkout.Infraestructure/Data/Repositories/Read/CartRepository.csImplementação de GetCartWithRecommendationsAsync
src/Checkout.API/Configuration/DependencyInjectionConfig.csRegistro de ICartIntegrationService + HttpClient + Polly

Interfaces

// Extensão de ICartRepository (NÃO criar ICartRecommendedItemRepository)
public interface ICartRepository
{
// ... métodos existentes (GetByIdAsync, GetBasicCartByIdAsync, etc.)
Task<CartModel> GetCartWithRecommendationsAsync(int cartId); // NOVO
}

// Novo serviço de integração com coezzion-service-cart
public interface ICartIntegrationService
{
Task<BaseResult> AddRecommendedItemAsync(int cartId, int recommendationId);
}

Implementação canônica do ICartIntegrationService

Seguir o padrão de EcommerceBaseService + EcommerceCartService:

  • HttpClient injetado pelo DI factory com AddHttpClient<ICartIntegrationService, CartIntegrationService>().
  • .AddPolicyHandler(PollyPolicy.WaitAndRetryCheckoutHttpResponse()) — mesmo policy das integrações Braspag/PagarMe/hybris.
  • Logging de request + response + URL via LogQueueMessageEvent.SaveLogOrderHistoryMessage(schema, paymentId, description, url, request, response, method) (fire-and-forget).
  • Retorno via BaseResult (não exceção): sucesso → BaseResult.With(response); erro → BaseResult.Errors.Add(...) + Response = new TooManyRequestDto(...) quando aplicável.
  • Endpoint alvo: TBD — será criado em outra tarefa no coezzion-service-cart (ver seção Dependências Externas).

Data Models

AddRecommendedItemCommand

using Coezzion.Common.Communication;
using Coezzion.Common.ZZMediator.Abstractions;

namespace Checkout.API.Application.Messages.Commands.RecommendedItemCommands;

public class AddRecommendedItemCommand : ICommand<BaseResult>
{
public Guid PaymentGuid { get; set; }
public int RecommendedItemId { get; set; } // CartItemRecommendationModel.Id
}

AddRecommendedItemResponseDTO

public record AddRecommendedItemResponseDTO
{
public Values Values { get; init; } // reutilizado de GetInfoBase
public List<Product> Products { get; init; } // reutilizado de GetInfoBase
public PaymentMethods PaymentMethods { get; init; } // reutilizado de GetInfoBase
public List<RecommendedItemDTO> RecommendedItems { get; init; }
}

RecommendedItemDTO

Atenção: os campos ProductName, Sku, Url não existem como colunas em CartItemRecommendationModel. Devem ser obtidos via: (a) navigation ProductModel carregada junto, ou (b) DTO retornado pela API interna do coezzion-service-cart.

A opção (b) é preferida pois a API interna já terá montado esses dados ao processar o add-item.

public record RecommendedItemDTO
{
public int Id { get; init; } // CartItemRecommendationModel.Id
public string ProductName { get; init; } // de ProductModel ou resposta Cart API
public string Size { get; init; } // CartItemRecommendationModel.Size
public decimal Price { get; init; } // CartItemRecommendationModel.UnitPrice
public string Url { get; init; } // de ProductModel ou resposta Cart API
public string Sku { get; init; } // de ProductModel ou resposta Cart API
public bool HasEmployeeDiscount { get; init; } // CartItemRecommendationModel.HasEmployeeDiscount
}

Entidade CartItemRecommendationModel (Core.OrgDB — JÁ EXISTE)

Arquivo: coezzion-db-core/src/Core.OrgDB/Entities/CartItemRecommendationModel.cs DbSet: CoreOrgDbContext.CartItemRecommendations

namespace Core.OrgDB.Entities;

public class CartItemRecommendationModel : BaseEntity // herda DateCreated/Updated/Deleted
{
public int CartId { get; set; }
public int ProductId { get; set; }
public int? CartItemId { get; set; } // FK opcional para CartItemsModel quando a recomendação virou item
public string Size { get; set; }
public DiscountType DiscountType { get; set; } // None=0, Percentage=1, Value=2
public decimal DiscountValue { get; set; }
public decimal UnitPrice { get; set; }
public decimal FullPrice { get; set; }
public bool HasEmployeeDiscount { get; set; }

// Navigation Properties
public CartModel Cart { get; set; }
public ProductModel Product { get; set; }
public CartItemsModel CartItem { get; set; } // item efetivado (se CartItemId != null)
}

Campos que NÃO existem nesta entidade (remover do design v1): ProductName, Sku, Price, Url.

Semântica do par recomendação ↔ item efetivado:

  • CartItemRecommendationModel.CartItemId != null → a recomendação já foi convertida em um CartItemsModel.
  • CartItemsModel.FromRecommendation == true → marca que o item efetivo do carrinho veio de uma recomendação.
  • Esses dois campos formam o par que liga a recomendação ao item efetivado.

Entidade PaymentsModel (Link.OrgDB — JÁ EXISTE)

Arquivo: coezzion-db-link/src/Link.OrgDB/Entities/PaymentsModel.cs

namespace Link.OrgDB.Entities;

public class PaymentsModel : BaseEntity, IAggregateRoot
{
public int CartId { get; set; }
public PaymentStatus Status { get; set; }
public DateTime DateExpiration { get; set; }
public decimal Total { get; set; }
public Guid PaymentGuid { get; set; }
public string TransactionId { get; set; }
public bool NPSDone { get; set; }
public bool Retry { get; set; }
// ... demais campos

// EF Navigations
public IEnumerable<PaymentsHistoryAllLogsModel> PaymentsHistoryAllLogs { get; set; }
// ...
}

Crítico: PaymentsModel NÃO possui propriedade Type ou PaymentType. A distinção Store vs Ecommerce é feita via CartModel.SaleEcommerce (bool). Para este endpoint, só deve prosseguir se Cart.SaleEcommerce == false.

Entidade CartModel (Core.OrgDB — JÁ EXISTE, fragmento relevante)

Arquivo: coezzion-db-core/src/Core.OrgDB/Entities/CartModel.cs

public class CartModel : BaseEntity, IAggregateRoot
{
public int StoreId { get; set; }
public int CustomerId { get; set; }
public int? UserId { get; set; }
public bool SaleEcommerce { get; set; } // <-- usado para validar Store vs Ecommerce
public decimal Total { get; set; }
public decimal ItemsTotal { get; set; }
public CartStatus CartStatus { get; set; }
// ...

// Navigation Properties relevantes
public ICollection<CartItemsModel> CartItems { get; set; }
public ICollection<CartItemRecommendationModel> CartItemRecommendations { get; set; } // <-- Navigation
public CustomerModel Customer { get; set; }
public StoreModel Store { get; set; }
}

Diagrama de Relacionamento

Nota: PaymentsModel reside em schema do Link.OrgDB e referencia CartId que é entidade do Core.OrgDB. Não há FK física cross-schema — a relação é lógica, resolvida em código pela aplicação.


Correctness Properties

Property 1: Time window validation is consistent

For any Cart.DateCreated value and current time now, the recommendation validity check SHALL accept the operation if and only if Cart.DateCreated + 1 hour >= now, and SHALL reject with "Recomendação não está mais disponível" otherwise.

Âncora confirmada: CartModel.DateCreated (herdado de BaseEntity), não CartItemRecommendationModel.DateCreated. O CartModel deve ser carregado via GetCartWithRecommendationsAsync antes da validação.

Validates: Requirements 3.3

Property 2: Cart API non-success error categorization

For any non-success response da API interna do coezzion-service-cart que não seja especificamente um erro de out-of-stock, o handler SHALL retornar "Não foi possível adicionar o item" como mensagem de erro, independentemente do HTTP status code ou payload recebido.

Dependência: o mapeamento exato (estoque vs genérico) depende do contrato de erro retornado pelo endpoint interno, que será definido na tarefa de criação do coezzion-service-cart. Assumir inicialmente o mesmo formato BaseResult (Coezzion.Common) com Errors como lista de strings; marcador canônico de "out_of_stock" a confirmar no contrato.

Validates: Requirements 4.4, 4.5

Property 3: Response DTO completeness on success

For any successful execution of the add-recommended-item operation (Cart API success + Payment.Total persisted), the response SHALL contain non-null values, products, paymentMethods, and recommendedItems fields, where:

  • products reflete os itens atualizados do carrinho;
  • recommendedItems exclui itens cuja recomendação já foi efetivada (i.e., onde CartItemRecommendationModel.CartItemId != null).

Correção vs v1: v1 dizia "excludes items whose SKU matches any item already in the cart". A semântica real é via CartItemId != null (par FromRecommendation/CartItemId), não por match de SKU.

Validates: Requirements 5.2

Property 4: Log message truncation

For any error description string of arbitrary length, the log message recorded via PaymentsHistoryAllLogs SHALL have length less than or equal to 512 characters.

Origem da regra: PaymentsHistoryAllLogsLogEvent.Description não tem limite explícito no modelo do checkout. A regra de 512 chars é decisão de design (truncamento defensivo para evitar estouro de coluna/SLA de logs). Implementar com helper TruncateTo(desc, 512).

Validates: Requirements 6.3

Property 5: Input validation rejects non-positive recommendedItemId

For any integer value less than or equal to zero provided as recommendedItemId, the validator SHALL reject the request with a validation error message in the response body.

Validates: Requirements 7.2


Error Handling

Estratégia de Erros

Seguindo o padrão existente do projeto, erros de negócio são adicionados via AddError("mensagem em pt-BR") (ou helper WithError(...)) e retornados no BaseResult. Nunca são lançadas exceções para erros de negócio. O controller converte BaseResult em HTTP via CustomResponse(result):

  • Errors.Count > 0400 BadRequest com { Title, Status, Errors: { Messages } }.
  • Response is TooManyRequestDto429.
  • Response == null sem erros → 200 OK sem body.
  • Response != null sem erros → 200 OK com body.

Tabela de Erros (revisada)

CenárioStatusMensagemObservação
Token ausente/inválido401(sem body - middleware)[Authorize(PaymentsScheme)]
GUID inválido na rota400"Payment não encontrado"
Payment não encontrado400"Payment não encontrado"GetByGuidPaymentAsync retorna null
Lock não adquirido (race)429"Pagamento em processamento"BaseResult.WithTooManyRequest(...)
Cart não encontrado400"Carrinho não encontrado"
Cart.SaleEcommerce == true400"Operação não disponível para este tipo de pedido"substitui check de Type inexistente
CartId nulo/zero400"Pagamento não possui carrinho associado"
Recomendação não encontrada400"Recomendação não encontrada"Cart.CartItemRecommendations não contém o Id
Recomendação de outro carrinho400"Recomendação não encontrada"CartId mismatch
Janela expirada400"Recomendação não está mais disponível"Cart.DateCreated + 1h < now
Produto sem estoque400"Produto sem estoque na loja"Depende do contrato da API interna (TBD)
Erro genérico Cart API400"Não foi possível adicionar o item"
Timeout Cart API400"Não foi possível adicionar o item"Polly retry esgotado
Falha persistência500"Erro interno ao atualizar o pagamento"try/catch no handler
Falha montagem DTO500"Erro interno ao montar a resposta"try/catch no handler
recommendedItemId <= 0400"O campo recommendedItemId deve ser um número inteiro positivo"FluentValidation

Tratamento de Exceções no Handler

Padrão retirado de CreatePaymentCommandHandler + EcommerceCommandHandler:

public async Task<BaseResult> Handle(AddRecommendedItemCommand command, CancellationToken cancellationToken)
{
var payment = await _paymentRepository.GetByGuidPaymentAsync(command.PaymentGuid);
if (payment is null)
return WithError("Payment não encontrado.");

var lockKey = $"add-rec-item:{payment.Id}";
try
{
var locked = await _lockService.LockAsync(lockKey, payment.Id.ToString(), TimeSpan.FromSeconds(10));
if (!locked)
return BaseResult.WithTooManyRequest("Pagamento em processamento.");

var cart = await _cartRepository.GetCartWithRecommendationsAsync(payment.CartId);
if (cart is null)
return WithError("Carrinho não encontrado.");
if (cart.SaleEcommerce)
return WithError("Operação não disponível para este tipo de pedido.");

// ... validação da recomendação, janela, chamada à Cart API, update do Total ...

var dto = await BuildResponseAsync(cart, payment);
return BaseResult.With(dto);
}
catch (Exception ex)
{
_ = _logQueueService.EnqueueAsync(LogQueueMessageEvent.SaveLogOrderHistoryMessage(
_schema, payment.Id, ex));
return WithError("Erro interno ao atualizar o pagamento.");
}
finally
{
await _lockService.ReleaseLockAsync(lockKey, payment.Id.ToString());
}
}

private BaseResult WithError(string message)
{
BaseResult.Errors.Add(message);
return BaseResult;
}

Mapeamento de Erros da Cart API (interna)

O handler identifica o tipo de erro com base na resposta da ICartIntegrationService.AddRecommendedItemAsync:

  • Se BaseResult retornado indica "out_of_stock" (marcador exato a confirmar no contrato da API interna — TBD) → "Produto sem estoque na loja".
  • Qualquer outro erro (5xx, validation, timeout após Polly, connection) → "Não foi possível adicionar o item".

Não usar ResponseErrorDTO do hybris — a API interna é Coezzion.Common e retorna BaseResult.


Testing Strategy

Testes Unitários do Handler (xUnit + Moq)

CenárioResultado esperado
Payment não encontrado (GetByGuidPaymentAsync → null)erro "Payment não encontrado"
Lock não adquirido429 "Pagamento em processamento"
Cart não encontradoerro "Carrinho não encontrado"
Cart.SaleEcommerce == trueerro "Operação não disponível para este tipo de pedido"
Payment sem CartIderro "Pagamento não possui carrinho associado"
Recomendação não encontrada em cart.CartItemRecommendationserro "Recomendação não encontrada"
Recomendação com CartId mismatcherro "Recomendação não encontrada"
Janela expirada (Cart.DateCreated + 1h < now)erro "Recomendação não está mais disponível"
Cart API sucessoatualiza Payment.Total e retorna DTO
Cart API sem estoqueerro "Produto sem estoque na loja"
Cart API erro genéricoerro "Não foi possível adicionar o item"
Cart API timeout (Polly esgotado)erro "Não foi possível adicionar o item"
Falha persistência (SaveAsync throws)erro 500 "Erro interno ao atualizar o pagamento"
Lock adquirido e release sempre executadoverify ReleaseLockAsync chamado no finally
LogQueueService.EnqueueAsync chamado nos caminhos de erroverify

Testes Unitários do Validator (FluentValidation)

InputResultado
recommendedItemId == 0falha
recommendedItemId < 0falha
recommendedItemId > 0passa
PaymentGuid == Guid.Emptyfalha
PaymentGuid válidopassa

Testes Unitários do CartIntegrationService

  • Construção do request (URL, body, headers).
  • Parsing da resposta de sucesso → BaseResult.With(...).
  • Parsing de erro de estoque → mapeamento específico.
  • Parsing de erro genérico → "Não foi possível adicionar o item".
  • Logging via EnqueueAsync chamado com request + response + URL.
  • Timeout/HttpClient exception → erro genérico.

Testes Unitários do CartRepository.GetCartWithRecommendationsAsync

  • Retorna CartModel com CartItemRecommendations populada.
  • Filtra por Id == cartId.
  • AsNoTracking() aplicado.

Testes de Propriedade (FsCheck via xUnit)

Biblioteca: FsCheck.Xunit. Mínimo 100 iterações por teste.

PropertyTag
1 — Time window validation is consistentFeature: add-recommended-item, Property 1
2 — Cart API non-success error categorizationFeature: add-recommended-item, Property 2
3 — Response DTO completeness on success (CartItemId != null)Feature: add-recommended-item, Property 3
4 — Log message truncation (<= 512 chars)Feature: add-recommended-item, Property 4
5 — Input validation rejects non-positive recommendedItemIdFeature: add-recommended-item, Property 5

Testes de Integração

  • Endpoint completo com auth válida (PaymentsScheme) e dados reais.
  • Mock do coezzion-service-cart via WebApplicationFactory/WireMock.
  • Endpoint sem token → 401.
  • Endpoint com GUID inválido na rota → 400.
  • Fluxo de lock: segunda chamada concorrente → 429.

Cobertura Esperada

  • Handler: validações, orquestração, lock, mapeamento de erros, finally release.
  • Validator: todas as regras FluentValidation.
  • CartIntegrationService: construção do request, parsing da response, Polly config.
  • CartRepository.GetCartWithRecommendationsAsync: query + Include.

Dependências Externas / Open Questions

DependênciaDescriçãoStatus
coezzion-service-cart — endpoint AddItemAPI interna para adicionar item recomendado ao carrinho. Será criado em outra tarefa. Contrato esperado: POST api/cart/{cartId}/recommendations/{recommendationId}/add (sugestão), retorna BaseResult (Coezzion.Common) com novo total e/ou dados do item.🔴 TBD
Contrato de erro "out_of_stock"Marcador canônico para diferenciar erro de estoque de erro genérico na resposta da API interna.🔴 TBD (depende da tarefa acima)
CartItemRecommendationModelEntidade Core.OrgDB já existe com migration.🟢 disponível
Multitenancy via IUserProvider.GetSchemaName()Padrão existente, já injetado nos handlers.🟢 disponível
ILockServiceJá registrado e usado em CreatePaymentCommandHandler.🟢 disponível
Recaptcha no endpointEndpoints públicos PaymentsScheme tipicamente usam IRecaptchaService.Validate.🟡 a confirmar com o requisito da US 194880

Questões em Aberto

  1. Recaptcha: o endpoint add-recommended-item deve ter gate de Recaptcha (como getInfo) ou não? Confirmar com a US 194880.
  2. Contrato da API interna: qual o formato exato de request/response do endpoint no coezzion-service-cart? (Definido na tarefa que criará o endpoint.)
  3. Atualização do Payment.Total: o novo total vem da resposta da API interna, ou deve ser recalculado localmente a partir de Cart.Total após a chamada? Definir para evitar divergência.
  4. Autorização do endpoint alvo: o coezzion-service-cart usará [UseApiKey("internal")] ou JWT service-to-service? Alinhar com a tarefa do service-cart.
  5. Cache invalidation: ao estender ICartRepository com GetCartWithRecommendationsAsync, definir se o decorator CacheCartRepositoryDecorator deve invalidar a chave CacheKeys.CartById(cartId) após a mutação (sim, provavelmente — documentar no handler).

Registro de DI (referência)

Em src/Checkout.API/Configuration/DependencyInjectionConfig.cs:

// ICartRepository já registrado com decorator de cache — apenas adicionar método na interface/impl
// (sem mudança no registro DI)

// NOVO: registro do CartIntegrationService com HttpClient + Polly
services.AddHttpClient<ICartIntegrationService, CartIntegrationService>()
.AddPolicyHandler(PollyPolicy.WaitAndRetryCheckoutHttpResponse());

// ILockService, ILogQueueService, IPaymentsRepository, IUserProvider, ITransactionContext — já registrados

Referências de Código (padrões canônicos a seguir)

PadrãoArquivo de referência
Handler com lock + try/finally + logCheckout.API/Application/Messages/Commands/CreateCommands/CreatePaymentCommandHandler.cs
Handler multi-command com chamada HTTP externaCheckout.API/Application/Messages/Commands/EcommerceCommands/EcommerceCommandHandler.cs
Service de integração HTTP + log + error parsingCheckout.Infraestructure/Integrations/Ecommerce/EcommerceCartService.cs
HttpClient base com Polly + cookie/timeoutCheckout.Infraestructure/Integrations/Ecommerce/EcommerceBaseService.cs
Controller endpoint POST v2 + CustomResponseCheckout.API/Controllers/PaymentController.cs (linhas 42-51, 374-428)
Validator FluentValidationCheckout.API/Application/Validators/Ecommerce/PostCartValidator.cs
BaseResult extensions (WithError, With, WithTooManyRequest)Checkout.Domain/Utils/BaseResultExtensions.cs
LogEvent factoriesCheckout.Domain/Events/LogQueueMessageEvent.cs
DI + Polly + HttpClientCheckout.API/Configuration/DependencyInjectionConfig.cs (linhas 178-232)
CartModel + CartItemRecommendations navigationCore.OrgDB/Entities/CartModel.cs:131
CartItemRecommendationModel (entidade)Core.OrgDB/Entities/CartItemRecommendationModel.cs
PaymentsModel (entidade Store)Link.OrgDB/Entities/PaymentsModel.cs
PaymentType enumCheckout.Domain/Enums/PaymentType.cs

Resumo das mudanças vs v1

Itemv1 (errado/impreciso)v2 (corrigido)
Entidade de item recomendadoCartRecommendedItemModel (inexistente)CartItemRecommendationModel (Core.OrgDB)
Entidade de pagamentoPaymentsModel com campo TypePaymentsModel sem Type; validar via Cart.SaleEcommerce
Repositório de recomendaçãoICartRecommendedItemRepository (novo)Estender ICartRepository com GetCartWithRecommendationsAsync
API externa"Cart API (hybris)" via IEcommerceCartServiceAPI interna coezzion-service-cart via novo ICartIntegrationService
Lock distribuídonão mencionadoILockService obrigatório (try/finally)
TransactionIdnão mencionadotransactionContext.SetTransactionId no controller
Property 3 (DTO completeness)"excludes by SKU match""excludes where CartItemId != null"
Campos de RecommendedItemDTOassumia colunas inexistentes (ProductName, Sku, Url)Obtidos via ProductModel ou resposta da API interna
Tabela de erroscheck Type = Ecommercecheck Cart.SaleEcommerce == true
Lock falha → 429não tratadoBaseResult.WithTooManyRequest(...)
Mapeamento erro Cart APIResponseErrorDTO do hybrisBaseResult (Coezzion.Common) da API interna