/// <summary> /// Check discount requirement /// </summary> /// <param name="request">Object that contains all information required to check the requirement (Current customer, discount, etc)</param> /// <returns>true - requirement is met; otherwise, false</returns> public DiscountRequirementValidationResult CheckRequirement(DiscountRequirementValidationRequest request) { if (request == null) throw new ArgumentNullException("request"); //invalid by default var result = new DiscountRequirementValidationResult(); if (request.Customer == null) return result; var restrictedToCustomerRoleId = _settingService.GetSettingByKey<int>(string.Format("DiscountRequirement.MustBeAssignedToCustomerRole-{0}-{1}", request.DiscountId, request.DiscountRequirementId)); if (restrictedToCustomerRoleId == 0) return result; foreach (var customerRole in request.Customer.CustomerRoles.Where(cr => cr.Active).ToList()) if (restrictedToCustomerRoleId == customerRole.Id) { //valid result.IsValid = true; return result; } return result; }
/// <summary> /// Check discount requirement /// </summary> /// <param name="request">Object that contains all information required to check the requirement (Current customer, discount, etc)</param> /// <returns>Result</returns> public DiscountRequirementValidationResult CheckRequirement(DiscountRequirementValidationRequest request) { if (request == null) throw new ArgumentNullException("request"); //invalid by default var result = new DiscountRequirementValidationResult(); var spentAmountRequirement = _settingService.GetSettingByKey<decimal>(string.Format("DiscountRequirement.HadSpentAmount-{0}", request.DiscountRequirementId)); if (spentAmountRequirement == decimal.Zero) { //valid result.IsValid = true; return result; } if (request.Customer == null || request.Customer.IsGuest()) return result; var orders = _orderService.SearchOrders(storeId: request.Store.Id, customerId: request.Customer.Id, os: OrderStatus.Complete); decimal spentAmount = orders.Sum(o => o.OrderTotal); if (spentAmount > spentAmountRequirement) { result.IsValid = true; } else { result.UserError = _localizationService.GetResource("Plugins.DiscountRules.HadSpentAmount.NotEnough"); } return result; }
/// <summary> /// Check discount requirement /// </summary> /// <param name="request">Object that contains all information required to check the requirement (Current customer, discount, etc)</param> /// <returns>Result</returns> public DiscountRequirementValidationResult CheckRequirement(DiscountRequirementValidationRequest request) { return new DiscountRequirementValidationResult { IsValid = true }; }
/// <summary> /// Check discount requirement /// </summary> /// <param name="request">Object that contains all information required to check the requirement (Current customer, discount, etc)</param> /// <returns>Result</returns> public DiscountRequirementValidationResult CheckRequirement(DiscountRequirementValidationRequest request) { if (request == null) throw new ArgumentNullException("request"); //invalid by default var result = new DiscountRequirementValidationResult(); var restrictedProductIds = _settingService.GetSettingByKey<string>(string.Format("DiscountRequirement.RestrictedProductIds-{0}", request.DiscountRequirementId)); if (String.IsNullOrWhiteSpace(restrictedProductIds)) { //valid result.IsValid = true; return result; } if (request.Customer == null) return result; //we support three ways of specifying products: //1. The comma-separated list of product identifiers (e.g. 77, 123, 156). //2. The comma-separated list of product identifiers with quantities. // {Product ID}:{Quantity}. For example, 77:1, 123:2, 156:3 //3. The comma-separated list of product identifiers with quantity range. // {Product ID}:{Min quantity}-{Max quantity}. For example, 77:1-3, 123:2-5, 156:3-8 var restrictedProducts = restrictedProductIds .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .ToList(); if (!restrictedProducts.Any()) return result; //group products in the cart by product ID //it could be the same product with distinct product attributes //that's why we get the total quantity of this product var cartQuery = from sci in request.Customer.ShoppingCartItems.LimitPerStore(request.Store.Id) where sci.ShoppingCartType == ShoppingCartType.ShoppingCart group sci by sci.ProductId into g select new { ProductId = g.Key, TotalQuantity = g.Sum(x => x.Quantity) }; var cart = cartQuery.ToList(); bool allFound = true; foreach (var restrictedProduct in restrictedProducts) { if (String.IsNullOrWhiteSpace(restrictedProduct)) continue; bool found1 = false; foreach (var sci in cart) { if (restrictedProduct.Contains(":")) { if (restrictedProduct.Contains("-")) { //the third way (the quantity rage specified) //{Product ID}:{Min quantity}-{Max quantity}. For example, 77:1-3, 123:2-5, 156:3-8 int restrictedProductId; if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[0], out restrictedProductId)) //parsing error; exit; return result; int quantityMin; if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[1].Split(new[] { '-' })[0], out quantityMin)) //parsing error; exit; return result; int quantityMax; if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[1].Split(new[] { '-' })[1], out quantityMax)) //parsing error; exit; return result; if (sci.ProductId == restrictedProductId && quantityMin <= sci.TotalQuantity && sci.TotalQuantity <= quantityMax) { found1 = true; break; } } else { //the second way (the quantity specified) //{Product ID}:{Quantity}. For example, 77:1, 123:2, 156:3 int restrictedProductId; if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[0], out restrictedProductId)) //parsing error; exit; return result; int quantity; if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[1], out quantity)) //parsing error; exit; return result; if (sci.ProductId == restrictedProductId && sci.TotalQuantity == quantity) { found1 = true; break; } } } else { //the first way (the quantity is not specified) int restrictedProductId; if (int.TryParse(restrictedProduct, out restrictedProductId)) { if (sci.ProductId == restrictedProductId) { found1 = true; break; } } } } if (!found1) { allFound = false; break; } } if (allFound) { //valid result.IsValid = true; return result; } return result; }
/// <summary> /// Validate discount /// </summary> /// <param name="discount">Discount</param> /// <param name="customer">Customer</param> /// <param name="couponCodeToValidate">Coupon code to validate</param> /// <returns>Discount validation result</returns> public virtual DiscountValidationResult ValidateDiscount(Discount discount, Customer customer, string couponCodeToValidate) { if (discount == null) { throw new ArgumentNullException("discount"); } if (customer == null) { throw new ArgumentNullException("customer"); } //invalid by default var result = new DiscountValidationResult(); //check coupon code if (discount.RequiresCouponCode) { if (String.IsNullOrEmpty(discount.CouponCode)) { return(result); } if (!discount.CouponCode.Equals(couponCodeToValidate, StringComparison.InvariantCultureIgnoreCase)) { return(result); } } //Do not allow discounts applied to order subtotal or total when a customer has gift cards in the cart. //Otherwise, this customer can purchase gift cards with discount and get more than paid ("free money"). if (discount.DiscountType == DiscountType.AssignedToOrderSubTotal || discount.DiscountType == DiscountType.AssignedToOrderTotal) { var cart = customer.ShoppingCartItems .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) .LimitPerStore(_storeContext.CurrentStore.Id) .ToList(); var hasGiftCards = cart.Any(x => x.Product.IsGiftCard); if (hasGiftCards) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedWithGiftCards"); return(result); } } //check date range DateTime now = DateTime.UtcNow; if (discount.StartDateUtc.HasValue) { DateTime startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); if (startDate.CompareTo(now) > 0) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.NotStartedYet"); return(result); } } if (discount.EndDateUtc.HasValue) { DateTime endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); if (endDate.CompareTo(now) < 0) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.Expired"); return(result); } } //discount limitation switch (discount.DiscountLimitation) { case DiscountLimitationType.NTimesOnly: { var usedTimes = GetAllDiscountUsageHistory(discount.Id, null, null, 0, 1).TotalCount; if (usedTimes >= discount.LimitationTimes) { return(result); } } break; case DiscountLimitationType.NTimesPerCustomer: { if (customer.IsRegistered()) { var usedTimes = GetAllDiscountUsageHistory(discount.Id, customer.Id, null, 0, 1).TotalCount; if (usedTimes >= discount.LimitationTimes) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedAnymore"); return(result); } } } break; case DiscountLimitationType.Unlimited: default: break; } //discount requirements //UNDONE we should inject static cache manager into constructor. we we already have "per request" cache manager injected. better way to do it? //we cache meta info of discount requirements. this way we should not load them for each HTTP request var staticCacheManager = EngineContext.Current.ContainerManager.Resolve <ICacheManager>("nop_cache_static"); string key = string.Format(DiscountRequirementEventConsumer.DISCOUNT_REQUIREMENT_MODEL_KEY, discount.Id); //var requirements = discount.DiscountRequirements; var requirements = staticCacheManager.Get(key, () => { var cachedRequirements = new List <DiscountRequirementForCaching>(); foreach (var dr in discount.DiscountRequirements) { cachedRequirements.Add(new DiscountRequirementForCaching { Id = dr.Id, SystemName = dr.DiscountRequirementRuleSystemName }); } return(cachedRequirements); }); foreach (var req in requirements) { //load a plugin var requirementRulePlugin = LoadDiscountRequirementRuleBySystemName(req.SystemName); if (requirementRulePlugin == null) { continue; } if (!_pluginFinder.AuthenticateStore(requirementRulePlugin.PluginDescriptor, _storeContext.CurrentStore.Id)) { continue; } var ruleRequest = new DiscountRequirementValidationRequest { DiscountRequirementId = req.Id, Customer = customer, Store = _storeContext.CurrentStore }; var ruleResult = requirementRulePlugin.CheckRequirement(ruleRequest); if (!ruleResult.IsValid) { result.UserError = ruleResult.UserError; return(result); } } result.IsValid = true; return(result); }
/// <summary> /// Validate discount /// </summary> /// <param name="discount">Discount</param> /// <param name="customer">Customer</param> /// <param name="couponCodeToValidate">Coupon code to validate</param> /// <returns>Discount validation result</returns> public virtual DiscountValidationResult ValidateDiscount(Discount discount, Customer customer, string couponCodeToValidate) { if (discount == null) throw new ArgumentNullException("discount"); if (customer == null) throw new ArgumentNullException("customer"); //invalid by default var result = new DiscountValidationResult(); //check coupon code if (discount.RequiresCouponCode) { if (String.IsNullOrEmpty(discount.CouponCode)) return result; if (!discount.CouponCode.Equals(couponCodeToValidate, StringComparison.InvariantCultureIgnoreCase)) return result; } //check date range DateTime now = DateTime.UtcNow; if (discount.StartDateUtc.HasValue) { DateTime startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); if (startDate.CompareTo(now) > 0) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.NotStartedYet"); return result; } } if (discount.EndDateUtc.HasValue) { DateTime endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); if (endDate.CompareTo(now) < 0) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.Expired"); return result; } } //discount limitation switch (discount.DiscountLimitation) { case DiscountLimitationType.NTimesOnly: { var usedTimes = GetAllDiscountUsageHistory(discount.Id, null, null, 0, 1).TotalCount; if (usedTimes >= discount.LimitationTimes) return result; } break; case DiscountLimitationType.NTimesPerCustomer: { if (customer.IsRegistered()) { var usedTimes = GetAllDiscountUsageHistory(discount.Id, customer.Id, null, 0, 1).TotalCount; if (usedTimes >= discount.LimitationTimes) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedAnymore"); return result; } } } break; case DiscountLimitationType.Unlimited: default: break; } //discount requirements var requirements = discount.DiscountRequirements; foreach (var req in requirements) { var requirementRule = LoadDiscountRequirementRuleBySystemName(req.DiscountRequirementRuleSystemName); if (requirementRule == null) continue; if (!_pluginFinder.AuthenticateStore(requirementRule.PluginDescriptor, _storeContext.CurrentStore.Id)) continue; var ruleRequest = new DiscountRequirementValidationRequest { DiscountRequirement = req, Customer = customer, Store = _storeContext.CurrentStore }; var ruleResult = requirementRule.CheckRequirement(ruleRequest); if (!ruleResult.IsValid) { result.UserError = ruleResult.UserError; return result; } } //Do not allow discounts applied to order subtotal or total when a customer has gift cards in the cart. //Otherwise, this customer can purchase gift cards with discount and get more than paid ("free money"). if (discount.DiscountType == DiscountType.AssignedToOrderSubTotal || discount.DiscountType == DiscountType.AssignedToOrderTotal) { var cart = customer.ShoppingCartItems .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) .LimitPerStore(_storeContext.CurrentStore.Id) .ToList(); var hasGiftCards = cart.Any(x => x.Product.IsGiftCard); if (hasGiftCards) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedWithGiftCards"); return result; } } result.IsValid = true; return result; }
/// <summary> /// Validate discount /// </summary> /// <param name="discount">Discount</param> /// <param name="customer">Customer</param> /// <param name="couponCodeToValidate">Coupon code to validate</param> /// <returns>Discount validation result</returns> public virtual DiscountValidationResult ValidateDiscount(Discount discount, Customer customer, string couponCodeToValidate) { if (discount == null) throw new ArgumentNullException("discount"); if (customer == null) throw new ArgumentNullException("customer"); //invalid by default var result = new DiscountValidationResult(); //check coupon code if (discount.RequiresCouponCode) { if (String.IsNullOrEmpty(discount.CouponCode)) return result; if (!discount.CouponCode.Equals(couponCodeToValidate, StringComparison.InvariantCultureIgnoreCase)) return result; } //Do not allow discounts applied to order subtotal or total when a customer has gift cards in the cart. //Otherwise, this customer can purchase gift cards with discount and get more than paid ("free money"). if (discount.DiscountType == DiscountType.AssignedToOrderSubTotal || discount.DiscountType == DiscountType.AssignedToOrderTotal) { var cart = customer.ShoppingCartItems .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) .LimitPerStore(_storeContext.CurrentStore.Id) .ToList(); var hasGiftCards = cart.Any(x => x.Product.IsGiftCard); if (hasGiftCards) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedWithGiftCards"); return result; } } //check date range DateTime now = DateTime.UtcNow; if (discount.StartDateUtc.HasValue) { DateTime startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); if (startDate.CompareTo(now) > 0) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.NotStartedYet"); return result; } } if (discount.EndDateUtc.HasValue) { DateTime endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); if (endDate.CompareTo(now) < 0) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.Expired"); return result; } } //discount limitation switch (discount.DiscountLimitation) { case DiscountLimitationType.NTimesOnly: { var usedTimes = GetAllDiscountUsageHistory(discount.Id, null, null, 0, 1).TotalCount; if (usedTimes >= discount.LimitationTimes) return result; } break; case DiscountLimitationType.NTimesPerCustomer: { if (customer.IsRegistered()) { var usedTimes = GetAllDiscountUsageHistory(discount.Id, customer.Id, null, 0, 1).TotalCount; if (usedTimes >= discount.LimitationTimes) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedAnymore"); return result; } } } break; case DiscountLimitationType.Unlimited: default: break; } //discount requirements //UNDONE we should inject static cache manager into constructor. we we already have "per request" cache manager injected. better way to do it? //we cache meta info of rdiscount requirements. this way we should not load them for each HTTP request var staticCacheManager = EngineContext.Current.ContainerManager.Resolve<ICacheManager>("nop_cache_static"); string key = string.Format(DiscountRequirementEventConsumer.DISCOUNT_REQUIREMENT_MODEL_KEY, discount.Id); //var requirements = discount.DiscountRequirements; var requirements = staticCacheManager.Get(key, () => { var cachedRequirements = new List<DiscountRequirementForCaching>(); foreach (var dr in discount.DiscountRequirements) cachedRequirements.Add(new DiscountRequirementForCaching { Id = dr.Id, SystemName = dr.DiscountRequirementRuleSystemName }); return cachedRequirements; }); foreach (var req in requirements) { //load a plugin var requirementRulePlugin = LoadDiscountRequirementRuleBySystemName(req.SystemName); if (requirementRulePlugin == null) continue; if (!_pluginFinder.AuthenticateStore(requirementRulePlugin.PluginDescriptor, _storeContext.CurrentStore.Id)) continue; var ruleRequest = new DiscountRequirementValidationRequest { DiscountRequirementId = req.Id, Customer = customer, Store = _storeContext.CurrentStore }; var ruleResult = requirementRulePlugin.CheckRequirement(ruleRequest); if (!ruleResult.IsValid) { result.UserError = ruleResult.UserError; return result; } } result.IsValid = true; return result; }
/// <summary> /// Validate discount /// </summary> /// <param name="discount">Discount</param> /// <param name="customer">Customer</param> /// <param name="couponCodeToValidate">Coupon code to validate</param> /// <returns>Discount validation result</returns> public virtual DiscountValidationResult ValidateDiscount(Discount discount, Customer customer, string couponCodeToValidate) { if (discount == null) { throw new ArgumentNullException("discount"); } if (customer == null) { throw new ArgumentNullException("customer"); } //invalid by default var result = new DiscountValidationResult(); //check coupon code if (discount.RequiresCouponCode) { if (String.IsNullOrEmpty(discount.CouponCode)) { return(result); } if (!discount.CouponCode.Equals(couponCodeToValidate, StringComparison.InvariantCultureIgnoreCase)) { return(result); } } //check date range DateTime now = DateTime.UtcNow; if (discount.StartDateUtc.HasValue) { DateTime startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); if (startDate.CompareTo(now) > 0) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.NotStartedYet"); return(result); } } if (discount.EndDateUtc.HasValue) { DateTime endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); if (endDate.CompareTo(now) < 0) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.Expired"); return(result); } } //discount limitation switch (discount.DiscountLimitation) { case DiscountLimitationType.NTimesOnly: { var usedTimes = GetAllDiscountUsageHistory(discount.Id, null, null, 0, 1).TotalCount; if (usedTimes >= discount.LimitationTimes) { return(result); } } break; case DiscountLimitationType.NTimesPerCustomer: { if (customer.IsRegistered()) { var usedTimes = GetAllDiscountUsageHistory(discount.Id, customer.Id, null, 0, 1).TotalCount; if (usedTimes >= discount.LimitationTimes) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedAnymore"); return(result); } } } break; case DiscountLimitationType.Unlimited: default: break; } //discount requirements var requirements = discount.DiscountRequirements; foreach (var req in requirements) { var requirementRule = LoadDiscountRequirementRuleBySystemName(req.DiscountRequirementRuleSystemName); if (requirementRule == null) { continue; } if (!_pluginFinder.AuthenticateStore(requirementRule.PluginDescriptor, _storeContext.CurrentStore.Id)) { continue; } var ruleRequest = new DiscountRequirementValidationRequest { DiscountRequirement = req, Customer = customer, Store = _storeContext.CurrentStore }; var ruleResult = requirementRule.CheckRequirement(ruleRequest); if (!ruleResult.IsValid) { result.UserError = ruleResult.UserError; return(result); } } //Do not allow discounts applied to order subtotal or total when a customer has gift cards in the cart. //Otherwise, this customer can purchase gift cards with discount and get more than paid ("free money"). if (discount.DiscountType == DiscountType.AssignedToOrderSubTotal || discount.DiscountType == DiscountType.AssignedToOrderTotal) { var cart = customer.ShoppingCartItems .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) .LimitPerStore(_storeContext.CurrentStore.Id) .ToList(); var hasGiftCards = cart.Any(x => x.Product.IsGiftCard); if (hasGiftCards) { result.UserError = _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedWithGiftCards"); return(result); } } result.IsValid = true; return(result); }