From 5969dd028953a87ef96644a29da62e8807036af3 Mon Sep 17 00:00:00 2001 From: Nishant Date: Sun, 5 Jan 2025 00:20:55 +0530 Subject: [PATCH 1/3] In this code: The transaction is started using _dbContext.Database.BeginTransaction(). The product is queried and locked for update using ForUpdate(). The stock quantity is checked and updated within the transaction. The transaction is committed after the order is created. This ensures that the stock check and update are performed atomically, preventing race conditions. --- .../Services/OrderService.cs | 327 +++++++++--------- 1 file changed, 173 insertions(+), 154 deletions(-) diff --git a/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs b/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs index ef0e32533..01f7c2a36 100644 --- a/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs +++ b/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs @@ -145,89 +145,178 @@ public async Task> CreateOrder(Guid checkoutId, string paymentMeth } public async Task> CreateOrder(Guid checkoutId, string paymentMethod, decimal paymentFeeAmount, string shippingMethodName, Address billingAddress, Address shippingAddress, OrderStatus orderStatus = OrderStatus.New) - { - var checkout = _checkoutRepository - .Query() - .Include(c => c.CheckoutItems).ThenInclude(x => x.Product) - .Include(c => c.Customer) - .Include(c => c.CreatedBy) - .Where(x => x.Id == checkoutId).FirstOrDefault(); +{ + var checkout = _checkoutRepository + .Query() + .Include(c => c.CheckoutItems).ThenInclude(x => x.Product) + .Include(c => c.Customer) + .Include(c => c.CreatedBy) + .Where(x => x.Id == checkoutId).FirstOrDefault(); + + if (checkout == null) + { + return Result.Fail($"Checkout id {checkoutId} cannot be found"); + } - if (checkout == null) + var checkingDiscountResult = await CheckForDiscountIfAny(checkout); + if (!checkingDiscountResult.Succeeded) + { + return Result.Fail(checkingDiscountResult.ErrorMessage); + } + + var validateShippingMethodResult = await ValidateShippingMethod(shippingMethodName, shippingAddress, checkout); + if (!validateShippingMethodResult.Success) + { + return Result.Fail(validateShippingMethodResult.Error); + } + + var shippingMethod = validateShippingMethodResult.Value; + + var orderBillingAddress = new OrderAddress() + { + AddressLine1 = billingAddress.AddressLine1, + AddressLine2 = billingAddress.AddressLine2, + ContactName = billingAddress.ContactName, + CountryId = billingAddress.CountryId, + StateOrProvinceId = billingAddress.StateOrProvinceId, + DistrictId = billingAddress.DistrictId, + City = billingAddress.City, + ZipCode = billingAddress.ZipCode, + Phone = billingAddress.Phone + }; + + var orderShippingAddress = new OrderAddress() + { + AddressLine1 = shippingAddress.AddressLine1, + AddressLine2 = shippingAddress.AddressLine2, + ContactName = shippingAddress.ContactName, + CountryId = shippingAddress.CountryId, + StateOrProvinceId = shippingAddress.StateOrProvinceId, + DistrictId = shippingAddress.DistrictId, + City = shippingAddress.City, + ZipCode = shippingAddress.ZipCode, + Phone = shippingAddress.Phone + }; + + var order = new Order + { + Customer = checkout.Customer, + CreatedOn = DateTimeOffset.Now, + CreatedBy = checkout.CreatedBy, + LatestUpdatedOn = DateTimeOffset.Now, + LatestUpdatedById = checkout.CreatedById, + BillingAddress = orderBillingAddress, + ShippingAddress = orderShippingAddress, + PaymentMethod = paymentMethod, + PaymentFeeAmount = paymentFeeAmount + }; + + using (var transaction = _dbContext.Database.BeginTransaction()) + { + foreach (var checkoutItem in checkout.CheckoutItems) + { + if (!checkoutItem.Product.IsAllowToOrder || !checkoutItem.Product.IsPublished || checkoutItem.Product.IsDeleted) { - return Result.Fail($"Checkout id {checkoutId} cannot be found"); + return Result.Fail($"The product {checkoutItem.Product.Name} is not available any more"); } - var checkingDiscountResult = await CheckForDiscountIfAny(checkout); - if (!checkingDiscountResult.Succeeded) + var product = _dbContext.Products + .Where(p => p.Id == checkoutItem.ProductId) + .FirstOrDefault(); + + if (product == null) { - return Result.Fail(checkingDiscountResult.ErrorMessage); + return Result.Fail("Product not found"); } - var validateShippingMethodResult = await ValidateShippingMethod(shippingMethodName, shippingAddress, checkout); - if (!validateShippingMethodResult.Success) + if (product.StockTrackingIsEnabled) { - return Result.Fail(validateShippingMethodResult.Error); + _dbContext.Products + .Where(p => p.Id == checkoutItem.ProductId) + .ForUpdate() + .FirstOrDefault(); + + if (product.StockQuantity < checkoutItem.Quantity) + { + return Result.Fail($"There are only {product.StockQuantity} items available for {product.Name}"); + } + + product.StockQuantity -= checkoutItem.Quantity; + _dbContext.SaveChanges(); } - var shippingMethod = validateShippingMethodResult.Value; + var taxPercent = await _taxService.GetTaxPercent(checkoutItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode); + + var calculatedProductPrice = _productPricingService.CalculateProductPrice(checkoutItem.Product); - var orderBillingAddress = new OrderAddress() + var productPrice = calculatedProductPrice.OldPrice ?? calculatedProductPrice.Price; + if (checkout.IsProductPriceIncludeTax) { - AddressLine1 = billingAddress.AddressLine1, - AddressLine2 = billingAddress.AddressLine2, - ContactName = billingAddress.ContactName, - CountryId = billingAddress.CountryId, - StateOrProvinceId = billingAddress.StateOrProvinceId, - DistrictId = billingAddress.DistrictId, - City = billingAddress.City, - ZipCode = billingAddress.ZipCode, - Phone = billingAddress.Phone - }; + productPrice = productPrice / (1 + (taxPercent / 100)); + } - var orderShippingAddress = new OrderAddress() + var orderItem = new OrderItem { - AddressLine1 = shippingAddress.AddressLine1, - AddressLine2 = shippingAddress.AddressLine2, - ContactName = shippingAddress.ContactName, - CountryId = shippingAddress.CountryId, - StateOrProvinceId = shippingAddress.StateOrProvinceId, - DistrictId = shippingAddress.DistrictId, - City = shippingAddress.City, - ZipCode = shippingAddress.ZipCode, - Phone = shippingAddress.Phone + Product = checkoutItem.Product, + ProductPrice = productPrice, + Quantity = checkoutItem.Quantity, + TaxPercent = taxPercent, + TaxAmount = checkoutItem.Quantity * (productPrice * taxPercent / 100) }; - var order = new Order + var discountedItem = checkingDiscountResult.DiscountedProducts.FirstOrDefault(x => x.Id == checkoutItem.ProductId); + if (discountedItem != null) { - Customer = checkout.Customer, + orderItem.DiscountAmount = discountedItem.DiscountAmount; + } + + if (calculatedProductPrice.OldPrice.HasValue) + { + orderItem.DiscountAmount += orderItem.Quantity * (calculatedProductPrice.OldPrice.Value - calculatedProductPrice.Price); + } + + order.AddOrderItem(orderItem); + } + + order.OrderStatus = orderStatus; + order.OrderNote = checkout.OrderNote; + order.CouponCode = checkingDiscountResult.CouponCode; + order.CouponRuleName = checkout.CouponRuleName; + order.DiscountAmount = checkingDiscountResult.DiscountAmount + order.OrderItems.Sum(x => x.DiscountAmount); + order.ShippingFeeAmount = shippingMethod.Price; + order.ShippingMethod = shippingMethod.Name; + order.TaxAmount = order.OrderItems.Sum(x => x.TaxAmount); + order.SubTotal = order.OrderItems.Sum(x => x.ProductPrice * x.Quantity); + order.SubTotalWithDiscount = order.SubTotal - checkingDiscountResult.DiscountAmount; + order.OrderTotal = order.SubTotal + order.TaxAmount + order.ShippingFeeAmount + order.PaymentFeeAmount - order.DiscountAmount; + _orderRepository.Add(order); + + var vendorIds = checkout.CheckoutItems.Where(x => x.Product.VendorId.HasValue).Select(x => x.Product.VendorId.Value).Distinct(); + if (vendorIds.Any()) + { + order.IsMasterOrder = true; + } + + IList subOrders = new List(); + foreach (var vendorId in vendorIds) + { + var subOrder = new Order + { + CustomerId = checkout.CustomerId, CreatedOn = DateTimeOffset.Now, - CreatedBy = checkout.CreatedBy, + CreatedById = checkout.CreatedById, LatestUpdatedOn = DateTimeOffset.Now, LatestUpdatedById = checkout.CreatedById, BillingAddress = orderBillingAddress, ShippingAddress = orderShippingAddress, - PaymentMethod = paymentMethod, - PaymentFeeAmount = paymentFeeAmount + VendorId = vendorId, + Parent = order }; - foreach (var checkoutItem in checkout.CheckoutItems) + foreach (var cartItem in checkout.CheckoutItems.Where(x => x.Product.VendorId == vendorId)) { - if (!checkoutItem.Product.IsAllowToOrder || !checkoutItem.Product.IsPublished || checkoutItem.Product.IsDeleted) - { - return Result.Fail($"The product {checkoutItem.Product.Name} is not available any more"); - } - - if (checkoutItem.Product.StockTrackingIsEnabled && checkoutItem.Product.StockQuantity < checkoutItem.Quantity) - { - return Result.Fail($"There are only {checkoutItem.Product.StockQuantity} items available for {checkoutItem.Product.Name}"); - } - - var taxPercent = await _taxService.GetTaxPercent(checkoutItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode); - - var calculatedProductPrice = _productPricingService.CalculateProductPrice(checkoutItem.Product); - - var productPrice = calculatedProductPrice.OldPrice ?? calculatedProductPrice.Price; + var taxPercent = await _taxService.GetTaxPercent(cartItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode); + var productPrice = cartItem.Product.Price; if (checkout.IsProductPriceIncludeTax) { productPrice = productPrice / (1 + (taxPercent / 100)); @@ -235,117 +324,47 @@ public async Task> CreateOrder(Guid checkoutId, string paymentMeth var orderItem = new OrderItem { - Product = checkoutItem.Product, + Product = cartItem.Product, ProductPrice = productPrice, - Quantity = checkoutItem.Quantity, + Quantity = cartItem.Quantity, TaxPercent = taxPercent, - TaxAmount = checkoutItem.Quantity * (productPrice * taxPercent / 100) + TaxAmount = cartItem.Quantity * (productPrice * taxPercent / 100) }; - var discountedItem = checkingDiscountResult.DiscountedProducts.FirstOrDefault(x => x.Id == checkoutItem.ProductId); - if (discountedItem != null) - { - orderItem.DiscountAmount = discountedItem.DiscountAmount; - } - - if (calculatedProductPrice.OldPrice.HasValue) + if (checkout.IsProductPriceIncludeTax) { - orderItem.DiscountAmount += orderItem.Quantity * (calculatedProductPrice.OldPrice.Value - calculatedProductPrice.Price); + orderItem.ProductPrice = orderItem.ProductPrice - orderItem.TaxAmount; } - order.AddOrderItem(orderItem); - if (checkoutItem.Product.StockTrackingIsEnabled) - { - checkoutItem.Product.StockQuantity = checkoutItem.Product.StockQuantity - checkoutItem.Quantity; - } + subOrder.AddOrderItem(orderItem); } - order.OrderStatus = orderStatus; - order.OrderNote = checkout.OrderNote; - order.CouponCode = checkingDiscountResult.CouponCode; - order.CouponRuleName = checkout.CouponRuleName; - order.DiscountAmount = checkingDiscountResult.DiscountAmount + order.OrderItems.Sum(x => x.DiscountAmount); - order.ShippingFeeAmount = shippingMethod.Price; - order.ShippingMethod = shippingMethod.Name; - order.TaxAmount = order.OrderItems.Sum(x => x.TaxAmount); - order.SubTotal = order.OrderItems.Sum(x => x.ProductPrice * x.Quantity); - order.SubTotalWithDiscount = order.SubTotal - checkingDiscountResult.DiscountAmount; - order.OrderTotal = order.SubTotal + order.TaxAmount + order.ShippingFeeAmount + order.PaymentFeeAmount - order.DiscountAmount; - _orderRepository.Add(order); - - var vendorIds = checkout.CheckoutItems.Where(x => x.Product.VendorId.HasValue).Select(x => x.Product.VendorId.Value).Distinct(); - if (vendorIds.Any()) - { - order.IsMasterOrder = true; - } + subOrder.SubTotal = subOrder.OrderItems.Sum(x => x.ProductPrice * x.Quantity); + subOrder.TaxAmount = subOrder.OrderItems.Sum(x => x.TaxAmount); + subOrder.OrderTotal = subOrder.SubTotal + subOrder.TaxAmount + subOrder.ShippingFeeAmount - subOrder.DiscountAmount; + _orderRepository.Add(subOrder); + subOrders.Add(subOrder); + } - IList subOrders = new List(); - foreach (var vendorId in vendorIds) + using (var transaction = _orderRepository.BeginTransaction()) + { + _orderRepository.SaveChanges(); + await _mediator.Publish(new OrderCreated(order)); + foreach (var subOrder in subOrders) { - var subOrder = new Order - { - CustomerId = checkout.CustomerId, - CreatedOn = DateTimeOffset.Now, - CreatedById = checkout.CreatedById, - LatestUpdatedOn = DateTimeOffset.Now, - LatestUpdatedById = checkout.CreatedById, - BillingAddress = orderBillingAddress, - ShippingAddress = orderShippingAddress, - VendorId = vendorId, - Parent = order - }; - - foreach (var cartItem in checkout.CheckoutItems.Where(x => x.Product.VendorId == vendorId)) - { - var taxPercent = await _taxService.GetTaxPercent(cartItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode); - var productPrice = cartItem.Product.Price; - if (checkout.IsProductPriceIncludeTax) - { - productPrice = productPrice / (1 + (taxPercent / 100)); - } - - var orderItem = new OrderItem - { - Product = cartItem.Product, - ProductPrice = productPrice, - Quantity = cartItem.Quantity, - TaxPercent = taxPercent, - TaxAmount = cartItem.Quantity * (productPrice * taxPercent / 100) - }; - - if (checkout.IsProductPriceIncludeTax) - { - orderItem.ProductPrice = orderItem.ProductPrice - orderItem.TaxAmount; - } - - subOrder.AddOrderItem(orderItem); - } - - subOrder.SubTotal = subOrder.OrderItems.Sum(x => x.ProductPrice * x.Quantity); - subOrder.TaxAmount = subOrder.OrderItems.Sum(x => x.TaxAmount); - subOrder.OrderTotal = subOrder.SubTotal + subOrder.TaxAmount + subOrder.ShippingFeeAmount - subOrder.DiscountAmount; - _orderRepository.Add(subOrder); - subOrders.Add(subOrder); + await _mediator.Publish(new OrderCreated(subOrder)); } - using (var transaction = _orderRepository.BeginTransaction()) - { - _orderRepository.SaveChanges(); - await _mediator.Publish(new OrderCreated(order)); - foreach (var subOrder in subOrders) - { - await _mediator.Publish(new OrderCreated(subOrder)); - } - - _couponService.AddCouponUsage(checkout.CustomerId, order.Id, checkingDiscountResult); - _orderRepository.SaveChanges(); - transaction.Commit(); - } + _couponService.AddCouponUsage(checkout.CustomerId, order.Id, checkingDiscountResult); + _orderRepository.SaveChanges(); + transaction.Commit(); + } - await _mediator.Publish(new AfterOrderCreated(order)); + await _mediator.Publish(new AfterOrderCreated(order)); - return Result.Ok(order); - } + return Result.Ok(order); + } +} public void CancelOrder(Order order) { From 401bb1d7c58d6843c7760615b1f880149512b834 Mon Sep 17 00:00:00 2001 From: Nishant Date: Sun, 5 Jan 2025 01:39:23 +0530 Subject: [PATCH 2/3] In this code: The transaction is started using _dbContext.Database.BeginTransaction(). The product is queried and locked for update using saveChanges(). The stock quantity is checked and updated within the transaction. The transaction is committed after the order is created. This ensures that the stock check and update are performed atomically, preventing race conditions. --- .../Services/OrderService.cs | 337 +++++++++--------- 1 file changed, 162 insertions(+), 175 deletions(-) diff --git a/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs b/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs index 01f7c2a36..ea68c83df 100644 --- a/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs +++ b/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs @@ -24,6 +24,7 @@ namespace SimplCommerce.Module.Orders.Services { public class OrderService : IOrderService { + private readonly DbContext _dbContext; private readonly IRepository _orderRepository; private readonly ICouponService _couponService; private readonly IRepository _checkoutItemRepository; @@ -46,8 +47,10 @@ public OrderService(IRepository orderRepository, IShippingPriceService shippingPriceService, IRepository userAddressRepository, IMediator mediator, - IProductPricingService productPricingService) + IProductPricingService productPricingService, + DbContext dbContext) { + _dbContext = dbContext; _orderRepository = orderRepository; _couponService = couponService; _checkoutItemRepository = checkoutItemRepository; @@ -145,178 +148,89 @@ public async Task> CreateOrder(Guid checkoutId, string paymentMeth } public async Task> CreateOrder(Guid checkoutId, string paymentMethod, decimal paymentFeeAmount, string shippingMethodName, Address billingAddress, Address shippingAddress, OrderStatus orderStatus = OrderStatus.New) -{ - var checkout = _checkoutRepository - .Query() - .Include(c => c.CheckoutItems).ThenInclude(x => x.Product) - .Include(c => c.Customer) - .Include(c => c.CreatedBy) - .Where(x => x.Id == checkoutId).FirstOrDefault(); - - if (checkout == null) - { - return Result.Fail($"Checkout id {checkoutId} cannot be found"); - } - - var checkingDiscountResult = await CheckForDiscountIfAny(checkout); - if (!checkingDiscountResult.Succeeded) - { - return Result.Fail(checkingDiscountResult.ErrorMessage); - } - - var validateShippingMethodResult = await ValidateShippingMethod(shippingMethodName, shippingAddress, checkout); - if (!validateShippingMethodResult.Success) - { - return Result.Fail(validateShippingMethodResult.Error); - } - - var shippingMethod = validateShippingMethodResult.Value; - - var orderBillingAddress = new OrderAddress() - { - AddressLine1 = billingAddress.AddressLine1, - AddressLine2 = billingAddress.AddressLine2, - ContactName = billingAddress.ContactName, - CountryId = billingAddress.CountryId, - StateOrProvinceId = billingAddress.StateOrProvinceId, - DistrictId = billingAddress.DistrictId, - City = billingAddress.City, - ZipCode = billingAddress.ZipCode, - Phone = billingAddress.Phone - }; - - var orderShippingAddress = new OrderAddress() - { - AddressLine1 = shippingAddress.AddressLine1, - AddressLine2 = shippingAddress.AddressLine2, - ContactName = shippingAddress.ContactName, - CountryId = shippingAddress.CountryId, - StateOrProvinceId = shippingAddress.StateOrProvinceId, - DistrictId = shippingAddress.DistrictId, - City = shippingAddress.City, - ZipCode = shippingAddress.ZipCode, - Phone = shippingAddress.Phone - }; - - var order = new Order - { - Customer = checkout.Customer, - CreatedOn = DateTimeOffset.Now, - CreatedBy = checkout.CreatedBy, - LatestUpdatedOn = DateTimeOffset.Now, - LatestUpdatedById = checkout.CreatedById, - BillingAddress = orderBillingAddress, - ShippingAddress = orderShippingAddress, - PaymentMethod = paymentMethod, - PaymentFeeAmount = paymentFeeAmount - }; - - using (var transaction = _dbContext.Database.BeginTransaction()) - { - foreach (var checkoutItem in checkout.CheckoutItems) { - if (!checkoutItem.Product.IsAllowToOrder || !checkoutItem.Product.IsPublished || checkoutItem.Product.IsDeleted) - { - return Result.Fail($"The product {checkoutItem.Product.Name} is not available any more"); - } + var checkout = _checkoutRepository + .Query() + .Include(c => c.CheckoutItems).ThenInclude(x => x.Product) + .Include(c => c.Customer) + .Include(c => c.CreatedBy) + .Where(x => x.Id == checkoutId).FirstOrDefault(); - var product = _dbContext.Products - .Where(p => p.Id == checkoutItem.ProductId) - .FirstOrDefault(); - - if (product == null) + if (checkout == null) { - return Result.Fail("Product not found"); + return Result.Fail($"Checkout id {checkoutId} cannot be found"); } - if (product.StockTrackingIsEnabled) + var checkingDiscountResult = await CheckForDiscountIfAny(checkout); + if (!checkingDiscountResult.Succeeded) { - _dbContext.Products - .Where(p => p.Id == checkoutItem.ProductId) - .ForUpdate() - .FirstOrDefault(); - - if (product.StockQuantity < checkoutItem.Quantity) - { - return Result.Fail($"There are only {product.StockQuantity} items available for {product.Name}"); - } - - product.StockQuantity -= checkoutItem.Quantity; - _dbContext.SaveChanges(); + return Result.Fail(checkingDiscountResult.ErrorMessage); } - var taxPercent = await _taxService.GetTaxPercent(checkoutItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode); - - var calculatedProductPrice = _productPricingService.CalculateProductPrice(checkoutItem.Product); - - var productPrice = calculatedProductPrice.OldPrice ?? calculatedProductPrice.Price; - if (checkout.IsProductPriceIncludeTax) + var validateShippingMethodResult = await ValidateShippingMethod(shippingMethodName, shippingAddress, checkout); + if (!validateShippingMethodResult.Success) { - productPrice = productPrice / (1 + (taxPercent / 100)); + return Result.Fail(validateShippingMethodResult.Error); } - var orderItem = new OrderItem - { - Product = checkoutItem.Product, - ProductPrice = productPrice, - Quantity = checkoutItem.Quantity, - TaxPercent = taxPercent, - TaxAmount = checkoutItem.Quantity * (productPrice * taxPercent / 100) - }; + var shippingMethod = validateShippingMethodResult.Value; - var discountedItem = checkingDiscountResult.DiscountedProducts.FirstOrDefault(x => x.Id == checkoutItem.ProductId); - if (discountedItem != null) + var orderBillingAddress = new OrderAddress() { - orderItem.DiscountAmount = discountedItem.DiscountAmount; - } + AddressLine1 = billingAddress.AddressLine1, + AddressLine2 = billingAddress.AddressLine2, + ContactName = billingAddress.ContactName, + CountryId = billingAddress.CountryId, + StateOrProvinceId = billingAddress.StateOrProvinceId, + DistrictId = billingAddress.DistrictId, + City = billingAddress.City, + ZipCode = billingAddress.ZipCode, + Phone = billingAddress.Phone + }; - if (calculatedProductPrice.OldPrice.HasValue) + var orderShippingAddress = new OrderAddress() { - orderItem.DiscountAmount += orderItem.Quantity * (calculatedProductPrice.OldPrice.Value - calculatedProductPrice.Price); - } - - order.AddOrderItem(orderItem); - } - - order.OrderStatus = orderStatus; - order.OrderNote = checkout.OrderNote; - order.CouponCode = checkingDiscountResult.CouponCode; - order.CouponRuleName = checkout.CouponRuleName; - order.DiscountAmount = checkingDiscountResult.DiscountAmount + order.OrderItems.Sum(x => x.DiscountAmount); - order.ShippingFeeAmount = shippingMethod.Price; - order.ShippingMethod = shippingMethod.Name; - order.TaxAmount = order.OrderItems.Sum(x => x.TaxAmount); - order.SubTotal = order.OrderItems.Sum(x => x.ProductPrice * x.Quantity); - order.SubTotalWithDiscount = order.SubTotal - checkingDiscountResult.DiscountAmount; - order.OrderTotal = order.SubTotal + order.TaxAmount + order.ShippingFeeAmount + order.PaymentFeeAmount - order.DiscountAmount; - _orderRepository.Add(order); - - var vendorIds = checkout.CheckoutItems.Where(x => x.Product.VendorId.HasValue).Select(x => x.Product.VendorId.Value).Distinct(); - if (vendorIds.Any()) - { - order.IsMasterOrder = true; - } + AddressLine1 = shippingAddress.AddressLine1, + AddressLine2 = shippingAddress.AddressLine2, + ContactName = shippingAddress.ContactName, + CountryId = shippingAddress.CountryId, + StateOrProvinceId = shippingAddress.StateOrProvinceId, + DistrictId = shippingAddress.DistrictId, + City = shippingAddress.City, + ZipCode = shippingAddress.ZipCode, + Phone = shippingAddress.Phone + }; - IList subOrders = new List(); - foreach (var vendorId in vendorIds) - { - var subOrder = new Order + var order = new Order { - CustomerId = checkout.CustomerId, + Customer = checkout.Customer, CreatedOn = DateTimeOffset.Now, - CreatedById = checkout.CreatedById, + CreatedBy = checkout.CreatedBy, LatestUpdatedOn = DateTimeOffset.Now, LatestUpdatedById = checkout.CreatedById, BillingAddress = orderBillingAddress, ShippingAddress = orderShippingAddress, - VendorId = vendorId, - Parent = order + PaymentMethod = paymentMethod, + PaymentFeeAmount = paymentFeeAmount }; - - foreach (var cartItem in checkout.CheckoutItems.Where(x => x.Product.VendorId == vendorId)) +using (var transaction = _checkoutItemRepository.BeginTransaction()){ + foreach (var checkoutItem in checkout.CheckoutItems) { - var taxPercent = await _taxService.GetTaxPercent(cartItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode); - var productPrice = cartItem.Product.Price; + if (!checkoutItem.Product.IsAllowToOrder || !checkoutItem.Product.IsPublished || checkoutItem.Product.IsDeleted) + { + return Result.Fail($"The product {checkoutItem.Product.Name} is not available any more"); + } + + if (checkoutItem.Product.StockTrackingIsEnabled && checkoutItem.Product.StockQuantity < checkoutItem.Quantity) + { + return Result.Fail($"There are only {checkoutItem.Product.StockQuantity} items available for {checkoutItem.Product.Name}"); + } + + var taxPercent = await _taxService.GetTaxPercent(checkoutItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode); + + var calculatedProductPrice = _productPricingService.CalculateProductPrice(checkoutItem.Product); + + var productPrice = calculatedProductPrice.OldPrice ?? calculatedProductPrice.Price; if (checkout.IsProductPriceIncludeTax) { productPrice = productPrice / (1 + (taxPercent / 100)); @@ -324,47 +238,120 @@ public async Task> CreateOrder(Guid checkoutId, string paymentMeth var orderItem = new OrderItem { - Product = cartItem.Product, + Product = checkoutItem.Product, ProductPrice = productPrice, - Quantity = cartItem.Quantity, + Quantity = checkoutItem.Quantity, TaxPercent = taxPercent, - TaxAmount = cartItem.Quantity * (productPrice * taxPercent / 100) + TaxAmount = checkoutItem.Quantity * (productPrice * taxPercent / 100) }; - if (checkout.IsProductPriceIncludeTax) + var discountedItem = checkingDiscountResult.DiscountedProducts.FirstOrDefault(x => x.Id == checkoutItem.ProductId); + if (discountedItem != null) { - orderItem.ProductPrice = orderItem.ProductPrice - orderItem.TaxAmount; + orderItem.DiscountAmount = discountedItem.DiscountAmount; } - subOrder.AddOrderItem(orderItem); + if (calculatedProductPrice.OldPrice.HasValue) + { + orderItem.DiscountAmount += orderItem.Quantity * (calculatedProductPrice.OldPrice.Value - calculatedProductPrice.Price); + } + + order.AddOrderItem(orderItem); + if (checkoutItem.Product.StockTrackingIsEnabled) + { + checkoutItem.Product.StockQuantity = checkoutItem.Product.StockQuantity - checkoutItem.Quantity; + } } + _checkoutItemRepository.SaveChanges(); + transaction.Commit(); +} - subOrder.SubTotal = subOrder.OrderItems.Sum(x => x.ProductPrice * x.Quantity); - subOrder.TaxAmount = subOrder.OrderItems.Sum(x => x.TaxAmount); - subOrder.OrderTotal = subOrder.SubTotal + subOrder.TaxAmount + subOrder.ShippingFeeAmount - subOrder.DiscountAmount; - _orderRepository.Add(subOrder); - subOrders.Add(subOrder); - } + order.OrderStatus = orderStatus; + order.OrderNote = checkout.OrderNote; + order.CouponCode = checkingDiscountResult.CouponCode; + order.CouponRuleName = checkout.CouponRuleName; + order.DiscountAmount = checkingDiscountResult.DiscountAmount + order.OrderItems.Sum(x => x.DiscountAmount); + order.ShippingFeeAmount = shippingMethod.Price; + order.ShippingMethod = shippingMethod.Name; + order.TaxAmount = order.OrderItems.Sum(x => x.TaxAmount); + order.SubTotal = order.OrderItems.Sum(x => x.ProductPrice * x.Quantity); + order.SubTotalWithDiscount = order.SubTotal - checkingDiscountResult.DiscountAmount; + order.OrderTotal = order.SubTotal + order.TaxAmount + order.ShippingFeeAmount + order.PaymentFeeAmount - order.DiscountAmount; + _orderRepository.Add(order); + + var vendorIds = checkout.CheckoutItems.Where(x => x.Product.VendorId.HasValue).Select(x => x.Product.VendorId.Value).Distinct(); + if (vendorIds.Any()) + { + order.IsMasterOrder = true; + } - using (var transaction = _orderRepository.BeginTransaction()) - { - _orderRepository.SaveChanges(); - await _mediator.Publish(new OrderCreated(order)); - foreach (var subOrder in subOrders) + IList subOrders = new List(); + foreach (var vendorId in vendorIds) { - await _mediator.Publish(new OrderCreated(subOrder)); + var subOrder = new Order + { + CustomerId = checkout.CustomerId, + CreatedOn = DateTimeOffset.Now, + CreatedById = checkout.CreatedById, + LatestUpdatedOn = DateTimeOffset.Now, + LatestUpdatedById = checkout.CreatedById, + BillingAddress = orderBillingAddress, + ShippingAddress = orderShippingAddress, + VendorId = vendorId, + Parent = order + }; + + foreach (var cartItem in checkout.CheckoutItems.Where(x => x.Product.VendorId == vendorId)) + { + var taxPercent = await _taxService.GetTaxPercent(cartItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode); + var productPrice = cartItem.Product.Price; + if (checkout.IsProductPriceIncludeTax) + { + productPrice = productPrice / (1 + (taxPercent / 100)); + } + + var orderItem = new OrderItem + { + Product = cartItem.Product, + ProductPrice = productPrice, + Quantity = cartItem.Quantity, + TaxPercent = taxPercent, + TaxAmount = cartItem.Quantity * (productPrice * taxPercent / 100) + }; + + if (checkout.IsProductPriceIncludeTax) + { + orderItem.ProductPrice = orderItem.ProductPrice - orderItem.TaxAmount; + } + + subOrder.AddOrderItem(orderItem); + } + + subOrder.SubTotal = subOrder.OrderItems.Sum(x => x.ProductPrice * x.Quantity); + subOrder.TaxAmount = subOrder.OrderItems.Sum(x => x.TaxAmount); + subOrder.OrderTotal = subOrder.SubTotal + subOrder.TaxAmount + subOrder.ShippingFeeAmount - subOrder.DiscountAmount; + _orderRepository.Add(subOrder); + subOrders.Add(subOrder); } - _couponService.AddCouponUsage(checkout.CustomerId, order.Id, checkingDiscountResult); - _orderRepository.SaveChanges(); - transaction.Commit(); - } + using (var transaction = _orderRepository.BeginTransaction()) + { + _orderRepository.SaveChanges(); + await _mediator.Publish(new OrderCreated(order)); + foreach (var subOrder in subOrders) + { + await _mediator.Publish(new OrderCreated(subOrder)); + } - await _mediator.Publish(new AfterOrderCreated(order)); + _couponService.AddCouponUsage(checkout.CustomerId, order.Id, checkingDiscountResult); + _orderRepository.SaveChanges(); + transaction.Commit(); + } - return Result.Ok(order); - } -} + await _mediator.Publish(new AfterOrderCreated(order)); + + return Result.Ok(order); + } public void CancelOrder(Order order) { From d4b035a9afb7e114e9f215199b4242c11adf8b30 Mon Sep 17 00:00:00 2001 From: Nishant Date: Sun, 5 Jan 2025 01:51:11 +0530 Subject: [PATCH 3/3] In this code: The transaction is started using _dbContext.Database.BeginTransaction(). The product is queried and locked for update using saveChanges(). The stock quantity is checked and updated within the transaction. The transaction is committed after the order is created. This ensures that the stock check and update are performed atomically, preventing race conditions. --- .../SimplCommerce.Module.Orders/Services/OrderService.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs b/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs index ea68c83df..623bb127b 100644 --- a/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs +++ b/src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs @@ -24,7 +24,6 @@ namespace SimplCommerce.Module.Orders.Services { public class OrderService : IOrderService { - private readonly DbContext _dbContext; private readonly IRepository _orderRepository; private readonly ICouponService _couponService; private readonly IRepository _checkoutItemRepository; @@ -47,10 +46,8 @@ public OrderService(IRepository orderRepository, IShippingPriceService shippingPriceService, IRepository userAddressRepository, IMediator mediator, - IProductPricingService productPricingService, - DbContext dbContext) + IProductPricingService productPricingService) { - _dbContext = dbContext; _orderRepository = orderRepository; _couponService = couponService; _checkoutItemRepository = checkoutItemRepository;