Skip to content

Commit 9e52db0

Browse files
Nishanthishamco
authored andcommitted
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.
1 parent 2c43b38 commit 9e52db0

File tree

1 file changed

+173
-154
lines changed

1 file changed

+173
-154
lines changed

src/Modules/SimplCommerce.Module.Orders/Services/OrderService.cs

Lines changed: 173 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -145,207 +145,226 @@ public async Task<Result<Order>> CreateOrder(Guid checkoutId, string paymentMeth
145145
}
146146

147147
public async Task<Result<Order>> CreateOrder(Guid checkoutId, string paymentMethod, decimal paymentFeeAmount, string shippingMethodName, Address billingAddress, Address shippingAddress, OrderStatus orderStatus = OrderStatus.New)
148-
{
149-
var checkout = _checkoutRepository
150-
.Query()
151-
.Include(c => c.CheckoutItems).ThenInclude(x => x.Product)
152-
.Include(c => c.Customer)
153-
.Include(c => c.CreatedBy)
154-
.Where(x => x.Id == checkoutId).FirstOrDefault();
148+
{
149+
var checkout = _checkoutRepository
150+
.Query()
151+
.Include(c => c.CheckoutItems).ThenInclude(x => x.Product)
152+
.Include(c => c.Customer)
153+
.Include(c => c.CreatedBy)
154+
.Where(x => x.Id == checkoutId).FirstOrDefault();
155+
156+
if (checkout == null)
157+
{
158+
return Result.Fail<Order>($"Checkout id {checkoutId} cannot be found");
159+
}
155160

156-
if (checkout == null)
161+
var checkingDiscountResult = await CheckForDiscountIfAny(checkout);
162+
if (!checkingDiscountResult.Succeeded)
163+
{
164+
return Result.Fail<Order>(checkingDiscountResult.ErrorMessage);
165+
}
166+
167+
var validateShippingMethodResult = await ValidateShippingMethod(shippingMethodName, shippingAddress, checkout);
168+
if (!validateShippingMethodResult.Success)
169+
{
170+
return Result.Fail<Order>(validateShippingMethodResult.Error);
171+
}
172+
173+
var shippingMethod = validateShippingMethodResult.Value;
174+
175+
var orderBillingAddress = new OrderAddress()
176+
{
177+
AddressLine1 = billingAddress.AddressLine1,
178+
AddressLine2 = billingAddress.AddressLine2,
179+
ContactName = billingAddress.ContactName,
180+
CountryId = billingAddress.CountryId,
181+
StateOrProvinceId = billingAddress.StateOrProvinceId,
182+
DistrictId = billingAddress.DistrictId,
183+
City = billingAddress.City,
184+
ZipCode = billingAddress.ZipCode,
185+
Phone = billingAddress.Phone
186+
};
187+
188+
var orderShippingAddress = new OrderAddress()
189+
{
190+
AddressLine1 = shippingAddress.AddressLine1,
191+
AddressLine2 = shippingAddress.AddressLine2,
192+
ContactName = shippingAddress.ContactName,
193+
CountryId = shippingAddress.CountryId,
194+
StateOrProvinceId = shippingAddress.StateOrProvinceId,
195+
DistrictId = shippingAddress.DistrictId,
196+
City = shippingAddress.City,
197+
ZipCode = shippingAddress.ZipCode,
198+
Phone = shippingAddress.Phone
199+
};
200+
201+
var order = new Order
202+
{
203+
Customer = checkout.Customer,
204+
CreatedOn = DateTimeOffset.Now,
205+
CreatedBy = checkout.CreatedBy,
206+
LatestUpdatedOn = DateTimeOffset.Now,
207+
LatestUpdatedById = checkout.CreatedById,
208+
BillingAddress = orderBillingAddress,
209+
ShippingAddress = orderShippingAddress,
210+
PaymentMethod = paymentMethod,
211+
PaymentFeeAmount = paymentFeeAmount
212+
};
213+
214+
using (var transaction = _dbContext.Database.BeginTransaction())
215+
{
216+
foreach (var checkoutItem in checkout.CheckoutItems)
217+
{
218+
if (!checkoutItem.Product.IsAllowToOrder || !checkoutItem.Product.IsPublished || checkoutItem.Product.IsDeleted)
157219
{
158-
return Result.Fail<Order>($"Checkout id {checkoutId} cannot be found");
220+
return Result.Fail<Order>($"The product {checkoutItem.Product.Name} is not available any more");
159221
}
160222

161-
var checkingDiscountResult = await CheckForDiscountIfAny(checkout);
162-
if (!checkingDiscountResult.Succeeded)
223+
var product = _dbContext.Products
224+
.Where(p => p.Id == checkoutItem.ProductId)
225+
.FirstOrDefault();
226+
227+
if (product == null)
163228
{
164-
return Result.Fail<Order>(checkingDiscountResult.ErrorMessage);
229+
return Result.Fail<Order>("Product not found");
165230
}
166231

167-
var validateShippingMethodResult = await ValidateShippingMethod(shippingMethodName, shippingAddress, checkout);
168-
if (!validateShippingMethodResult.Success)
232+
if (product.StockTrackingIsEnabled)
169233
{
170-
return Result.Fail<Order>(validateShippingMethodResult.Error);
234+
_dbContext.Products
235+
.Where(p => p.Id == checkoutItem.ProductId)
236+
.ForUpdate()
237+
.FirstOrDefault();
238+
239+
if (product.StockQuantity < checkoutItem.Quantity)
240+
{
241+
return Result.Fail<Order>($"There are only {product.StockQuantity} items available for {product.Name}");
242+
}
243+
244+
product.StockQuantity -= checkoutItem.Quantity;
245+
_dbContext.SaveChanges();
171246
}
172247

173-
var shippingMethod = validateShippingMethodResult.Value;
248+
var taxPercent = await _taxService.GetTaxPercent(checkoutItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode);
249+
250+
var calculatedProductPrice = _productPricingService.CalculateProductPrice(checkoutItem.Product);
174251

175-
var orderBillingAddress = new OrderAddress()
252+
var productPrice = calculatedProductPrice.OldPrice ?? calculatedProductPrice.Price;
253+
if (checkout.IsProductPriceIncludeTax)
176254
{
177-
AddressLine1 = billingAddress.AddressLine1,
178-
AddressLine2 = billingAddress.AddressLine2,
179-
ContactName = billingAddress.ContactName,
180-
CountryId = billingAddress.CountryId,
181-
StateOrProvinceId = billingAddress.StateOrProvinceId,
182-
DistrictId = billingAddress.DistrictId,
183-
City = billingAddress.City,
184-
ZipCode = billingAddress.ZipCode,
185-
Phone = billingAddress.Phone
186-
};
255+
productPrice = productPrice / (1 + (taxPercent / 100));
256+
}
187257

188-
var orderShippingAddress = new OrderAddress()
258+
var orderItem = new OrderItem
189259
{
190-
AddressLine1 = shippingAddress.AddressLine1,
191-
AddressLine2 = shippingAddress.AddressLine2,
192-
ContactName = shippingAddress.ContactName,
193-
CountryId = shippingAddress.CountryId,
194-
StateOrProvinceId = shippingAddress.StateOrProvinceId,
195-
DistrictId = shippingAddress.DistrictId,
196-
City = shippingAddress.City,
197-
ZipCode = shippingAddress.ZipCode,
198-
Phone = shippingAddress.Phone
260+
Product = checkoutItem.Product,
261+
ProductPrice = productPrice,
262+
Quantity = checkoutItem.Quantity,
263+
TaxPercent = taxPercent,
264+
TaxAmount = checkoutItem.Quantity * (productPrice * taxPercent / 100)
199265
};
200266

201-
var order = new Order
267+
var discountedItem = checkingDiscountResult.DiscountedProducts.FirstOrDefault(x => x.Id == checkoutItem.ProductId);
268+
if (discountedItem != null)
202269
{
203-
Customer = checkout.Customer,
270+
orderItem.DiscountAmount = discountedItem.DiscountAmount;
271+
}
272+
273+
if (calculatedProductPrice.OldPrice.HasValue)
274+
{
275+
orderItem.DiscountAmount += orderItem.Quantity * (calculatedProductPrice.OldPrice.Value - calculatedProductPrice.Price);
276+
}
277+
278+
order.AddOrderItem(orderItem);
279+
}
280+
281+
order.OrderStatus = orderStatus;
282+
order.OrderNote = checkout.OrderNote;
283+
order.CouponCode = checkingDiscountResult.CouponCode;
284+
order.CouponRuleName = checkout.CouponRuleName;
285+
order.DiscountAmount = checkingDiscountResult.DiscountAmount + order.OrderItems.Sum(x => x.DiscountAmount);
286+
order.ShippingFeeAmount = shippingMethod.Price;
287+
order.ShippingMethod = shippingMethod.Name;
288+
order.TaxAmount = order.OrderItems.Sum(x => x.TaxAmount);
289+
order.SubTotal = order.OrderItems.Sum(x => x.ProductPrice * x.Quantity);
290+
order.SubTotalWithDiscount = order.SubTotal - checkingDiscountResult.DiscountAmount;
291+
order.OrderTotal = order.SubTotal + order.TaxAmount + order.ShippingFeeAmount + order.PaymentFeeAmount - order.DiscountAmount;
292+
_orderRepository.Add(order);
293+
294+
var vendorIds = checkout.CheckoutItems.Where(x => x.Product.VendorId.HasValue).Select(x => x.Product.VendorId.Value).Distinct();
295+
if (vendorIds.Any())
296+
{
297+
order.IsMasterOrder = true;
298+
}
299+
300+
IList<Order> subOrders = new List<Order>();
301+
foreach (var vendorId in vendorIds)
302+
{
303+
var subOrder = new Order
304+
{
305+
CustomerId = checkout.CustomerId,
204306
CreatedOn = DateTimeOffset.Now,
205-
CreatedBy = checkout.CreatedBy,
307+
CreatedById = checkout.CreatedById,
206308
LatestUpdatedOn = DateTimeOffset.Now,
207309
LatestUpdatedById = checkout.CreatedById,
208310
BillingAddress = orderBillingAddress,
209311
ShippingAddress = orderShippingAddress,
210-
PaymentMethod = paymentMethod,
211-
PaymentFeeAmount = paymentFeeAmount
312+
VendorId = vendorId,
313+
Parent = order
212314
};
213315

214-
foreach (var checkoutItem in checkout.CheckoutItems)
316+
foreach (var cartItem in checkout.CheckoutItems.Where(x => x.Product.VendorId == vendorId))
215317
{
216-
if (!checkoutItem.Product.IsAllowToOrder || !checkoutItem.Product.IsPublished || checkoutItem.Product.IsDeleted)
217-
{
218-
return Result.Fail<Order>($"The product {checkoutItem.Product.Name} is not available any more");
219-
}
220-
221-
if (checkoutItem.Product.StockTrackingIsEnabled && checkoutItem.Product.StockQuantity < checkoutItem.Quantity)
222-
{
223-
return Result.Fail<Order>($"There are only {checkoutItem.Product.StockQuantity} items available for {checkoutItem.Product.Name}");
224-
}
225-
226-
var taxPercent = await _taxService.GetTaxPercent(checkoutItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode);
227-
228-
var calculatedProductPrice = _productPricingService.CalculateProductPrice(checkoutItem.Product);
229-
230-
var productPrice = calculatedProductPrice.OldPrice ?? calculatedProductPrice.Price;
318+
var taxPercent = await _taxService.GetTaxPercent(cartItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode);
319+
var productPrice = cartItem.Product.Price;
231320
if (checkout.IsProductPriceIncludeTax)
232321
{
233322
productPrice = productPrice / (1 + (taxPercent / 100));
234323
}
235324

236325
var orderItem = new OrderItem
237326
{
238-
Product = checkoutItem.Product,
327+
Product = cartItem.Product,
239328
ProductPrice = productPrice,
240-
Quantity = checkoutItem.Quantity,
329+
Quantity = cartItem.Quantity,
241330
TaxPercent = taxPercent,
242-
TaxAmount = checkoutItem.Quantity * (productPrice * taxPercent / 100)
331+
TaxAmount = cartItem.Quantity * (productPrice * taxPercent / 100)
243332
};
244333

245-
var discountedItem = checkingDiscountResult.DiscountedProducts.FirstOrDefault(x => x.Id == checkoutItem.ProductId);
246-
if (discountedItem != null)
247-
{
248-
orderItem.DiscountAmount = discountedItem.DiscountAmount;
249-
}
250-
251-
if (calculatedProductPrice.OldPrice.HasValue)
334+
if (checkout.IsProductPriceIncludeTax)
252335
{
253-
orderItem.DiscountAmount += orderItem.Quantity * (calculatedProductPrice.OldPrice.Value - calculatedProductPrice.Price);
336+
orderItem.ProductPrice = orderItem.ProductPrice - orderItem.TaxAmount;
254337
}
255338

256-
order.AddOrderItem(orderItem);
257-
if (checkoutItem.Product.StockTrackingIsEnabled)
258-
{
259-
checkoutItem.Product.StockQuantity = checkoutItem.Product.StockQuantity - checkoutItem.Quantity;
260-
}
339+
subOrder.AddOrderItem(orderItem);
261340
}
262341

263-
order.OrderStatus = orderStatus;
264-
order.OrderNote = checkout.OrderNote;
265-
order.CouponCode = checkingDiscountResult.CouponCode;
266-
order.CouponRuleName = checkout.CouponRuleName;
267-
order.DiscountAmount = checkingDiscountResult.DiscountAmount + order.OrderItems.Sum(x => x.DiscountAmount);
268-
order.ShippingFeeAmount = shippingMethod.Price;
269-
order.ShippingMethod = shippingMethod.Name;
270-
order.TaxAmount = order.OrderItems.Sum(x => x.TaxAmount);
271-
order.SubTotal = order.OrderItems.Sum(x => x.ProductPrice * x.Quantity);
272-
order.SubTotalWithDiscount = order.SubTotal - checkingDiscountResult.DiscountAmount;
273-
order.OrderTotal = order.SubTotal + order.TaxAmount + order.ShippingFeeAmount + order.PaymentFeeAmount - order.DiscountAmount;
274-
_orderRepository.Add(order);
275-
276-
var vendorIds = checkout.CheckoutItems.Where(x => x.Product.VendorId.HasValue).Select(x => x.Product.VendorId.Value).Distinct();
277-
if (vendorIds.Any())
278-
{
279-
order.IsMasterOrder = true;
280-
}
342+
subOrder.SubTotal = subOrder.OrderItems.Sum(x => x.ProductPrice * x.Quantity);
343+
subOrder.TaxAmount = subOrder.OrderItems.Sum(x => x.TaxAmount);
344+
subOrder.OrderTotal = subOrder.SubTotal + subOrder.TaxAmount + subOrder.ShippingFeeAmount - subOrder.DiscountAmount;
345+
_orderRepository.Add(subOrder);
346+
subOrders.Add(subOrder);
347+
}
281348

282-
IList<Order> subOrders = new List<Order>();
283-
foreach (var vendorId in vendorIds)
349+
using (var transaction = _orderRepository.BeginTransaction())
350+
{
351+
_orderRepository.SaveChanges();
352+
await _mediator.Publish(new OrderCreated(order));
353+
foreach (var subOrder in subOrders)
284354
{
285-
var subOrder = new Order
286-
{
287-
CustomerId = checkout.CustomerId,
288-
CreatedOn = DateTimeOffset.Now,
289-
CreatedById = checkout.CreatedById,
290-
LatestUpdatedOn = DateTimeOffset.Now,
291-
LatestUpdatedById = checkout.CreatedById,
292-
BillingAddress = orderBillingAddress,
293-
ShippingAddress = orderShippingAddress,
294-
VendorId = vendorId,
295-
Parent = order
296-
};
297-
298-
foreach (var cartItem in checkout.CheckoutItems.Where(x => x.Product.VendorId == vendorId))
299-
{
300-
var taxPercent = await _taxService.GetTaxPercent(cartItem.Product.TaxClassId, shippingAddress.CountryId, shippingAddress.StateOrProvinceId, shippingAddress.ZipCode);
301-
var productPrice = cartItem.Product.Price;
302-
if (checkout.IsProductPriceIncludeTax)
303-
{
304-
productPrice = productPrice / (1 + (taxPercent / 100));
305-
}
306-
307-
var orderItem = new OrderItem
308-
{
309-
Product = cartItem.Product,
310-
ProductPrice = productPrice,
311-
Quantity = cartItem.Quantity,
312-
TaxPercent = taxPercent,
313-
TaxAmount = cartItem.Quantity * (productPrice * taxPercent / 100)
314-
};
315-
316-
if (checkout.IsProductPriceIncludeTax)
317-
{
318-
orderItem.ProductPrice = orderItem.ProductPrice - orderItem.TaxAmount;
319-
}
320-
321-
subOrder.AddOrderItem(orderItem);
322-
}
323-
324-
subOrder.SubTotal = subOrder.OrderItems.Sum(x => x.ProductPrice * x.Quantity);
325-
subOrder.TaxAmount = subOrder.OrderItems.Sum(x => x.TaxAmount);
326-
subOrder.OrderTotal = subOrder.SubTotal + subOrder.TaxAmount + subOrder.ShippingFeeAmount - subOrder.DiscountAmount;
327-
_orderRepository.Add(subOrder);
328-
subOrders.Add(subOrder);
355+
await _mediator.Publish(new OrderCreated(subOrder));
329356
}
330357

331-
using (var transaction = _orderRepository.BeginTransaction())
332-
{
333-
_orderRepository.SaveChanges();
334-
await _mediator.Publish(new OrderCreated(order));
335-
foreach (var subOrder in subOrders)
336-
{
337-
await _mediator.Publish(new OrderCreated(subOrder));
338-
}
339-
340-
_couponService.AddCouponUsage(checkout.CustomerId, order.Id, checkingDiscountResult);
341-
_orderRepository.SaveChanges();
342-
transaction.Commit();
343-
}
358+
_couponService.AddCouponUsage(checkout.CustomerId, order.Id, checkingDiscountResult);
359+
_orderRepository.SaveChanges();
360+
transaction.Commit();
361+
}
344362

345-
await _mediator.Publish(new AfterOrderCreated(order));
363+
await _mediator.Publish(new AfterOrderCreated(order));
346364

347-
return Result.Ok(order);
348-
}
365+
return Result.Ok(order);
366+
}
367+
}
349368

350369
public void CancelOrder(Order order)
351370
{

0 commit comments

Comments
 (0)