/// <summary> /// Gets a value indicating whether customers can complete a payment after order is placed but not completed (for redirection payment methods) /// </summary> /// <param name="order">Order</param> /// <returns>Result</returns> public virtual bool CanRePostProcessPayment(Order order) { if (order == null) throw new ArgumentNullException("order"); if (!_paymentSettings.AllowRePostingPayments) return false; var paymentMethod = LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); if (paymentMethod == null) return false; //Payment method couldn't be loaded (for example, was uninstalled) if (paymentMethod.PaymentMethodType != PaymentMethodType.Redirection) return false; //this option is available only for redirection payment methods if (order.Deleted) return false; //do not allow for deleted orders if (order.OrderStatus == OrderStatus.Cancelled) return false; //do not allow for cancelled orders if (order.PaymentStatus != PaymentStatus.Pending) return false; //payment status should be Pending return paymentMethod.CanRePostProcessPayment(order); }
/// <summary> /// Deletes an order /// </summary> /// <param name="order">The order</param> public virtual void DeleteOrder(Order order) { if (order == null) throw new ArgumentNullException("order"); order.Deleted = true; UpdateOrder(order); }
public static void AddRewardPointsHistoryEntry(this Customer customer, int points, string message = "", Order usedWithOrder = null, decimal usedAmount = 0M) { int newPointsBalance = customer.GetRewardPointsBalance() + points; var rewardPointsHistory = new RewardPointsHistory() { Customer = customer, UsedWithOrder = usedWithOrder, Points = points, PointsBalance = newPointsBalance, UsedAmount = usedAmount, Message = message, CreatedOnUtc = DateTime.UtcNow }; customer.RewardPointsHistory.Add(rewardPointsHistory); }
/// <summary> /// Sends an order placed notification to a vendor /// </summary> /// <param name="order">Order instance</param> /// <param name="vendor">Vendor instance</param> /// <param name="languageId">Message language identifier</param> /// <returns>Queued email identifier</returns> public virtual int SendOrderPlacedVendorNotification(Order order, Vendor vendor, int languageId) { if (order == null) throw new ArgumentNullException("order"); if (vendor == null) throw new ArgumentNullException("vendor"); var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; languageId = EnsureLanguageIsActive(languageId, store.Id); var messageTemplate = GetLocalizedActiveMessageTemplate("OrderPlaced.VendorNotification", languageId, store.Id); if (messageTemplate == null) return 0; //tokens var tokens = new List<Token>(); _messageTokenProvider.AddStoreTokens(tokens, store); _messageTokenProvider.AddOrderTokens(tokens, order, languageId); _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); var toEmail = vendor.Email; var toName = vendor.Name; return SendNotification(messageTemplate, emailAccount, languageId, tokens, toEmail, toName); }
protected OrderDetailsModel PrepareOrderDetailsModel(Order order) { if (order == null) throw new ArgumentNullException("order"); var model = new OrderDetailsModel(); model.Id = order.Id; model.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc); model.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); model.IsReOrderAllowed = _orderSettings.IsReOrderAllowed; model.IsReturnRequestAllowed = _orderProcessingService.IsReturnRequestAllowed(order); model.DisplayPdfInvoice = _pdfSettings.Enabled; //shipping info model.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext); if (order.ShippingStatus != ShippingStatus.ShippingNotRequired) { model.IsShippable = true; model.ShippingAddress.PrepareModel(order.ShippingAddress, false, _addressSettings); model.ShippingMethod = order.ShippingMethod; //shipments (only already shipped) var shipments = order.Shipments.Where(x => x.ShippedDateUtc.HasValue).OrderBy(x => x.CreatedOnUtc).ToList(); foreach (var shipment in shipments) { var shipmentModel = new OrderDetailsModel.ShipmentBriefModel() { Id = shipment.Id, TrackingNumber = shipment.TrackingNumber, }; if (shipment.ShippedDateUtc.HasValue) shipmentModel.ShippedDate = _dateTimeHelper.ConvertToUserTime(shipment.ShippedDateUtc.Value, DateTimeKind.Utc); if (shipment.DeliveryDateUtc.HasValue) shipmentModel.DeliveryDate = _dateTimeHelper.ConvertToUserTime(shipment.DeliveryDateUtc.Value, DateTimeKind.Utc); model.Shipments.Add(shipmentModel); } } //billing info model.BillingAddress.PrepareModel(order.BillingAddress, false, _addressSettings); //VAT number model.VatNumber = order.VatNumber; //payment method var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); model.PaymentMethod = paymentMethod != null ? paymentMethod.GetLocalizedFriendlyName(_localizationService, _workContext.WorkingLanguage.Id) : order.PaymentMethodSystemName; model.CanRePostProcessPayment = _paymentService.CanRePostProcessPayment(order); //purchase order number (we have to find a better to inject this information because it's related to a certain plugin) if (paymentMethod != null && paymentMethod.PluginDescriptor.SystemName.Equals("Payments.PurchaseOrder", StringComparison.InvariantCultureIgnoreCase)) { model.DisplayPurchaseOrderNumber = true; model.PurchaseOrderNumber = order.PurchaseOrderNumber; } //totals switch (order.CustomerTaxDisplayType) { case TaxDisplayType.ExcludingTax: { //order subtotal var orderSubtotalExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate); model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false); //discount (applied to order subtotal) var orderSubTotalDiscountExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate); if (orderSubTotalDiscountExclTaxInCustomerCurrency > decimal.Zero) model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false); //order shipping var orderShippingExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingExclTax, order.CurrencyRate); model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false); //payment method additional fee var paymentMethodAdditionalFeeExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax, order.CurrencyRate); if (paymentMethodAdditionalFeeExclTaxInCustomerCurrency > decimal.Zero) model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false); } break; case TaxDisplayType.IncludingTax: { //order subtotal var orderSubtotalInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate); model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true); //discount (applied to order subtotal) var orderSubTotalDiscountInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate); if (orderSubTotalDiscountInclTaxInCustomerCurrency > decimal.Zero) model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true); //order shipping var orderShippingInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingInclTax, order.CurrencyRate); model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true); //payment method additional fee var paymentMethodAdditionalFeeInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax, order.CurrencyRate); if (paymentMethodAdditionalFeeInclTaxInCustomerCurrency > decimal.Zero) model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true); } break; } //tax bool displayTax = true; bool displayTaxRates = true; if (_taxSettings.HideTaxInOrderSummary && order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) { displayTax = false; displayTaxRates = false; } else { if (order.OrderTax == 0 && _taxSettings.HideZeroTax) { displayTax = false; displayTaxRates = false; } else { displayTaxRates = _taxSettings.DisplayTaxRates && order.TaxRatesDictionary.Count > 0; displayTax = !displayTaxRates; var orderTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTax, order.CurrencyRate); //TODO pass languageId to _priceFormatter.FormatPrice model.Tax = _priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); foreach (var tr in order.TaxRatesDictionary) { model.TaxRates.Add(new OrderDetailsModel.TaxRate() { Rate = _priceFormatter.FormatTaxRate(tr.Key), //TODO pass languageId to _priceFormatter.FormatPrice Value = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value, order.CurrencyRate), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage), }); } } } model.DisplayTaxRates = displayTaxRates; model.DisplayTax = displayTax; //discount (applied to order total) var orderDiscountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderDiscount, order.CurrencyRate); if (orderDiscountInCustomerCurrency > decimal.Zero) model.OrderTotalDiscount = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); //gift cards foreach (var gcuh in order.GiftCardUsageHistory) { model.GiftCards.Add(new OrderDetailsModel.GiftCard() { CouponCode = gcuh.GiftCard.GiftCardCouponCode, Amount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage), }); } //reward points if (order.RedeemedRewardPointsEntry != null) { model.RedeemedRewardPoints = -order.RedeemedRewardPointsEntry.Points; model.RedeemedRewardPointsAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmount, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); } //total var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); model.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); //checkout attributes model.CheckoutAttributeInfo = order.CheckoutAttributeDescription; //order notes foreach (var orderNote in order.OrderNotes .Where(on => on.DisplayToCustomer) .OrderByDescending(on => on.CreatedOnUtc) .ToList()) { model.OrderNotes.Add(new OrderDetailsModel.OrderNote() { Note = orderNote.FormatOrderNoteText(), CreatedOn = _dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc) }); } //purchased products model.ShowSku = _catalogSettings.ShowProductSku; var orderProductVariants = _orderService.GetAllOrderProductVariants(order.Id, null, null, null, null, null, null); foreach (var opv in orderProductVariants) { var opvModel = new OrderDetailsModel.OrderProductVariantModel() { Id = opv.Id, Sku = opv.ProductVariant.FormatSku(opv.AttributesXml, _productAttributeParser), ProductId = opv.ProductVariant.ProductId, ProductSeName = opv.ProductVariant.Product.GetSeName(), Quantity = opv.Quantity, AttributeInfo = opv.AttributeDescription, }; //product name if (!String.IsNullOrEmpty(opv.ProductVariant.GetLocalized(x => x.Name))) opvModel.ProductName = string.Format("{0} ({1})", opv.ProductVariant.Product.GetLocalized(x => x.Name), opv.ProductVariant.GetLocalized(x => x.Name)); else opvModel.ProductName = opv.ProductVariant.Product.GetLocalized(x => x.Name); model.Items.Add(opvModel); //unit price, subtotal switch (order.CustomerTaxDisplayType) { case TaxDisplayType.ExcludingTax: { var opvUnitPriceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(opv.UnitPriceExclTax, order.CurrencyRate); opvModel.UnitPrice = _priceFormatter.FormatPrice(opvUnitPriceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false); var opvPriceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(opv.PriceExclTax, order.CurrencyRate); opvModel.SubTotal = _priceFormatter.FormatPrice(opvPriceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false); } break; case TaxDisplayType.IncludingTax: { var opvUnitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(opv.UnitPriceInclTax, order.CurrencyRate); opvModel.UnitPrice = _priceFormatter.FormatPrice(opvUnitPriceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true); var opvPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(opv.PriceInclTax, order.CurrencyRate); opvModel.SubTotal = _priceFormatter.FormatPrice(opvPriceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true); } break; } } return model; }
/// <summary> /// Partially refunds an order (from admin panel) /// </summary> /// <param name="order">Order</param> /// <param name="amountToRefund">Amount to refund</param> /// <returns>A list of errors; empty list if no errors</returns> public virtual IList<string> PartiallyRefund(Order order, decimal amountToRefund) { if (order == null) throw new ArgumentNullException("order"); if (!CanPartiallyRefund(order, amountToRefund)) throw new NasException("Cannot do partial refund for order."); var request = new RefundPaymentRequest(); RefundPaymentResult result = null; try { request.Order = order; request.AmountToRefund = amountToRefund; request.IsPartialRefund = true; result = _paymentService.Refund(request); if (result.Success) { //total amount refunded decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; //update order info order.RefundedAmount = totalAmountRefunded; order.PaymentStatus = result.NewPaymentStatus; _orderService.UpdateOrder(order); //add a note order.OrderNotes.Add(new OrderNote() { Note = string.Format("Order has been partially refunded. Amount = {0}", _priceFormatter.FormatPrice(amountToRefund, true, false)), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); //check order status CheckOrderStatus(order); } } catch (Exception exc) { if (result == null) result = new RefundPaymentResult(); result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); } //process errors string error = ""; for (int i = 0; i < result.Errors.Count; i++) { error += string.Format("Error {0}: {1}", i, result.Errors[i]); if (i != result.Errors.Count - 1) error += ". "; } if (!String.IsNullOrEmpty(error)) { //add a note order.OrderNotes.Add(new OrderNote() { Note = string.Format("Unable to partially refund order. {0}", error), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); //log it string logError = string.Format("Error refunding order #{0}. Error: {1}", order.Id, error); _logger.InsertLog(LogLevel.Error, logError, logError); } return result.Errors; }
/// <summary> /// Marks order as authorized /// </summary> /// <param name="order">Order</param> public virtual void MarkAsAuthorized(Order order) { if (order == null) throw new ArgumentNullException("order"); order.PaymentStatusId = (int)PaymentStatus.Authorized; _orderService.UpdateOrder(order); //add a note order.OrderNotes.Add(new OrderNote() { Note = "Order has been marked as authorized", DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); //check order status CheckOrderStatus(order); }
/// <summary> /// Deletes an order /// </summary> /// <param name="order">The order</param> public virtual void DeleteOrder(Order order) { if (order == null) throw new ArgumentNullException("order"); //check whether the order wasn't cancelled before // if it already was cancelled, then there's no need to make the following adjustments //(such as reward poitns, inventory, recurring payments) //they already was done when cancelling the order if (order.OrderStatus != OrderStatus.Cancelled) { //reward points ReduceRewardPoints(order); //cancel recurring payments var recurringPayments = _orderService.SearchRecurringPayments(0, 0, order.Id, null, 0, int.MaxValue); foreach (var rp in recurringPayments) { //use errors? var errors = CancelRecurringPayment(rp); } //Adjust inventory foreach (var opv in order.OrderProductVariants) _productService.AdjustInventory(opv.ProductVariant, false, opv.Quantity, opv.AttributesXml); } //add a note order.OrderNotes.Add(new OrderNote() { Note = "Order has been deleted", DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); //now delete an order _orderService.DeleteOrder(order); }
/// <summary> /// Capture an order (from admin panel) /// </summary> /// <param name="order">Order</param> /// <returns>A list of errors; empty list if no errors</returns> public virtual IList<string> Capture(Order order) { if (order == null) throw new ArgumentNullException("order"); if (!CanCapture(order)) throw new NasException("Cannot do capture for order."); var request = new CapturePaymentRequest(); CapturePaymentResult result = null; try { //old info from placing order request.Order = order; result = _paymentService.Capture(request); if (result.Success) { var paidDate = order.PaidDateUtc; if (result.NewPaymentStatus == PaymentStatus.Paid) paidDate = DateTime.UtcNow; order.CaptureTransactionId = result.CaptureTransactionId; order.CaptureTransactionResult = result.CaptureTransactionResult; order.PaymentStatus = result.NewPaymentStatus; order.PaidDateUtc = paidDate; _orderService.UpdateOrder(order); //add a note order.OrderNotes.Add(new OrderNote() { Note = "Order has been captured", DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); CheckOrderStatus(order); //raise event if (order.PaymentStatus == PaymentStatus.Paid) { _eventPublisher.PublishOrderPaid(order); } } } catch (Exception exc) { if (result == null) result = new CapturePaymentResult(); result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); } //process errors string error = ""; for (int i = 0; i < result.Errors.Count; i++) { error += string.Format("Error {0}: {1}", i, result.Errors[i]); if (i != result.Errors.Count - 1) error += ". "; } if (!String.IsNullOrEmpty(error)) { //add a note order.OrderNotes.Add(new OrderNote() { Note = string.Format("Unable to capture order. {0}", error), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); //log it string logError = string.Format("Error capturing order #{0}. Error: {1}", order.Id, error); _logger.InsertLog(LogLevel.Error, logError, logError); } return result.Errors; }
/// <summary> /// Award reward points /// </summary> /// <param name="order">Order</param> protected void ReduceRewardPoints(Order order) { if (!_rewardPointsSettings.Enabled) return; if (_rewardPointsSettings.PointsForPurchases_Amount <= decimal.Zero) return; //Ensure that reward points are applied only to registered users if (order.Customer == null || order.Customer.IsGuest()) return; int points = (int)Math.Truncate(order.OrderTotal / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); if (points == 0) return; //ensure that reward points were already earned for this order before if (!order.RewardPointsWereAdded) return; //reduce reward points order.Customer.AddRewardPointsHistoryEntry(-points, string.Format(_localizationService.GetResource("RewardPoints.Message.ReducedForOrder"), order.Id)); _orderService.UpdateOrder(order); }
/// <summary> /// Voids order (offline) /// </summary> /// <param name="order">Order</param> public virtual void VoidOffline(Order order) { if (order == null) throw new ArgumentNullException("order"); if (!CanVoidOffline(order)) throw new NasException("You can't void this order"); order.PaymentStatusId = (int)PaymentStatus.Voided; _orderService.UpdateOrder(order); //add a note order.OrderNotes.Add(new OrderNote() { Note = "Order has been marked as voided", DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); //check orer status CheckOrderStatus(order); }
/// <summary> /// Voids order (from admin panel) /// </summary> /// <param name="order">Order</param> /// <returns>Voided order</returns> public virtual IList<string> Void(Order order) { if (order == null) throw new ArgumentNullException("order"); if (!CanVoid(order)) throw new NasException("Cannot do void for order."); var request = new VoidPaymentRequest(); VoidPaymentResult result = null; try { request.Order = order; result = _paymentService.Void(request); if (result.Success) { //update order info order.PaymentStatus = result.NewPaymentStatus; _orderService.UpdateOrder(order); //add a note order.OrderNotes.Add(new OrderNote() { Note = "Order has been voided", DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); //check order status CheckOrderStatus(order); } } catch (Exception exc) { if (result == null) result = new VoidPaymentResult(); result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); } //process errors string error = ""; for (int i = 0; i < result.Errors.Count; i++) { error += string.Format("Error {0}: {1}", i, result.Errors[i]); if (i != result.Errors.Count - 1) error += ". "; } if (!String.IsNullOrEmpty(error)) { //add a note order.OrderNotes.Add(new OrderNote() { Note = string.Format("Unable to voiding order. {0}", error), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); //log it string logError = string.Format("Error voiding order #{0}. Error: {1}", order.Id, error); _logger.InsertLog(LogLevel.Error, logError, logError); } return result.Errors; }
/// <summary> /// Place order items in current user shopping cart. /// </summary> /// <param name="order">The order</param> public virtual void ReOrder(Order order) { if (order == null) throw new ArgumentNullException("order"); foreach (var opv in order.OrderProductVariants) { _shoppingCartService.AddToCart(opv.Order.Customer, opv.ProductVariant, ShoppingCartType.ShoppingCart, opv.Order.StoreId, opv.AttributesXml, opv.UnitPriceExclTax, opv.Quantity, false); } }
/// <summary> /// Gets a value indicating whether cancel is allowed /// </summary> /// <param name="order">Order</param> /// <returns>A value indicating whether cancel is allowed</returns> public virtual bool CanCancelOrder(Order order) { if (order == null) throw new ArgumentNullException("order"); if (order.OrderStatus == OrderStatus.Cancelled) return false; return true; }
/// <summary> /// Places an order /// </summary> /// <param name="processPaymentRequest">Process payment request</param> /// <returns>Place order result</returns> public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentRequest) { //think about moving functionality of processing recurring orders (after the initial order was placed) to ProcessNextRecurringPayment() method if (processPaymentRequest == null) throw new ArgumentNullException("processPaymentRequest"); if (processPaymentRequest.OrderGuid == Guid.Empty) processPaymentRequest.OrderGuid = Guid.NewGuid(); var result = new PlaceOrderResult(); try { #region Order details (customer, addresses, totals) //Recurring orders. Load initial order Order initialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); if (processPaymentRequest.IsRecurringPayment) { if (initialOrder == null) throw new ArgumentException("Initial order is not set for recurring payment"); processPaymentRequest.PaymentMethodSystemName = initialOrder.PaymentMethodSystemName; } //customer var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); if (customer == null) throw new ArgumentException("Customer is not set"); //affilites int affiliateId = 0; var affiliate = _affiliateService.GetAffiliateById(customer.AffiliateId); if (affiliate != null && affiliate.Active && !affiliate.Deleted) affiliateId = affiliate.Id; //customer currency string customerCurrencyCode = ""; decimal customerCurrencyRate = decimal.Zero; if (!processPaymentRequest.IsRecurringPayment) { var currencyTmp = _currencyService.GetCurrencyById(customer.GetAttribute<int>(SystemCustomerAttributeNames.CurrencyId, processPaymentRequest.StoreId)); var customerCurrency = (currencyTmp != null && currencyTmp.Published) ? currencyTmp : _workContext.WorkingCurrency; customerCurrencyCode = customerCurrency.CurrencyCode; var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); customerCurrencyRate = customerCurrency.Rate / primaryStoreCurrency.Rate; } else { customerCurrencyCode = initialOrder.CustomerCurrencyCode; customerCurrencyRate = initialOrder.CurrencyRate; } //customer language Language customerLanguage = null; if (!processPaymentRequest.IsRecurringPayment) { customerLanguage = _languageService.GetLanguageById(customer.GetAttribute<int>( SystemCustomerAttributeNames.LanguageId, processPaymentRequest.StoreId)); } else { customerLanguage = _languageService.GetLanguageById(initialOrder.CustomerLanguageId); } if (customerLanguage == null || !customerLanguage.Published) customerLanguage = _workContext.WorkingLanguage; //check whether customer is guest if (customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) throw new NasException("Anonymous checkout is not allowed"); //billing address Address billingAddress = null; if (!processPaymentRequest.IsRecurringPayment) { if (customer.BillingAddress == null) throw new NasException("Billing address is not provided"); if (!CommonHelper.IsValidEmail(customer.BillingAddress.Email)) throw new NasException("Email is not valid"); //clone billing address billingAddress = (Address)customer.BillingAddress.Clone(); if (billingAddress.Country != null && !billingAddress.Country.AllowsBilling) throw new NasException(string.Format("Country '{0}' is not allowed for billing", billingAddress.Country.Name)); } else { if (initialOrder.BillingAddress == null) throw new NasException("Billing address is not available"); //clone billing address billingAddress = (Address)initialOrder.BillingAddress.Clone(); if (billingAddress.Country != null && !billingAddress.Country.AllowsBilling) throw new NasException(string.Format("Country '{0}' is not allowed for billing", billingAddress.Country.Name)); } //load and validate customer shopping cart IList<ShoppingCartItem> cart = null; if (!processPaymentRequest.IsRecurringPayment) { //load shopping cart cart = customer.ShoppingCartItems .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) .Where(sci => sci.StoreId == processPaymentRequest.StoreId) .ToList(); if (cart.Count == 0) throw new NasException("Cart is empty"); //validate the entire shopping cart var warnings = _shoppingCartService.GetShoppingCartWarnings(cart, customer.GetAttribute<string>(SystemCustomerAttributeNames.CheckoutAttributes), true); if (warnings.Count > 0) { var warningsSb = new StringBuilder(); foreach (string warning in warnings) { warningsSb.Append(warning); warningsSb.Append(";"); } throw new NasException(warningsSb.ToString()); } //validate individual cart items foreach (var sci in cart) { var sciWarnings = _shoppingCartService.GetShoppingCartItemWarnings(customer, sci.ShoppingCartType, sci.ProductVariant, processPaymentRequest.StoreId, sci.AttributesXml, sci.CustomerEnteredPrice, sci.Quantity, false); if (sciWarnings.Count > 0) { var warningsSb = new StringBuilder(); foreach (string warning in sciWarnings) { warningsSb.Append(warning); warningsSb.Append(";"); } throw new NasException(warningsSb.ToString()); } } } //min totals validation if (!processPaymentRequest.IsRecurringPayment) { bool minOrderSubtotalAmountOk = ValidateMinOrderSubtotalAmount(cart); if (!minOrderSubtotalAmountOk) { decimal minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); throw new NasException(string.Format(_localizationService.GetResource("Checkout.MinOrderSubtotalAmount"), _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); } bool minOrderTotalAmountOk = ValidateMinOrderTotalAmount(cart); if (!minOrderTotalAmountOk) { decimal minOrderTotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderTotalAmount, _workContext.WorkingCurrency); throw new NasException(string.Format(_localizationService.GetResource("Checkout.MinOrderTotalAmount"), _priceFormatter.FormatPrice(minOrderTotalAmount, true, false))); } } //tax display type var customerTaxDisplayType = TaxDisplayType.IncludingTax; if (!processPaymentRequest.IsRecurringPayment) { if (_taxSettings.AllowCustomersToSelectTaxDisplayType) customerTaxDisplayType = (TaxDisplayType)customer.GetAttribute<int>(SystemCustomerAttributeNames.TaxDisplayTypeId, processPaymentRequest.StoreId); else customerTaxDisplayType = _taxSettings.TaxDisplayType; } else { customerTaxDisplayType = initialOrder.CustomerTaxDisplayType; } //checkout attributes string checkoutAttributeDescription, checkoutAttributesXml; if (!processPaymentRequest.IsRecurringPayment) { checkoutAttributesXml = customer.GetAttribute<string>(SystemCustomerAttributeNames.CheckoutAttributes); checkoutAttributeDescription = _checkoutAttributeFormatter.FormatAttributes(checkoutAttributesXml, customer); } else { checkoutAttributesXml = initialOrder.CheckoutAttributesXml; checkoutAttributeDescription = initialOrder.CheckoutAttributeDescription; } //applied discount (used to store discount usage history) var appliedDiscounts = new List<Discount>(); //sub total decimal orderSubTotalInclTax, orderSubTotalExclTax; decimal orderSubTotalDiscountInclTax = 0, orderSubTotalDiscountExclTax = 0; if (!processPaymentRequest.IsRecurringPayment) { //sub total (incl tax) decimal orderSubTotalDiscountAmount1 = decimal.Zero; Discount orderSubTotalAppliedDiscount1 = null; decimal subTotalWithoutDiscountBase1 = decimal.Zero; decimal subTotalWithDiscountBase1 = decimal.Zero; _orderTotalCalculationService.GetShoppingCartSubTotal(cart, true, out orderSubTotalDiscountAmount1, out orderSubTotalAppliedDiscount1, out subTotalWithoutDiscountBase1, out subTotalWithDiscountBase1); orderSubTotalInclTax = subTotalWithoutDiscountBase1; orderSubTotalDiscountInclTax = orderSubTotalDiscountAmount1; //discount history if (orderSubTotalAppliedDiscount1 != null && !appliedDiscounts.ContainsDiscount(orderSubTotalAppliedDiscount1)) appliedDiscounts.Add(orderSubTotalAppliedDiscount1); //sub total (excl tax) decimal orderSubTotalDiscountAmount2 = decimal.Zero; Discount orderSubTotalAppliedDiscount2 = null; decimal subTotalWithoutDiscountBase2 = decimal.Zero; decimal subTotalWithDiscountBase2 = decimal.Zero; _orderTotalCalculationService.GetShoppingCartSubTotal(cart, false, out orderSubTotalDiscountAmount2, out orderSubTotalAppliedDiscount2, out subTotalWithoutDiscountBase2, out subTotalWithDiscountBase2); orderSubTotalExclTax = subTotalWithoutDiscountBase2; orderSubTotalDiscountExclTax = orderSubTotalDiscountAmount2; } else { orderSubTotalInclTax = initialOrder.OrderSubtotalInclTax; orderSubTotalExclTax = initialOrder.OrderSubtotalExclTax; } //shipping info bool shoppingCartRequiresShipping = false; if (!processPaymentRequest.IsRecurringPayment) { shoppingCartRequiresShipping = cart.RequiresShipping(); } else { shoppingCartRequiresShipping = initialOrder.ShippingStatus != ShippingStatus.ShippingNotRequired; } Address shippingAddress = null; string shippingMethodName = "", shippingRateComputationMethodSystemName = ""; if (shoppingCartRequiresShipping) { if (!processPaymentRequest.IsRecurringPayment) { if (customer.ShippingAddress == null) throw new NasException("Shipping address is not provided"); if (!CommonHelper.IsValidEmail(customer.ShippingAddress.Email)) throw new NasException("Email is not valid"); //clone shipping address shippingAddress = (Address)customer.ShippingAddress.Clone(); if (shippingAddress.Country != null && !shippingAddress.Country.AllowsShipping) throw new NasException(string.Format("Country '{0}' is not allowed for shipping", shippingAddress.Country.Name)); var shippingOption = customer.GetAttribute<ShippingOption>(SystemCustomerAttributeNames.SelectedShippingOption, processPaymentRequest.StoreId); if (shippingOption != null) { shippingMethodName = shippingOption.Name; shippingRateComputationMethodSystemName = shippingOption.ShippingRateComputationMethodSystemName; } } else { if (initialOrder.ShippingAddress == null) throw new NasException("Shipping address is not available"); //clone billing address shippingAddress = (Address)initialOrder.ShippingAddress.Clone(); if (shippingAddress.Country != null && !shippingAddress.Country.AllowsShipping) throw new NasException(string.Format("Country '{0}' is not allowed for shipping", shippingAddress.Country.Name)); shippingMethodName = initialOrder.ShippingMethod; shippingRateComputationMethodSystemName = initialOrder.ShippingRateComputationMethodSystemName; } } //shipping total decimal? orderShippingTotalInclTax, orderShippingTotalExclTax = null; if (!processPaymentRequest.IsRecurringPayment) { decimal taxRate = decimal.Zero; Discount shippingTotalDiscount = null; orderShippingTotalInclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, true, out taxRate, out shippingTotalDiscount); orderShippingTotalExclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, false); if (!orderShippingTotalInclTax.HasValue || !orderShippingTotalExclTax.HasValue) throw new NasException("Shipping total couldn't be calculated"); if (shippingTotalDiscount != null && !appliedDiscounts.ContainsDiscount(shippingTotalDiscount)) appliedDiscounts.Add(shippingTotalDiscount); } else { orderShippingTotalInclTax = initialOrder.OrderShippingInclTax; orderShippingTotalExclTax = initialOrder.OrderShippingExclTax; } //payment total decimal paymentAdditionalFeeInclTax, paymentAdditionalFeeExclTax; if (!processPaymentRequest.IsRecurringPayment) { decimal paymentAdditionalFee = _paymentService.GetAdditionalHandlingFee(cart, processPaymentRequest.PaymentMethodSystemName); paymentAdditionalFeeInclTax = _taxService.GetPaymentMethodAdditionalFee(paymentAdditionalFee, true, customer); paymentAdditionalFeeExclTax = _taxService.GetPaymentMethodAdditionalFee(paymentAdditionalFee, false, customer); } else { paymentAdditionalFeeInclTax = initialOrder.PaymentMethodAdditionalFeeInclTax; paymentAdditionalFeeExclTax = initialOrder.PaymentMethodAdditionalFeeExclTax; } //tax total decimal orderTaxTotal = decimal.Zero; string vatNumber = "", taxRates = ""; if (!processPaymentRequest.IsRecurringPayment) { //tax amount SortedDictionary<decimal, decimal> taxRatesDictionary = null; orderTaxTotal = _orderTotalCalculationService.GetTaxTotal(cart, out taxRatesDictionary); //VAT number var customerVatStatus = (VatNumberStatus)customer.GetAttribute<int>(SystemCustomerAttributeNames.VatNumberStatusId); if (_taxSettings.EuVatEnabled && customerVatStatus == VatNumberStatus.Valid) vatNumber = customer.GetAttribute<string>(SystemCustomerAttributeNames.VatNumber); //tax rates foreach (var kvp in taxRatesDictionary) { var taxRate = kvp.Key; var taxValue = kvp.Value; taxRates += string.Format("{0}:{1}; ", taxRate.ToString(CultureInfo.InvariantCulture), taxValue.ToString(CultureInfo.InvariantCulture)); } } else { orderTaxTotal = initialOrder.OrderTax; //VAT number vatNumber = initialOrder.VatNumber; } //order total (and applied discounts, gift cards, reward points) decimal? orderTotal = null; decimal orderDiscountAmount = decimal.Zero; List<AppliedGiftCard> appliedGiftCards = null; int redeemedRewardPoints = 0; decimal redeemedRewardPointsAmount = decimal.Zero; if (!processPaymentRequest.IsRecurringPayment) { Discount orderAppliedDiscount = null; orderTotal = _orderTotalCalculationService.GetShoppingCartTotal(cart, out orderDiscountAmount, out orderAppliedDiscount, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); if (!orderTotal.HasValue) throw new NasException("Order total couldn't be calculated"); //discount history if (orderAppliedDiscount != null && !appliedDiscounts.ContainsDiscount(orderAppliedDiscount)) appliedDiscounts.Add(orderAppliedDiscount); } else { orderDiscountAmount = initialOrder.OrderDiscount; orderTotal = initialOrder.OrderTotal; } processPaymentRequest.OrderTotal = orderTotal.Value; #endregion #region Payment workflow //skip payment workflow if order total equals zero bool skipPaymentWorkflow = orderTotal.Value == decimal.Zero; //payment workflow IPaymentMethod paymentMethod = null; if (!skipPaymentWorkflow) { paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); if (paymentMethod == null) throw new NasException("Payment method couldn't be loaded"); //ensure that payment method is active if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) throw new NasException("Payment method is not active"); } else processPaymentRequest.PaymentMethodSystemName = ""; //recurring or standard shopping cart? bool isRecurringShoppingCart = false; if (!processPaymentRequest.IsRecurringPayment) { isRecurringShoppingCart = cart.IsRecurring(); if (isRecurringShoppingCart) { int recurringCycleLength = 0; RecurringProductCyclePeriod recurringCyclePeriod; int recurringTotalCycles = 0; string recurringCyclesError = cart.GetRecurringCycleInfo(out recurringCycleLength, out recurringCyclePeriod, out recurringTotalCycles); if (!string.IsNullOrEmpty(recurringCyclesError)) throw new NasException(recurringCyclesError); processPaymentRequest.RecurringCycleLength = recurringCycleLength; processPaymentRequest.RecurringCyclePeriod = recurringCyclePeriod; processPaymentRequest.RecurringTotalCycles = recurringTotalCycles; } } else isRecurringShoppingCart = true; //process payment ProcessPaymentResult processPaymentResult = null; if (!skipPaymentWorkflow) { if (!processPaymentRequest.IsRecurringPayment) { if (isRecurringShoppingCart) { //recurring cart var recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); switch (recurringPaymentType) { case RecurringPaymentType.NotSupported: throw new NasException("Recurring payments are not supported by selected payment method"); case RecurringPaymentType.Manual: case RecurringPaymentType.Automatic: processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); break; default: throw new NasException("Not supported recurring payment type"); } } else { //standard cart processPaymentResult = _paymentService.ProcessPayment(processPaymentRequest); } } else { if (isRecurringShoppingCart) { //Old credit card info processPaymentRequest.CreditCardType = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardType) : ""; processPaymentRequest.CreditCardName = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardName) : ""; processPaymentRequest.CreditCardNumber = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardNumber) : ""; //MaskedCreditCardNumber processPaymentRequest.CreditCardCvv2 = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardCvv2) : ""; try { processPaymentRequest.CreditCardExpireMonth = initialOrder.AllowStoringCreditCardNumber ? Convert.ToInt32(_encryptionService.DecryptText(initialOrder.CardExpirationMonth)) : 0; processPaymentRequest.CreditCardExpireYear = initialOrder.AllowStoringCreditCardNumber ? Convert.ToInt32(_encryptionService.DecryptText(initialOrder.CardExpirationYear)) : 0; } catch {} var recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); switch (recurringPaymentType) { case RecurringPaymentType.NotSupported: throw new NasException("Recurring payments are not supported by selected payment method"); case RecurringPaymentType.Manual: processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); break; case RecurringPaymentType.Automatic: //payment is processed on payment gateway site processPaymentResult = new ProcessPaymentResult(); break; default: throw new NasException("Not supported recurring payment type"); } } else { throw new NasException("No recurring products"); } } } else { //payment is not required if (processPaymentResult == null) processPaymentResult = new ProcessPaymentResult(); processPaymentResult.NewPaymentStatus = PaymentStatus.Paid; } if (processPaymentResult == null) throw new NasException("processPaymentResult is not available"); #endregion if (processPaymentResult.Success) { //save order in data storage //uncomment this line to support transactions //using (var scope = new System.Transactions.TransactionScope()) { #region Save order details var shippingStatus = ShippingStatus.NotYetShipped; if (!shoppingCartRequiresShipping) shippingStatus = ShippingStatus.ShippingNotRequired; var order = new Order() { StoreId = processPaymentRequest.StoreId, OrderGuid = processPaymentRequest.OrderGuid, CustomerId = customer.Id, CustomerLanguageId = customerLanguage.Id, CustomerTaxDisplayType = customerTaxDisplayType, CustomerIp = _webHelper.GetCurrentIpAddress(), OrderSubtotalInclTax = orderSubTotalInclTax, OrderSubtotalExclTax = orderSubTotalExclTax, OrderSubTotalDiscountInclTax = orderSubTotalDiscountInclTax, OrderSubTotalDiscountExclTax = orderSubTotalDiscountExclTax, OrderShippingInclTax = orderShippingTotalInclTax.Value, OrderShippingExclTax = orderShippingTotalExclTax.Value, PaymentMethodAdditionalFeeInclTax = paymentAdditionalFeeInclTax, PaymentMethodAdditionalFeeExclTax = paymentAdditionalFeeExclTax, TaxRates = taxRates, OrderTax = orderTaxTotal, OrderTotal = orderTotal.Value, RefundedAmount = decimal.Zero, OrderDiscount = orderDiscountAmount, CheckoutAttributeDescription = checkoutAttributeDescription, CheckoutAttributesXml = checkoutAttributesXml, CustomerCurrencyCode = customerCurrencyCode, CurrencyRate = customerCurrencyRate, AffiliateId = affiliateId, OrderStatus = OrderStatus.Pending, AllowStoringCreditCardNumber = processPaymentResult.AllowStoringCreditCardNumber, CardType = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardType) : string.Empty, CardName = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardName) : string.Empty, CardNumber = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardNumber) : string.Empty, MaskedCreditCardNumber = _encryptionService.EncryptText(_paymentService.GetMaskedCreditCardNumber(processPaymentRequest.CreditCardNumber)), CardCvv2 = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardCvv2) : string.Empty, CardExpirationMonth = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardExpireMonth.ToString()) : string.Empty, CardExpirationYear = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardExpireYear.ToString()) : string.Empty, PaymentMethodSystemName = processPaymentRequest.PaymentMethodSystemName, AuthorizationTransactionId = processPaymentResult.AuthorizationTransactionId, AuthorizationTransactionCode = processPaymentResult.AuthorizationTransactionCode, AuthorizationTransactionResult = processPaymentResult.AuthorizationTransactionResult, CaptureTransactionId = processPaymentResult.CaptureTransactionId, CaptureTransactionResult = processPaymentResult.CaptureTransactionResult, SubscriptionTransactionId = processPaymentResult.SubscriptionTransactionId, PurchaseOrderNumber = processPaymentRequest.PurchaseOrderNumber, PaymentStatus = processPaymentResult.NewPaymentStatus, PaidDateUtc = null, BillingAddress = billingAddress, ShippingAddress = shippingAddress, ShippingStatus = shippingStatus, ShippingMethod = shippingMethodName, ShippingRateComputationMethodSystemName = shippingRateComputationMethodSystemName, VatNumber = vatNumber, CreatedOnUtc = DateTime.UtcNow }; _orderService.InsertOrder(order); result.PlacedOrder = order; if (!processPaymentRequest.IsRecurringPayment) { //move shopping cart items to order product variants foreach (var sc in cart) { //prices decimal taxRate = decimal.Zero; decimal scUnitPrice = _priceCalculationService.GetUnitPrice(sc, true); decimal scSubTotal = _priceCalculationService.GetSubTotal(sc, true); decimal scUnitPriceInclTax = _taxService.GetProductPrice(sc.ProductVariant, scUnitPrice, true, customer, out taxRate); decimal scUnitPriceExclTax = _taxService.GetProductPrice(sc.ProductVariant, scUnitPrice, false, customer, out taxRate); decimal scSubTotalInclTax = _taxService.GetProductPrice(sc.ProductVariant, scSubTotal, true, customer, out taxRate); decimal scSubTotalExclTax = _taxService.GetProductPrice(sc.ProductVariant, scSubTotal, false, customer, out taxRate); //discounts Discount scDiscount = null; decimal discountAmount = _priceCalculationService.GetDiscountAmount(sc, out scDiscount); decimal discountAmountInclTax = _taxService.GetProductPrice(sc.ProductVariant, discountAmount, true, customer, out taxRate); decimal discountAmountExclTax = _taxService.GetProductPrice(sc.ProductVariant, discountAmount, false, customer, out taxRate); if (scDiscount != null && !appliedDiscounts.ContainsDiscount(scDiscount)) appliedDiscounts.Add(scDiscount); //attributes string attributeDescription = _productAttributeFormatter.FormatAttributes(sc.ProductVariant, sc.AttributesXml, customer); var itemWeight = _shippingService.GetShoppingCartItemWeight(sc); //save order item var opv = new OrderProductVariant() { OrderProductVariantGuid = Guid.NewGuid(), Order = order, ProductVariantId = sc.ProductVariantId, UnitPriceInclTax = scUnitPriceInclTax, UnitPriceExclTax = scUnitPriceExclTax, PriceInclTax = scSubTotalInclTax, PriceExclTax = scSubTotalExclTax, AttributeDescription = attributeDescription, AttributesXml = sc.AttributesXml, Quantity = sc.Quantity, DiscountAmountInclTax = discountAmountInclTax, DiscountAmountExclTax = discountAmountExclTax, DownloadCount = 0, IsDownloadActivated = false, LicenseDownloadId = 0, ItemWeight = itemWeight, }; order.OrderProductVariants.Add(opv); _orderService.UpdateOrder(order); //gift cards if (sc.ProductVariant.IsGiftCard) { string giftCardRecipientName, giftCardRecipientEmail, giftCardSenderName, giftCardSenderEmail, giftCardMessage; _productAttributeParser.GetGiftCardAttribute(sc.AttributesXml, out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); for (int i = 0; i < sc.Quantity; i++) { var gc = new GiftCard() { GiftCardType = sc.ProductVariant.GiftCardType, PurchasedWithOrderProductVariant = opv, Amount = scUnitPriceExclTax, IsGiftCardActivated = false, GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), RecipientName = giftCardRecipientName, RecipientEmail = giftCardRecipientEmail, SenderName = giftCardSenderName, SenderEmail = giftCardSenderEmail, Message = giftCardMessage, IsRecipientNotified = false, CreatedOnUtc = DateTime.UtcNow }; _giftCardService.InsertGiftCard(gc); } } //inventory _productService.AdjustInventory(sc.ProductVariant, true, sc.Quantity, sc.AttributesXml); } //clear shopping cart cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci, false)); } else { //recurring payment var initialOrderProductVariants = initialOrder.OrderProductVariants; foreach (var opv in initialOrderProductVariants) { //save item var newOpv = new OrderProductVariant() { OrderProductVariantGuid = Guid.NewGuid(), Order = order, ProductVariantId = opv.ProductVariantId, UnitPriceInclTax = opv.UnitPriceInclTax, UnitPriceExclTax = opv.UnitPriceExclTax, PriceInclTax = opv.PriceInclTax, PriceExclTax = opv.PriceExclTax, AttributeDescription = opv.AttributeDescription, AttributesXml = opv.AttributesXml, Quantity = opv.Quantity, DiscountAmountInclTax = opv.DiscountAmountInclTax, DiscountAmountExclTax = opv.DiscountAmountExclTax, DownloadCount = 0, IsDownloadActivated = false, LicenseDownloadId = 0, ItemWeight = opv.ItemWeight, }; order.OrderProductVariants.Add(newOpv); _orderService.UpdateOrder(order); //gift cards if (opv.ProductVariant.IsGiftCard) { string giftCardRecipientName, giftCardRecipientEmail, giftCardSenderName, giftCardSenderEmail, giftCardMessage; _productAttributeParser.GetGiftCardAttribute(opv.AttributesXml, out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); for (int i = 0; i < opv.Quantity; i++) { var gc = new GiftCard() { GiftCardType = opv.ProductVariant.GiftCardType, PurchasedWithOrderProductVariant = newOpv, Amount = opv.UnitPriceExclTax, IsGiftCardActivated = false, GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), RecipientName = giftCardRecipientName, RecipientEmail = giftCardRecipientEmail, SenderName = giftCardSenderName, SenderEmail = giftCardSenderEmail, Message = giftCardMessage, IsRecipientNotified = false, CreatedOnUtc = DateTime.UtcNow }; _giftCardService.InsertGiftCard(gc); } } //inventory _productService.AdjustInventory(opv.ProductVariant, true, opv.Quantity, opv.AttributesXml); } } //discount usage history if (!processPaymentRequest.IsRecurringPayment) foreach (var discount in appliedDiscounts) { var duh = new DiscountUsageHistory() { Discount = discount, Order = order, CreatedOnUtc = DateTime.UtcNow }; _discountService.InsertDiscountUsageHistory(duh); } //gift card usage history if (!processPaymentRequest.IsRecurringPayment) if (appliedGiftCards != null) foreach (var agc in appliedGiftCards) { decimal amountUsed = agc.AmountCanBeUsed; var gcuh = new GiftCardUsageHistory() { GiftCard = agc.GiftCard, UsedWithOrder = order, UsedValue = amountUsed, CreatedOnUtc = DateTime.UtcNow }; agc.GiftCard.GiftCardUsageHistory.Add(gcuh); _giftCardService.UpdateGiftCard(agc.GiftCard); } //reward points history if (redeemedRewardPointsAmount > decimal.Zero) { customer.AddRewardPointsHistoryEntry(-redeemedRewardPoints, string.Format(_localizationService.GetResource("RewardPoints.Message.RedeemedForOrder", order.CustomerLanguageId), order.Id), order, redeemedRewardPointsAmount); _customerService.UpdateCustomer(customer); } //recurring orders if (!processPaymentRequest.IsRecurringPayment && isRecurringShoppingCart) { //create recurring payment (the first payment) var rp = new RecurringPayment() { CycleLength = processPaymentRequest.RecurringCycleLength, CyclePeriod = processPaymentRequest.RecurringCyclePeriod, TotalCycles = processPaymentRequest.RecurringTotalCycles, StartDateUtc = DateTime.UtcNow, IsActive = true, CreatedOnUtc = DateTime.UtcNow, InitialOrder = order, }; _orderService.InsertRecurringPayment(rp); var recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); switch (recurringPaymentType) { case RecurringPaymentType.NotSupported: { //not supported } break; case RecurringPaymentType.Manual: { //first payment var rph = new RecurringPaymentHistory() { RecurringPayment = rp, CreatedOnUtc = DateTime.UtcNow, OrderId = order.Id, }; rp.RecurringPaymentHistory.Add(rph); _orderService.UpdateRecurringPayment(rp); } break; case RecurringPaymentType.Automatic: { //will be created later (process is automated) } break; default: break; } } #endregion #region Notifications & notes //notes, messages if (_workContext.OriginalCustomerIfImpersonated != null) { //this order is placed by a store administrator impersonating a customer order.OrderNotes.Add(new OrderNote() { Note = string.Format( "Order placed by a store owner ('{0}'. ID = {1}) impersonating the customer.", _workContext.OriginalCustomerIfImpersonated.Email, _workContext.OriginalCustomerIfImpersonated.Id), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); } else { order.OrderNotes.Add(new OrderNote() { Note = "Order placed", DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); } //send email notifications int orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0) { order.OrderNotes.Add(new OrderNote() { Note = string.Format("\"Order placed\" email (to store owner) has been queued. Queued email identifier: {0}.", orderPlacedStoreOwnerNotificationQueuedEmailId), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); } int orderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedCustomerNotification(order, order.CustomerLanguageId); if (orderPlacedCustomerNotificationQueuedEmailId > 0) { order.OrderNotes.Add(new OrderNote() { Note = string.Format("\"Order placed\" email (to customer) has been queued. Queued email identifier: {0}.", orderPlacedCustomerNotificationQueuedEmailId), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); } var vendors = new List<Vendor>(); foreach (var opv in order.OrderProductVariants) { var vendorId = opv.ProductVariant.Product.VendorId; //find existing var vendor = vendors.FirstOrDefault(v => v.Id == vendorId); if (vendor == null) { //not found. load by Id vendor = _vendorService.GetVendorById(vendorId); if (vendor != null) { vendors.Add(vendor); } } } foreach (var vendor in vendors) { int orderPlacedVendorNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedVendorNotification(order, vendor, order.CustomerLanguageId); if (orderPlacedVendorNotificationQueuedEmailId > 0) { order.OrderNotes.Add(new OrderNote() { Note = string.Format("\"Order placed\" email (to vendor) has been queued. Queued email identifier: {0}.", orderPlacedVendorNotificationQueuedEmailId), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); } } //check order status CheckOrderStatus(order); //reset checkout data if (!processPaymentRequest.IsRecurringPayment) _customerService.ResetCheckoutData(customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true); if (!processPaymentRequest.IsRecurringPayment) { _customerActivityService.InsertActivity( "PublicStore.PlaceOrder", _localizationService.GetResource("ActivityLog.PublicStore.PlaceOrder"), order.Id); } //uncomment this line to support transactions //scope.Complete(); //raise event _eventPublisher.PublishOrderPlaced(order); //raise event if (order.PaymentStatus == PaymentStatus.Paid) { _eventPublisher.PublishOrderPaid(order); } #endregion } } else { foreach (var paymentError in processPaymentResult.Errors) result.AddError(string.Format("Payment error: {0}", paymentError)); } } catch (Exception exc) { _logger.Error(exc.Message, exc); result.AddError(exc.Message); } #region Process errors string error = ""; for (int i = 0; i < result.Errors.Count; i++) { error += string.Format("Error {0}: {1}", i + 1, result.Errors[i]); if (i != result.Errors.Count - 1) error += ". "; } if (!String.IsNullOrEmpty(error)) { //log it string logError = string.Format("Error while placing order. {0}", error); _logger.Error(logError); } #endregion return result; }
/// <summary> /// Gets a value indicating whether refund from admin panel is allowed /// </summary> /// <param name="order">Order</param> /// <returns>A value indicating whether refund from admin panel is allowed</returns> public virtual bool CanRefund(Order order) { if (order == null) throw new ArgumentNullException("order"); if (order.OrderTotal == decimal.Zero) return false; //uncomment the lines below in order to allow this operation for cancelled orders //if (order.OrderStatus == OrderStatus.Cancelled) // return false; if (order.PaymentStatus == PaymentStatus.Paid && _paymentService.SupportRefund(order.PaymentMethodSystemName)) return true; return false; }
/// <summary> /// Gets a value indicating whether order can be marked as voided /// </summary> /// <param name="order">Order</param> /// <returns>A value indicating whether order can be marked as voided</returns> public virtual bool CanVoidOffline(Order order) { if (order == null) throw new ArgumentNullException("order"); if (order.OrderTotal == decimal.Zero) return false; //uncomment the lines below in order to allow this operation for cancelled orders //if (order.OrderStatus == OrderStatus.Cancelled) // return false; if (order.PaymentStatus == PaymentStatus.Authorized) return true; return false; }
/// <summary> /// Set IsActivated value for purchase gift cards for particular order /// </summary> /// <param name="order">Order</param> /// <param name="activate">A value indicating whether to activate gift cards; true - actuvate, false - deactivate</param> protected void SetActivatedValueForPurchasedGiftCards(Order order, bool activate) { var giftCards = _giftCardService.GetAllGiftCards(order.Id, null, null, !activate, "", 0, int.MaxValue); foreach (var gc in giftCards) { if (activate) { //activate bool isRecipientNotified = gc.IsRecipientNotified; if (gc.GiftCardType == GiftCardType.Virtual) { //send email for virtual gift card if (!String.IsNullOrEmpty(gc.RecipientEmail) && !String.IsNullOrEmpty(gc.SenderEmail)) { var customerLang = _languageService.GetLanguageById(order.CustomerLanguageId); if (customerLang == null) customerLang = _languageService.GetAllLanguages().FirstOrDefault(); if (customerLang == null) throw new Exception("No languages could be loaded"); int queuedEmailId = _workflowMessageService.SendGiftCardNotification(gc, customerLang.Id); if (queuedEmailId > 0) isRecipientNotified = true; } } gc.IsGiftCardActivated = true; gc.IsRecipientNotified = isRecipientNotified; _giftCardService.UpdateGiftCard(gc); } else { //deactivate gc.IsGiftCardActivated = false; _giftCardService.UpdateGiftCard(gc); } } }
/// <summary> /// Checks order status /// </summary> /// <param name="order">Order</param> /// <returns>Validated order</returns> public void CheckOrderStatus(Order order) { if (order == null) throw new ArgumentNullException("order"); if (order.PaymentStatus == PaymentStatus.Paid && !order.PaidDateUtc.HasValue) { //ensure that paid date is set order.PaidDateUtc = DateTime.UtcNow; _orderService.UpdateOrder(order); } if (order.OrderStatus == OrderStatus.Pending) { if (order.PaymentStatus == PaymentStatus.Authorized || order.PaymentStatus == PaymentStatus.Paid) { SetOrderStatus(order, OrderStatus.Processing, false); } } if (order.OrderStatus == OrderStatus.Pending) { if (order.ShippingStatus == ShippingStatus.PartiallyShipped || order.ShippingStatus == ShippingStatus.Shipped || order.ShippingStatus == ShippingStatus.Delivered) { SetOrderStatus(order, OrderStatus.Processing, false); } } if (order.OrderStatus != OrderStatus.Cancelled && order.OrderStatus != OrderStatus.Complete) { if (order.PaymentStatus == PaymentStatus.Paid) { if (order.ShippingStatus == ShippingStatus.ShippingNotRequired || order.ShippingStatus == ShippingStatus.Delivered) { SetOrderStatus(order, OrderStatus.Complete, true); } } } }
/// <summary> /// Sets an order status /// </summary> /// <param name="order">Order</param> /// <param name="os">New order status</param> /// <param name="notifyCustomer">True to notify customer</param> protected void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) { if (order == null) throw new ArgumentNullException("order"); OrderStatus prevOrderStatus = order.OrderStatus; if (prevOrderStatus == os) return; //set and save new order status order.OrderStatusId = (int)os; _orderService.UpdateOrder(order); //order notes, notifications order.OrderNotes.Add(new OrderNote() { Note = string.Format("Order status has been changed to {0}", os.ToString()), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); if (prevOrderStatus != OrderStatus.Complete && os == OrderStatus.Complete && notifyCustomer) { //notification int orderCompletedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderCompletedCustomerNotification(order, order.CustomerLanguageId); if (orderCompletedCustomerNotificationQueuedEmailId > 0) { order.OrderNotes.Add(new OrderNote() { Note = string.Format("\"Order completed\" email (to customer) has been queued. Queued email identifier: {0}.", orderCompletedCustomerNotificationQueuedEmailId), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); } } if (prevOrderStatus != OrderStatus.Cancelled && os == OrderStatus.Cancelled && notifyCustomer) { //notification int orderCancelledCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderCancelledCustomerNotification(order, order.CustomerLanguageId); if (orderCancelledCustomerNotificationQueuedEmailId > 0) { order.OrderNotes.Add(new OrderNote() { Note = string.Format("\"Order cancelled\" email (to customer) has been queued. Queued email identifier: {0}.", orderCancelledCustomerNotificationQueuedEmailId), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); } } //reward points if (_rewardPointsSettings.PointsForPurchases_Awarded == order.OrderStatus) { AwardRewardPoints(order); } if (_rewardPointsSettings.PointsForPurchases_Canceled == order.OrderStatus) { ReduceRewardPoints(order); } //gift cards activation if (_orderSettings.GiftCards_Activated_OrderStatusId > 0 && _orderSettings.GiftCards_Activated_OrderStatusId == (int)order.OrderStatus) { SetActivatedValueForPurchasedGiftCards(order, true); } //gift cards deactivation if (_orderSettings.GiftCards_Deactivated_OrderStatusId > 0 && _orderSettings.GiftCards_Deactivated_OrderStatusId == (int)order.OrderStatus) { SetActivatedValueForPurchasedGiftCards(order, false); } }
/// <summary> /// Check whether return request is allowed /// </summary> /// <param name="order">Order</param> /// <returns>Result</returns> public virtual bool IsReturnRequestAllowed(Order order) { if (!_orderSettings.ReturnRequestsEnabled) return false; if (order == null || order.Deleted) return false; if (order.OrderStatus != OrderStatus.Complete) return false; bool numberOfDaysReturnRequestAvailableValid = false; if (_orderSettings.NumberOfDaysReturnRequestAvailable == 0) { numberOfDaysReturnRequestAvailableValid = true; } else { var daysPassed = (DateTime.UtcNow - order.CreatedOnUtc).TotalDays; numberOfDaysReturnRequestAvailableValid = (daysPassed - _orderSettings.NumberOfDaysReturnRequestAvailable) < 0; } return numberOfDaysReturnRequestAvailableValid; }
/// <summary> /// Gets a value indicating whether capture from admin panel is allowed /// </summary> /// <param name="order">Order</param> /// <returns>A value indicating whether capture from admin panel is allowed</returns> public virtual bool CanCapture(Order order) { if (order == null) throw new ArgumentNullException("order"); if (order.OrderStatus == OrderStatus.Cancelled || order.OrderStatus == OrderStatus.Pending) return false; if (order.PaymentStatus == PaymentStatus.Authorized && _paymentService.SupportCapture(order.PaymentMethodSystemName)) return true; return false; }
/// <summary> /// Marks order as paid /// </summary> /// <param name="order">Order</param> public virtual void MarkOrderAsPaid(Order order) { if (order == null) throw new ArgumentNullException("order"); if (!CanMarkOrderAsPaid(order)) throw new NasException("You can't mark this order as paid"); order.PaymentStatusId = (int)PaymentStatus.Paid; order.PaidDateUtc = DateTime.UtcNow; _orderService.UpdateOrder(order); //add a note order.OrderNotes.Add(new OrderNote() { Note = "Order has been marked as paid", DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); CheckOrderStatus(order); //raise event if (order.PaymentStatus == PaymentStatus.Paid) { _eventPublisher.PublishOrderPaid(order); } }
/// <summary> /// Cancels order /// </summary> /// <param name="order">Order</param> /// <param name="notifyCustomer">True to notify customer</param> public virtual void CancelOrder(Order order, bool notifyCustomer) { if (order == null) throw new ArgumentNullException("order"); if (!CanCancelOrder(order)) throw new NasException("Cannot do cancel for order."); //Cancel order SetOrderStatus(order, OrderStatus.Cancelled, notifyCustomer); //add a note order.OrderNotes.Add(new OrderNote() { Note = "Order has been cancelled", DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); //cancel recurring payments var recurringPayments = _orderService.SearchRecurringPayments(0, 0, order.Id, null, 0, int.MaxValue); foreach (var rp in recurringPayments) { //use errors? var errors = CancelRecurringPayment(rp); } //Adjust inventory foreach (var opv in order.OrderProductVariants) _productService.AdjustInventory(opv.ProductVariant, false, opv.Quantity, opv.AttributesXml); _eventPublisher.PublishOrderCancelled(order); }
/// <summary> /// Partially refunds an order (offline) /// </summary> /// <param name="order">Order</param> /// <param name="amountToRefund">Amount to refund</param> public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) { if (order == null) throw new ArgumentNullException("order"); if (!CanPartiallyRefundOffline(order, amountToRefund)) throw new NasException("You can't partially refund (offline) this order"); //total amount refunded decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; //update order info order.RefundedAmount = totalAmountRefunded; //if (order.OrderTotal == totalAmountRefunded), then set order.PaymentStatus = PaymentStatus.Refunded; order.PaymentStatus = PaymentStatus.PartiallyRefunded; _orderService.UpdateOrder(order); //add a note order.OrderNotes.Add(new OrderNote() { Note = string.Format("Order has been marked as partially refunded. Amount = {0}", _priceFormatter.FormatPrice(amountToRefund, true, false)), DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); //check order status CheckOrderStatus(order); }
/// <summary> /// Gets a value indicating whether order can be marked as authorized /// </summary> /// <param name="order">Order</param> /// <returns>A value indicating whether order can be marked as authorized</returns> public virtual bool CanMarkOrderAsAuthorized(Order order) { if (order == null) throw new ArgumentNullException("order"); if (order.OrderStatus == OrderStatus.Cancelled) return false; if (order.PaymentStatus == PaymentStatus.Pending) return true; return false; }
/// <summary> /// Sends an order completed notification to a customer /// </summary> /// <param name="order">Order instance</param> /// <param name="languageId">Message language identifier</param> /// <returns>Queued email identifier</returns> public virtual int SendOrderCompletedCustomerNotification(Order order, int languageId) { if (order == null) throw new ArgumentNullException("order"); var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; languageId = EnsureLanguageIsActive(languageId, store.Id); var messageTemplate = GetLocalizedActiveMessageTemplate("OrderCompleted.CustomerNotification", languageId, store.Id); if (messageTemplate == null) return 0; //tokens var tokens = new List<Token>(); _messageTokenProvider.AddStoreTokens(tokens, store); _messageTokenProvider.AddOrderTokens(tokens, order, languageId); _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); var toEmail = order.BillingAddress.Email; var toName = string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName); return SendNotification(messageTemplate, emailAccount, languageId, tokens, toEmail, toName); }
/// <summary> /// Gets a value indicating whether order can be marked as paid /// </summary> /// <param name="order">Order</param> /// <returns>A value indicating whether order can be marked as paid</returns> public virtual bool CanMarkOrderAsPaid(Order order) { if (order == null) throw new ArgumentNullException("order"); if (order.OrderStatus == OrderStatus.Cancelled) return false; if (order.PaymentStatus == PaymentStatus.Paid || order.PaymentStatus == PaymentStatus.Refunded || order.PaymentStatus == PaymentStatus.Voided) return false; return true; }
/// <summary> /// Gets a value indicating whether customers can complete a payment after order is placed but not completed (for redirection payment methods) /// </summary> /// <param name="order">Order</param> /// <returns>Result</returns> public bool CanRePostProcessPayment(Order order) { if (order == null) throw new ArgumentNullException("order"); //it's not a redirection payment method. So we always return false return false; }
/// <summary> /// Gets a value indicating whether order can be marked as partially refunded /// </summary> /// <param name="order">Order</param> /// <param name="amountToRefund">Amount to refund</param> /// <returns>A value indicating whether order can be marked as partially refunded</returns> public virtual bool CanPartiallyRefundOffline(Order order, decimal amountToRefund) { if (order == null) throw new ArgumentNullException("order"); if (order.OrderTotal == decimal.Zero) return false; //uncomment the lines below in order to allow this operation for cancelled orders //if (order.OrderStatus == OrderStatus.Cancelled) // return false; decimal canBeRefunded = order.OrderTotal - order.RefundedAmount; if (canBeRefunded <= decimal.Zero) return false; if (amountToRefund > canBeRefunded) return false; if (order.PaymentStatus == PaymentStatus.Paid || order.PaymentStatus == PaymentStatus.PartiallyRefunded) return true; return false; }