/// <summary>
        /// This method allows you void an authorized but not captured payment. In this case a Void button will be visible on the
        /// order details page in admin area. Note that an order should be authorized and SupportVoid property should return true.
        /// </summary>
        /// <param name="voidPaymentRequest">Request</param>
        /// <returns>Result</returns>
        public VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest)
        {
            var result = new VoidPaymentResult();

            Order order = voidPaymentRequest.Order;

            if (int.TryParse(order.AuthorizationTransactionId, out int transactionNumber))
            {
                PayexInterface payex        = GetPayexInterface();
                CancelResult   cancelResult = payex.Cancel(transactionNumber).GetAwaiter().GetResult();

                if (cancelResult.IsRequestSuccessful)
                {
                    result.NewPaymentStatus = PaymentStatus.Voided;
                }
                else
                {
                    result.AddError(cancelResult.GetErrorDescription());
                }
            }
            else
            {
                result.AddError(
                    string.Format(
                        "The order did not contain a valid TransactionNumber in the AuthorizationTransactionId field ('{0}').",
                        order.AuthorizationTransactionId));
            }

            return(result);
        }
        internal Task <CompleteResult> Complete(string orderRef)
        {
            PayexInterface payex = GetPayexInterface();

            var result = payex.Complete(orderRef);

            return(result);
        }
        /// <summary>
        /// This method allows you make a refund. In this case a Refund button will be visible on the order details page in admin
        /// area. Note that an order should be paid, and SupportRefund or SupportPartiallyRefund property should return true.
        /// </summary>
        /// <param name="refundPaymentRequest">Request</param>
        /// <returns>Result</returns>
        public RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest)
        {
            var result = new RefundPaymentResult();

            Order order = refundPaymentRequest.Order;

            if (int.TryParse(order.CaptureTransactionId, out int transactionNumber))
            {
                decimal amount = Math.Round(refundPaymentRequest.AmountToRefund, 2);

                PayexInterface payex        = GetPayexInterface();
                CreditResult   creditResult = payex.Credit(transactionNumber, amount, order.OrderGuid.ToString())
                                              .GetAwaiter().GetResult();

                if (creditResult.IsRequestSuccessful)
                {
                    // NOTE: We should save the transaction id for the refund, but no field is available in order.
                    // Add order note
                    var note = new StringBuilder();
                    note.AppendLine("PayEx: Credit succeeded");
                    note.AppendLine(string.Format("Credited amount: {0:n2}", amount));
                    note.AppendLine("TransactionNumber: " + creditResult.TransactionNumber);
                    note.AppendLine("TransactionStatus: " + creditResult.TransactionStatus);
                    order.OrderNotes.Add(
                        new OrderNote
                    {
                        Note = note.ToString(),
                        DisplayToCustomer = false,
                        CreatedOnUtc      = DateTime.UtcNow
                    });
                    _orderService.UpdateOrder(order);
                    // Set new payment status
                    if (refundPaymentRequest.IsPartialRefund &&
                        refundPaymentRequest.AmountToRefund + order.RefundedAmount < order.OrderTotal)
                    {
                        result.NewPaymentStatus = PaymentStatus.PartiallyRefunded;
                    }
                    else
                    {
                        result.NewPaymentStatus = PaymentStatus.Refunded;
                    }
                }
                else
                {
                    result.AddError(creditResult.GetErrorDescription());
                }
            }
            else
            {
                result.AddError(
                    string.Format(
                        "The order did not contain a valid TransactionNumber in the AuthorizationTransactionId field ('{0}').",
                        order.AuthorizationTransactionId));
            }

            return(result);
        }
        /// <summary>
        /// Some payment gateways allow you to authorize payments before they're captured. It allows store owners to review order
        /// details before the payment is actually done.
        /// </summary>
        /// <param name="capturePaymentRequest">Capture payment request</param>
        /// <returns>Capture payment result</returns>
        public CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest)
        {
            var result = new CapturePaymentResult();

            Order order = capturePaymentRequest.Order;

            if (int.TryParse(order.AuthorizationTransactionId, out int transactionNumber))
            {
                decimal amount = Math.Round(order.OrderTotal, 2);

                PayexInterface payex         = GetPayexInterface();
                CaptureResult  captureResult = payex.Capture(transactionNumber, amount, order.OrderGuid.ToString())
                                               .GetAwaiter().GetResult();

                result.CaptureTransactionResult = captureResult.ErrorCode;
                result.CaptureTransactionId     = captureResult.TransactionNumber;

                if (captureResult.IsTransactionSuccessful)
                {
                    result.NewPaymentStatus = PaymentStatus.Paid;
                    // Add order note
                    var note = new StringBuilder();
                    note.AppendLine("PayEx: Capture succeeded");
                    note.AppendLine(string.Format("Amount: {0:n2}", amount));
                    note.AppendLine("TransactionNumber: " + captureResult.TransactionNumber);
                    note.AppendLine("TransactionStatus: " + captureResult.TransactionStatus);
                    order.OrderNotes.Add(
                        new OrderNote
                    {
                        Note = note.ToString(),
                        DisplayToCustomer = false,
                        CreatedOnUtc      = DateTime.UtcNow
                    });
                    _orderService.UpdateOrder(order);
                }
                else
                {
                    result.AddError(captureResult.GetErrorDescription());
                }
            }
            else
            {
                result.Errors.Add(
                    string.Format(
                        "The order did not contain a valid TransactionNumber in the AuthorizationTransactionId field ('{0}').",
                        order.AuthorizationTransactionId));
            }

            return(result);
        }
        private PayexInterface GetPayexInterface()
        {
            PayexAccount account;

            if (_payExPaymentSettings.AccountNumber > 0)
            {
                account = new PayexAccount(_payExPaymentSettings.AccountNumber, _payExPaymentSettings.EncryptionKey);
            }
            else
            {
                account = new PayexAccount(TestAccount, TestEncryptionKey);
            }
            PayexInterface payex = new PayexInterface(account);

            payex.UseTestEnvironment =
                _payExPaymentSettings.UseTestEnvironment || _payExPaymentSettings.AccountNumber <= 0;
            return(payex);
        }
        private async Task <bool> AddOrderLine(
            PayexInterface payex, string orderRef, string itemNumber, string itemDescription, int quantity,
            decimal amount)
        {
            if (string.IsNullOrEmpty(itemNumber))
            {
                return(true); // Skip
            }
            BaseResult lineResult = await payex.AddSingleOrderLine(
                new AddSingleOrderLineRequest
            {
                OrderRef         = orderRef,
                ItemNumber       = itemNumber,
                ItemDescription1 = itemDescription,
                Quantity         = quantity,
                Amount           = amount,
            });

            return(lineResult.IsRequestSuccessful);
        }
        /// <summary>
        /// Post process payment (used by payment gateways that require redirecting to a third-party URL)
        /// </summary>
        /// <param name="postProcessPaymentRequest">Payment info required for an order processing</param>
        public void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest)
        {
            Order order = postProcessPaymentRequest.Order;

            // Make sure order is not already paid or authorized
            if (order.PaymentStatus == PaymentStatus.Paid || order.PaymentStatus == PaymentStatus.Authorized)
            {
                return;
            }

            string currencyCode =
                _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode;
            string description = string.Format("{0} - Order", _storeContext.CurrentStore.Name);
            string userAgent;

            if (_httpContextAccessor.HttpContext.Request != null)
            {
                userAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
            }
            else
            {
                userAgent = null;
            }

            string returnUrl = _webHelper.GetStoreLocation(false) + "Plugins/PaymentPayEx/Complete";
            string cancelUrl = _webHelper.GetStoreLocation(false) + "Plugins/PaymentPayEx/CancelOrder?id=" + order.Id;

            PayexInterface payex = GetPayexInterface();

            string agreementRef = null;

            // If the customer wishes to save his payment details, we make an agreement.
            // This should be saved later in the complete operation, if it occurs.
            agreementRef = TryGetAgreementRef(_paymentService.DeserializeCustomValues(order));
            if (agreementRef == "new")
            {
                CreateAgreementRequest agreementRequest = new CreateAgreementRequest
                {
                    PurchaseOperation = GetPurchaseOperation(),
                    MerchantRef       = order.OrderGuid.ToString(),
                    MaxAmount         = _payExPaymentSettings.AgreementMaxAmount,
                    Description       = description,
                };
                CreateAgreementResult agreementResult =
                    payex.CreateAgreement(agreementRequest).GetAwaiter().GetResult();
                if (agreementResult.IsRequestSuccessful)
                {
                    agreementRef = agreementResult.AgreementRef;
                }
                else
                {
                    _logger.Error(
                        string.Format("PayEx: CreateAgreement (for AutoPay) failed for order {0}.", order.Id),
                        new NopException(agreementResult.GetErrorDescription()), order.Customer);
                }
            }

            // Initialize the purchase and get the redirect URL
            InitializeRequest request = new InitializeRequest
            {
                PurchaseOperation = GetPurchaseOperation(),
                Amount            = order.OrderTotal,
                CurrencyCode      = currencyCode,
                OrderID           = order.OrderGuid.ToString(),
                ProductNumber     = "ncOrder",
                Description       = description,
                AgreementRef      = agreementRef,
                //VatPercent = 100M * order.OrderTax / order.OrderTotal,
                ClientIPAddress = order.CustomerIp,
                UserAgent       = userAgent,
                ReturnURL       = returnUrl,
                CancelUrl       = cancelUrl,
                View            = PaymentView,
                ClientLanguage  = _workContext.WorkingLanguage?.LanguageCulture,
            };

            BeforeInitialize(postProcessPaymentRequest, request);

            InitializeResult result = payex.Initialize(request).GetAwaiter().GetResult();

            if (result.IsRequestSuccessful)
            {
                // Save OrderRef in case TransactionCallback fails or is implemented externally.
                order.AuthorizationTransactionCode = result.OrderRef;
                _orderService.UpdateOrder(order);
                if (_payExPaymentSettings.PassProductNamesAndTotals)
                {
                    AddOrderLines(payex, result.OrderRef, order);
                }
                // Redirect to PayEx
                _httpContextAccessor.HttpContext.Response.Redirect(result.RedirectUrl);
            }
            else
            {
                throw new NopException(result.GetErrorDescription());
            }
        }
        /// <summary>
        /// This method is always invoked right before a customer places an order.
        /// Use it when you need to process a payment before an order is stored into database.
        /// For example, capture or authorize credit card. Usually this method is used when a customer
        /// is not redirected to third-party site for completing a payment and all payments
        /// are handled on your site (for example, PayPal Direct).
        /// </summary>
        /// <param name="processPaymentRequest">Payment info required for an order processing</param>
        /// <returns>Process payment result</returns>
        public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest)
        {
            var result = new ProcessPaymentResult();

            result.NewPaymentStatus = PaymentStatus.Pending;

            // Use an existing agreement to make the payment, if the customer chose this option.
            if (!processPaymentRequest.CustomValues.TryGetValue(AgreementRefKey, out object agreementRefObject) ||
                agreementRefObject == null)
            {
                return(result);
            }

            var agreementRef = TryGetAgreementRef(processPaymentRequest.CustomValues);

            if (!string.IsNullOrEmpty(agreementRef) &&
                agreementRef != "new")
            {
                string currencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)
                                      .CurrencyCode;
                string         description = string.Format("{0} - Order", _storeContext.CurrentStore.Name);
                AutoPayRequest request     = new AutoPayRequest
                {
                    PurchaseOperation = GetPurchaseOperation(),
                    Amount            = processPaymentRequest.OrderTotal,
                    CurrencyCode      = currencyCode,
                    OrderID           = processPaymentRequest.OrderGuid.ToString(),
                    ProductNumber     = "ncOrder",
                    Description       = description,
                    AgreementRef      = agreementRef,
                };
                PayexInterface payex         = GetPayexInterface();
                AutoPayResult  autopayResult = payex.AutoPay(request).GetAwaiter().GetResult();

                // Check result and set new payment status
                if (autopayResult.IsTransactionSuccessful)
                {
                    result.SubscriptionTransactionId = agreementRef;
                    if (autopayResult.TransactionStatus.Value == Enumerations.TransactionStatusCode.Authorize)
                    {
                        result.NewPaymentStatus               = PaymentStatus.Authorized;
                        result.AuthorizationTransactionId     = autopayResult.TransactionNumber;
                        result.AuthorizationTransactionResult = autopayResult.ErrorCode;
                    }
                    else if (autopayResult.TransactionStatus.Value == Enumerations.TransactionStatusCode.Sale)
                    {
                        result.NewPaymentStatus         = PaymentStatus.Paid;
                        result.CaptureTransactionId     = autopayResult.TransactionNumber;
                        result.CaptureTransactionResult = autopayResult.ErrorCode;
                    }
                }
                else
                {
                    _logger.Error(
                        string.Format("PayEx: AutoPay failed for order {0}.", processPaymentRequest.OrderGuid),
                        new NopException(autopayResult.GetErrorDescription()));
                }
            }

            return(result);
        }
        private void AddOrderLines(PayexInterface payex, string orderRef, Order order)
        {
            //get the items in the cart
            decimal cartTotal = decimal.Zero;
            var     cartItems = order.OrderItems;
            int     itemIndex = 1;

            foreach (var item in cartItems)
            {
                AddOrderLine(
                    payex, orderRef, item.Product.Sku ?? string.Format("{0}", itemIndex++), item.Product.Name,
                    item.Quantity, item.PriceInclTax)
                .GetAwaiter().GetResult();
                cartTotal += item.PriceInclTax;
            }

            //the checkout attributes that have a value and send them to PayEx as items to be paid for
            var caValues = _checkoutAttributeParser.ParseCheckoutAttributeValues(order.CheckoutAttributesXml);

            foreach (var val in caValues)
            {
                var caPriceInclTax   = _taxService.GetCheckoutAttributePrice(val, true, order.Customer);
                CheckoutAttribute ca = val.CheckoutAttribute;
                if (caPriceInclTax > decimal.Zero && ca != null) //if it has a price
                {
                    AddOrderLine(payex, orderRef, string.Format("{0}", itemIndex++), ca.Name, 1, caPriceInclTax)
                    .GetAwaiter().GetResult();
                    cartTotal += caPriceInclTax;
                }
            }

            //order totals

            //shipping
            var orderShippingInclTax = order.OrderShippingInclTax;

            if (orderShippingInclTax > decimal.Zero)
            {
                AddOrderLine(
                    payex, orderRef, string.Format("{0}", itemIndex++),
                    _localizationService.GetResource("Order.Shipping"), 1, orderShippingInclTax)
                .GetAwaiter().GetResult();
                cartTotal += orderShippingInclTax;
            }

            //payment method additional fee
            var paymentMethodAdditionalFeeInclTax = order.PaymentMethodAdditionalFeeInclTax;

            if (paymentMethodAdditionalFeeInclTax > decimal.Zero)
            {
                AddOrderLine(
                    payex, orderRef, string.Format("{0}", itemIndex++),
                    _localizationService.GetResource("Order.PaymentMethodAdditionalFee"), 1,
                    paymentMethodAdditionalFeeInclTax)
                .GetAwaiter().GetResult();
                cartTotal += paymentMethodAdditionalFeeInclTax;
            }

            if (cartTotal > order.OrderTotal)
            {
                /* Take the difference between what the order total is and what it should be and use that as the "discount".
                 * The difference equals the amount of the gift card and/or reward points used.
                 */
                decimal discountTotal = cartTotal - order.OrderTotal;
                //gift card or rewared point amount applied to cart in nopCommerce
                AddOrderLine(
                    payex, orderRef, string.Format("{0}", itemIndex++),
                    _localizationService.GetResource("Admin.Orders.Products.Discount"), 1, -discountTotal)
                .GetAwaiter().GetResult();
            }
        }