@ -0,0 +1,7 @@ | |||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; | |||||
public class CouponData | |||||
{ | |||||
public int Discount { get; set; } | |||||
public string Code { get; set; } | |||||
} |
@ -0,0 +1,28 @@ | |||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services; | |||||
public class CouponService : ICouponService | |||||
{ | |||||
public readonly HttpClient _httpClient; | |||||
private readonly UrlsConfig _urls; | |||||
private readonly ILogger<CouponService> _logger; | |||||
public CouponService(HttpClient httpClient, IOptions<UrlsConfig> config, ILogger<CouponService> logger) | |||||
{ | |||||
_urls = config.Value; | |||||
_httpClient = httpClient; | |||||
_logger = logger; | |||||
} | |||||
public async Task<HttpResponseMessage> CheckCouponByCodeNumberAsync(string codeNumber) | |||||
{ | |||||
_logger.LogInformation("Call coupon api with codenumber: {codeNumber}", codeNumber); | |||||
var url = new Uri($"{_urls.Catalog}/api/v1/coupon/{codeNumber}"); | |||||
var response = await _httpClient.GetAsync(url); | |||||
_logger.LogInformation("Coupon api response: {@response}", response); | |||||
return response; | |||||
} | |||||
} |
@ -0,0 +1,6 @@ | |||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services; | |||||
public interface ICouponService | |||||
{ | |||||
Task<HttpResponseMessage> CheckCouponByCodeNumberAsync(string codeNumber); | |||||
} |
@ -0,0 +1,17 @@ | |||||
namespace Ordering.API.Application.Commands; | |||||
public class CouponConfirmedCommand : IRequest<bool> | |||||
{ | |||||
[DataMember] | |||||
public int OrderNumber { get; private set; } | |||||
[DataMember] | |||||
public int Discount { get; private set; } | |||||
public CouponConfirmedCommand(int orderNumber, int discount) | |||||
{ | |||||
OrderNumber = orderNumber; | |||||
Discount = discount; | |||||
} | |||||
} |
@ -0,0 +1,41 @@ | |||||
namespace Ordering.API.Application.Commands; | |||||
public class CouponConfirmedCommandHandler : IRequestHandler<CouponConfirmedCommand, bool> | |||||
{ | |||||
private readonly IOrderRepository _orderRepository; | |||||
public CouponConfirmedCommandHandler(IOrderRepository orderRepository) | |||||
{ | |||||
_orderRepository = orderRepository; | |||||
} | |||||
public async Task<bool> Handle(CouponConfirmedCommand command, CancellationToken cancellationToken) | |||||
{ | |||||
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); | |||||
if (orderToUpdate == null) | |||||
{ | |||||
return false; | |||||
} | |||||
orderToUpdate.ProcessCouponConfirmed(); | |||||
return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); | |||||
} | |||||
} | |||||
public class CouponConfirmIdenfifiedCommandHandler : IdentifiedCommandHandler<CouponConfirmedCommand, bool> | |||||
{ | |||||
public CouponConfirmIdenfifiedCommandHandler( | |||||
IMediator mediator, | |||||
IRequestManager requestManager, | |||||
ILogger<IdentifiedCommandHandler<CouponConfirmedCommand, bool>> logger) | |||||
: base(mediator, requestManager, logger) | |||||
{ | |||||
} | |||||
protected override bool CreateResultForDuplicateRequest() | |||||
{ | |||||
return true; // Ignore duplicate requests for processing order. | |||||
} | |||||
} |
@ -0,0 +1,37 @@ | |||||
using Ordering.API.Application.IntegrationEvents.Events; | |||||
using Ordering.Domain.Events; | |||||
namespace Ordering.API.Application.DomainEventHandlers.OrderCoupon; | |||||
public class OrderStatusChangedToAwaitingCouponValidationDomainEventHandler : INotificationHandler<OrderStatusChangedToAwaitingCouponValidationDomainEvent> | |||||
{ | |||||
private readonly IOrderRepository _orderRepository; | |||||
private readonly IBuyerRepository _buyerRepository; | |||||
private readonly ILoggerFactory _logger; | |||||
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; | |||||
public OrderStatusChangedToAwaitingCouponValidationDomainEventHandler( | |||||
IOrderRepository orderRepository, | |||||
IBuyerRepository buyerRepository, | |||||
ILoggerFactory logger, | |||||
IOrderingIntegrationEventService orderingIntegrationEventService) | |||||
{ | |||||
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); | |||||
_buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
_orderingIntegrationEventService = orderingIntegrationEventService; | |||||
} | |||||
public async Task Handle(OrderStatusChangedToAwaitingCouponValidationDomainEvent domainEvent, CancellationToken cancellationToken) | |||||
{ | |||||
_logger.CreateLogger<OrderStatusChangedToAwaitingCouponValidationDomainEventHandler>() | |||||
.LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", domainEvent.OrderId, nameof(OrderStatus.StockConfirmed), OrderStatus.StockConfirmed.Id); | |||||
var order = await _orderRepository.GetAsync(domainEvent.OrderId); | |||||
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); | |||||
var integrationEvent = new OrderStatusChangedToAwaitingCouponValidationIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name, order.DiscountCode); | |||||
await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); | |||||
} | |||||
} |
@ -0,0 +1,32 @@ | |||||
using Ordering.API.Application.Commands; | |||||
using Ordering.API.Application.IntegrationEvents.Events; | |||||
namespace Ordering.API.Application.IntegrationEvents.EventHandling; | |||||
public class OrderCouponConfirmedIntegrationEventHandler : IIntegrationEventHandler<OrderCouponConfirmedIntegrationEvent> | |||||
{ | |||||
private readonly IMediator _mediator; | |||||
public OrderCouponConfirmedIntegrationEventHandler(IMediator mediator) | |||||
{ | |||||
_mediator = mediator; | |||||
} | |||||
public async Task Handle(OrderCouponConfirmedIntegrationEvent @event) | |||||
{ | |||||
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | |||||
{ | |||||
Log.Information("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | |||||
var command = new CouponConfirmedCommand(@event.OrderId, @event.Discount); | |||||
Log.Information("----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", | |||||
command.GetGenericTypeName(), | |||||
nameof(command.OrderNumber), | |||||
command.OrderNumber, | |||||
command); | |||||
await _mediator.Send(command); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,33 @@ | |||||
using Ordering.API.Application.IntegrationEvents.Events; | |||||
namespace Ordering.API.Application.IntegrationEvents.EventHandling; | |||||
public class OrderCouponRejectedIntegrationEventHandler : IIntegrationEventHandler<OrderCouponRejectedIntegrationEvent> | |||||
{ | |||||
private readonly IMediator _mediator; | |||||
public OrderCouponRejectedIntegrationEventHandler(IMediator mediator) | |||||
{ | |||||
_mediator = mediator; | |||||
} | |||||
public async Task Handle(OrderCouponRejectedIntegrationEvent @event) | |||||
{ | |||||
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | |||||
{ | |||||
Log.Information("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | |||||
Log.Warning("Discount failed, cancelling order {OrderId}", @event.OrderId); | |||||
var command = new CancelOrderCommand(@event.OrderId); | |||||
Log.Information("----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", | |||||
command.GetGenericTypeName(), | |||||
nameof(command.OrderNumber), | |||||
command.OrderNumber, | |||||
command); | |||||
await _mediator.Send(command); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,12 @@ | |||||
using Newtonsoft.Json; | |||||
namespace Ordering.API.Application.IntegrationEvents.Events; | |||||
public record OrderCouponConfirmedIntegrationEvent : IntegrationEvent | |||||
{ | |||||
[JsonProperty] | |||||
public int OrderId { get; private set; } | |||||
[JsonProperty] | |||||
public int Discount { get; private set; } | |||||
} |
@ -0,0 +1,12 @@ | |||||
using Newtonsoft.Json; | |||||
namespace Ordering.API.Application.IntegrationEvents.Events; | |||||
public record OrderCouponRejectedIntegrationEvent : IntegrationEvent | |||||
{ | |||||
[JsonProperty] | |||||
public int OrderId { get; private set; } | |||||
[JsonProperty] | |||||
public string Code { get; private set; } | |||||
} |
@ -0,0 +1,20 @@ | |||||
namespace Ordering.API.Application.IntegrationEvents.Events; | |||||
public record OrderStatusChangedToAwaitingCouponValidationIntegrationEvent : IntegrationEvent | |||||
{ | |||||
public int OrderId { get; } | |||||
public string OrderStatus { get; } | |||||
public string BuyerName { get; } | |||||
public string Code { get; set; } | |||||
public OrderStatusChangedToAwaitingCouponValidationIntegrationEvent(int orderId, string orderStatus, string buyerName, string code) | |||||
{ | |||||
OrderId = orderId; | |||||
OrderStatus = orderStatus; | |||||
BuyerName = buyerName; | |||||
Code = code; | |||||
} | |||||
} |
@ -0,0 +1,19 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Ordering.Domain.Events; | |||||
public class OrderStatusChangedToAwaitingCouponValidationDomainEvent : INotification | |||||
{ | |||||
public int OrderId { get; } | |||||
public string Code { get; set; } | |||||
public OrderStatusChangedToAwaitingCouponValidationDomainEvent(int orderId, string code) | |||||
{ | |||||
OrderId = orderId; | |||||
Code = code; | |||||
} | |||||
} |
@ -0,0 +1,30 @@ | |||||
using Ordering.SignalrHub.IntegrationEvents.Events; | |||||
namespace Ordering.SignalrHub.IntegrationEvents.EventHandling; | |||||
public class OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToAwaitingCouponValidationIntegrationEvent> | |||||
{ | |||||
private readonly IHubContext<NotificationsHub> _hubContext; | |||||
private readonly ILogger<OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler> _logger; | |||||
public OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler( | |||||
IHubContext<NotificationsHub> hubContext, | |||||
ILogger<OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler> logger) | |||||
{ | |||||
_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext)); | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
} | |||||
public async Task Handle(OrderStatusChangedToAwaitingCouponValidationIntegrationEvent @event) | |||||
{ | |||||
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | |||||
{ | |||||
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | |||||
await _hubContext.Clients | |||||
.Group(@event.BuyerName) | |||||
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,17 @@ | |||||
using Newtonsoft.Json; | |||||
namespace Ordering.SignalrHub.IntegrationEvents.Events; | |||||
public record OrderStatusChangedToAwaitingCouponValidationIntegrationEvent : IntegrationEvent | |||||
{ | |||||
[JsonProperty] | |||||
public int OrderId { get; private set; } | |||||
[JsonProperty] | |||||
public string OrderStatus { get; private set; } | |||||
[JsonProperty] | |||||
public string BuyerName { get; private set; } | |||||
[JsonProperty] | |||||
public string Code { get; private set; } | |||||
} |