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 docoezzion-service-checkoutecoezzion-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 (
PaymentsModeldeLink.OrgDB.Entities). - Não atende: pagamentos Ecommerce (
PaymentsEcommModel) — rejeitados na validação.
Decisões de Design
-
Command (não Query): a operação modifica estado (adiciona item ao carrinho via API interna e atualiza
Payment.Total), portanto é modelada comoICommand<BaseResult>. -
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 deILockService.LockAsync/ReleaseLockAsyncem try/finally para evitar race condition sobre o mesmo pagamento — padrão universal em handlers que mutamPaymentsModel. -
Serviço dedicado para a API interna de carrinho: a chamada HTTP para adicionar o item é encapsulada em
ICartIntegrationService, usandoHttpClientregistrado no DI com Polly retry (mesmo padrão deEcommerceBaseService, porém apontando para ocoezzion-service-cart). O endpoint alvo será criado em outra tarefa (ver seção Dependências Externas). -
Reutilizaç ão de DTOs existentes:
Values,Product,PaymentMethodseInstallmentsdoGetInfoBase(Checkout.Domain/DTO/GetInfo/GetInfoBase.cs) são reutilizados; apenasAddRecommendedItemResponseDTOeRecommendedItemDTOsão novos. -
Acesso a
CartItemRecommendationvia extensão deICartRepository: a entidadeCartItemRecommendationModeljá é navigation deCartModel(Cart.CartItemRecommendations). Não criar repositório dedicado — estenderICartRepositorycom um método que faça.Include(x => x.CartItemRecommendations), reaproveitando a infraestrutura canônica de acesso a cart no checkout. -
Validação Store vs Ecommerce via
Cart.SaleEcommerce:PaymentsModel(Link.OrgDB.Entities) não possui propriedadeType/PaymentType. A validação de tipo é feita indiretamente: carrega-se oCartModelviaPaymentsModel.CartIde verifica-seCart.SaleEcommerce == false(Store). SeSaleEcommerce == true, rejeitar com "Operação não disponível para este tipo de pedido".
Architecture
Diagrama de Fluxo
Camadas e Responsabilidades
| Camada | Componente | Responsabilidade |
|---|---|---|
| API | PaymentController | Recebe request, seta TransactionId, despacha command via mediator, converte BaseResult em HTTP |
| API | AddRecommendedItemCommand | DTO do command com PaymentGuid e RecommendedItemId |
| API | AddRecommendedItemValidator | FluentValidation do command (roda automático no pipeline do ZZMediatR) |
| API | AddRecommendedItemCommandHandler | Orquestração: validações, lock, chamada Cart API, atualização, resposta |
| Domain | ICartRepository (extensão) | Novo método GetCartWithRecommendationsAsync(int cartId) |
| Domain | ICartIntegrationService (novo) | Contrato para chamada HTTP ao coezzion-service-cart |
| Domain | AddRecommendedItemResponseDTO | DTO de resposta |
| Domain | RecommendedItemDTO | DTO de item recomendado |
| Infrastructure | CartRepository (extensão) | Implementa GetCartWithRecommendationsAsync com .Include(CartItemRecommendations) |
| Infrastructure | CartIntegrationService (novo) | HttpClient + Polly para coezzion-service-cart |
Components and Interfaces
Novos Arquivos
| Arquivo | Camada | Tipo |
|---|---|---|
src/Checkout.API/Application/Messages/Commands/RecommendedItemCommands/AddRecommendedItemCommand.cs | API | Command |
src/Checkout.API/Application/Messages/Commands/RecommendedItemCommands/AddRecommendedItemCommandHandler.cs | API | Handler |
src/Checkout.API/Application/Validators/RecommendedItem/AddRecommendedItemValidator.cs | API | Validator |
src/Checkout.Domain/DTO/RecommendedItem/AddRecommendedItemResponseDTO.cs | Domain | DTO |
src/Checkout.Domain/DTO/RecommendedItem/RecommendedItemDTO.cs | Domain | DTO |
src/Checkout.Domain/Interfaces/Services/Cart/ICartIntegrationService.cs | Domain | Interface |
src/Checkout.Infraestructure/Integrations/Cart/CartIntegrationService.cs | Infrastructure | Service |
Arquivos Modificados
| Arquivo | Modificação |
|---|---|
src/Checkout.API/Controllers/PaymentController.cs | Novo endpoint AddRecommendedItem |
src/Checkout.Domain/Interfaces/Repositories/Read/ICartRepository.cs | Novo método GetCartWithRecommendationsAsync |
src/Checkout.Infraestructure/Data/Repositories/Read/CartRepository.cs | Implementação de GetCartWithRecommendationsAsync |
src/Checkout.API/Configuration/DependencyInjectionConfig.cs | Registro 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:
HttpClientinjetado pelo DI factory comAddHttpClient<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,Urlnão existem como colunas emCartItemRecommendationModel. Devem ser obtidos via: (a) navigationProductModelcarregada junto, ou (b) DTO retornado pela API interna docoezzion-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 umCartItemsModel.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:
PaymentsModelNÃO possui propriedadeTypeouPaymentType. A distinção Store vs Ecommerce é feita viaCartModel.SaleEcommerce(bool). Para este endpoint, só deve prosseguir seCart.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:
PaymentsModelreside em schema doLink.OrgDBe referenciaCartIdque é entidade doCore.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 deBaseEntity), nãoCartItemRecommendationModel.DateCreated. OCartModeldeve ser carregado viaGetCartWithRecommendationsAsyncantes 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 formatoBaseResult(Coezzion.Common) comErrorscomo 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:
productsreflete os itens atualizados do carrinho;recommendedItemsexclui itens cuja recomendação já foi efetivada (i.e., ondeCartItemRecommendationModel.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(parFromRecommendation/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.Descriptionnã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 helperTruncateTo(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 > 0→ 400 BadRequest com{ Title, Status, Errors: { Messages } }.Response is TooManyRequestDto→ 429.Response == nullsem erros → 200 OK sem body.Response != nullsem erros → 200 OK com body.
Tabela de Erros (revisada)
| Cenário | Status | Mensagem | Observação |
|---|---|---|---|
| Token ausente/inválido | 401 | (sem body - middleware) | [Authorize(PaymentsScheme)] |
| GUID inválido na rota | 400 | "Payment não encontrado" | |
| Payment não encontrado | 400 | "Payment não encontrado" | GetByGuidPaymentAsync retorna null |
| Lock não adquirido (race) | 429 | "Pagamento em processamento" | BaseResult.WithTooManyRequest(...) |
| Cart não encontrado | 400 | "Carrinho não encontrado" | |
Cart.SaleEcommerce == true | 400 | "Operação não disponível para este tipo de pedido" | substitui check de Type inexistente |
| CartId nulo/zero | 400 | "Pagamento não possui carrinho associado" | |
| Recomendação não encontrada | 400 | "Recomendação não encontrada" | Cart.CartItemRecommendations não contém o Id |
| Recomendação de outro carrinho | 400 | "Recomendação não encontrada" | CartId mismatch |
| Janela expirada | 400 | "Recomendação não está mais disponível" | Cart.DateCreated + 1h < now |
| Produto sem estoque | 400 | "Produto sem estoque na loja" | Depende do contrato da API interna (TBD) |
| Erro genérico Cart API | 400 | "Não foi possível adicionar o item" | |
| Timeout Cart API | 400 | "Não foi possível adicionar o item" | Polly retry esgotado |
| Falha persistência | 500 | "Erro interno ao atualizar o pagamento" | try/catch no handler |
| Falha montagem DTO | 500 | "Erro interno ao montar a resposta" | try/catch no handler |
recommendedItemId <= 0 | 400 | "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
BaseResultretornado 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
ResponseErrorDTOdo hybris — a API interna é Coezzion.Common e retornaBaseResult.
Testing Strategy
Testes Unitários do Handler (xUnit + Moq)
| Cenário | Resultado esperado |
|---|---|
Payment não encontrado (GetByGuidPaymentAsync → null) | erro "Payment não encontrado" |
| Lock não adquirido | 429 "Pagamento em processamento" |
| Cart não encontrado | erro "Carrinho não encontrado" |
Cart.SaleEcommerce == true | erro "Operação não disponível para este tipo de pedido" |
| Payment sem CartId | erro "Pagamento não possui carrinho associado" |
Recomendação não encontrada em cart.CartItemRecommendations | erro "Recomendação não encontrada" |
Recomendação com CartId mismatch | erro "Recomendação não encontrada" |
Janela expirada (Cart.DateCreated + 1h < now) | erro "Recomendação não está mais disponível" |
| Cart API sucesso | atualiza Payment.Total e retorna DTO |
| Cart API sem estoque | erro "Produto sem estoque na loja" |
| Cart API erro genérico | erro "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 executado | verify ReleaseLockAsync chamado no finally |
LogQueueService.EnqueueAsync chamado nos caminhos de erro | verify |
Testes Unitários do Validator (FluentValidation)
| Input | Resultado |
|---|---|
recommendedItemId == 0 | falha |
recommendedItemId < 0 | falha |
recommendedItemId > 0 | passa |
PaymentGuid == Guid.Empty | falha |
PaymentGuid válido | passa |
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
EnqueueAsyncchamado com request + response + URL. - Timeout/HttpClient exception → erro genérico.
Testes Unitários do CartRepository.GetCartWithRecommendationsAsync
- Retorna
CartModelcomCartItemRecommendationspopulada. - Filtra por
Id == cartId. AsNoTracking()aplicado.
Testes de Propriedade (FsCheck via xUnit)
Biblioteca: FsCheck.Xunit. Mínimo 100 iterações por teste.
| Property | Tag |
|---|---|
| 1 — Time window validation is consistent | Feature: add-recommended-item, Property 1 |
| 2 — Cart API non-success error categorization | Feature: 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 recommendedItemId | Feature: add-recommended-item, Property 5 |
Testes de Integração
- Endpoint completo com auth válida (
PaymentsScheme) e dados reais. - Mock do
coezzion-service-cartviaWebApplicationFactory/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ência | Descrição | Status |
|---|---|---|
coezzion-service-cart — endpoint AddItem | API 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) |
CartItemRecommendationModel | Entidade Core.OrgDB já existe com migration. | 🟢 disponível |
Multitenancy via IUserProvider.GetSchemaName() | Padrão existente, já injetado nos handlers. | 🟢 disponível |
ILockService | Já registrado e usado em CreatePaymentCommandHandler. | 🟢 disponível |
| Recaptcha no endpoint | Endpoints públicos PaymentsScheme tipicamente usam IRecaptchaService.Validate. | 🟡 a confirmar com o requisito da US 194880 |
Questões em Aberto
- Recaptcha: o endpoint
add-recommended-itemdeve ter gate de Recaptcha (comogetInfo) ou não? Confirmar com a US 194880. - Contrato da API interna: qual o formato exato de request/response do endpoint no
coezzion-service-cart? (Definido na tarefa que criará o endpoint.) - Atualização do
Payment.Total: o novo total vem da resposta da API interna, ou deve ser recalculado localmente a partir deCart.Totalapós a chamada? Definir para evitar divergência. - Autorização do endpoint alvo: o
coezzion-service-cartusará[UseApiKey("internal")]ou JWT service-to-service? Alinhar com a tarefa do service-cart. - Cache invalidation: ao estender
ICartRepositorycomGetCartWithRecommendationsAsync, definir se o decoratorCacheCartRepositoryDecoratordeve invalidar a chaveCacheKeys.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ão | Arquivo de referência |
|---|---|
| Handler com lock + try/finally + log | Checkout.API/Application/Messages/Commands/CreateCommands/CreatePaymentCommandHandler.cs |
| Handler multi-command com chamada HTTP externa | Checkout.API/Application/Messages/Commands/EcommerceCommands/EcommerceCommandHandler.cs |
| Service de integração HTTP + log + error parsing | Checkout.Infraestructure/Integrations/Ecommerce/EcommerceCartService.cs |
| HttpClient base com Polly + cookie/timeout | Checkout.Infraestructure/Integrations/Ecommerce/EcommerceBaseService.cs |
| Controller endpoint POST v2 + CustomResponse | Checkout.API/Controllers/PaymentController.cs (linhas 42-51, 374-428) |
| Validator FluentValidation | Checkout.API/Application/Validators/Ecommerce/PostCartValidator.cs |
BaseResult extensions (WithError, With, WithTooManyRequest) | Checkout.Domain/Utils/BaseResultExtensions.cs |
| LogEvent factories | Checkout.Domain/Events/LogQueueMessageEvent.cs |
| DI + Polly + HttpClient | Checkout.API/Configuration/DependencyInjectionConfig.cs (linhas 178-232) |
| CartModel + CartItemRecommendations navigation | Core.OrgDB/Entities/CartModel.cs:131 |
| CartItemRecommendationModel (entidade) | Core.OrgDB/Entities/CartItemRecommendationModel.cs |
| PaymentsModel (entidade Store) | Link.OrgDB/Entities/PaymentsModel.cs |
| PaymentType enum | Checkout.Domain/Enums/PaymentType.cs |
Resumo das mudanças vs v1
| Item | v1 (errado/impreciso) | v2 (corrigido) |
|---|---|---|
| Entidade de item recomendado | CartRecommendedItemModel (inexistente) | CartItemRecommendationModel (Core.OrgDB) |
| Entidade de pagamento | PaymentsModel com campo Type | PaymentsModel sem Type; validar via Cart.SaleEcommerce |
| Repositório de recomendação | ICartRecommendedItemRepository (novo) | Estender ICartRepository com GetCartWithRecommendationsAsync |
| API externa | "Cart API (hybris)" via IEcommerceCartService | API interna coezzion-service-cart via novo ICartIntegrationService |
| Lock distribuído | não mencionado | ILockService obrigatório (try/finally) |
TransactionId | não mencionado | transactionContext.SetTransactionId no controller |
| Property 3 (DTO completeness) | "excludes by SKU match" | "excludes where CartItemId != null" |
Campos de RecommendedItemDTO | assumia colunas inexistentes (ProductName, Sku, Url) | Obtidos via ProductModel ou resposta da API interna |
| Tabela de erros | check Type = Ecommerce | check Cart.SaleEcommerce == true |
| Lock falha → 429 | não tratado | BaseResult.WithTooManyRequest(...) |
| Mapeamento erro Cart API | ResponseErrorDTO do hybris | BaseResult (Coezzion.Common) da API interna |