public override PaymentStepResult Process(IPayment payment, IOrderForm orderForm, IOrderGroup orderGroup, IShipment shipment)
        {
            var paymentStepResult = new PaymentStepResult();

            if (payment.TransactionType == TransactionType.Void.ToString())
            {
                try
                {
                    var orderId         = orderGroup.Properties[Constants.SwedbankPayOrderIdField]?.ToString();
                    var previousPayment = orderForm.Payments.FirstOrDefault(x => x.IsSwedbankPayPayment());

                    //If payed by swish, do a reversal
                    if (previousPayment != null && previousPayment.TransactionType == TransactionType.Sale.ToString() && !string.IsNullOrWhiteSpace(orderId))
                    {
                        var paymentOrder = AsyncHelper.RunSync(() => SwedbankPayClient.PaymentOrders.Get(new Uri(orderId, UriKind.Relative)));
                        if (paymentOrder.Operations.Reverse == null)
                        {
                            paymentStepResult.Message = "Reversal is not a valid operation";

                            AddNoteAndSaveChanges(orderGroup, payment.TransactionType, $"{paymentStepResult.Message}");
                        }
                        else
                        {
                            var reversalRequest  = _requestFactory.GetReversalRequest(payment, orderForm.GetAllLineItems(), _market, shipment, description: "Cancelling purchase order");
                            var reversalResponse = AsyncHelper.RunSync(() => paymentOrder.Operations.Reverse(reversalRequest));
                            if (reversalResponse.Reversal.Transaction.Type == Sdk.TransactionType.Reversal)
                            {
                                payment.Status = PaymentStatus.Processed.ToString();
                                AddNoteAndSaveChanges(orderGroup, payment.TransactionType, $"Refunded {payment.Amount}");
                                paymentStepResult.Status = true;
                                return(paymentStepResult);
                            }
                            else
                            {
                                paymentStepResult.Message = "Error when executing reversal";
                                AddNoteAndSaveChanges(orderGroup, payment.TransactionType, $"Error occurred {paymentStepResult.Message}");
                            }
                        }
                    }

                    else if (!string.IsNullOrWhiteSpace(orderId))
                    {
                        var paymentOrder = AsyncHelper.RunSync(() => SwedbankPayClient.PaymentOrders.Get(new Uri(orderId, UriKind.Relative)));
                        if (paymentOrder.Operations.Cancel == null)
                        {
                            AddNoteAndSaveChanges(orderGroup, payment.TransactionType, $"Cancel is not possible on this order {orderId}");
                            return(paymentStepResult);
                        }

                        var cancelRequest  = _requestFactory.GetCancelRequest();
                        var cancelResponse = AsyncHelper.RunSync(() => paymentOrder.Operations.Cancel(cancelRequest));
                        if (cancelResponse.Cancellation.Transaction.Type == Sdk.TransactionType.Cancellation && cancelResponse.Cancellation.Transaction.State.Equals(State.Completed))
                        {
                            payment.Status                = PaymentStatus.Processed.ToString();
                            payment.TransactionID         = cancelResponse.Cancellation.Transaction.Number;
                            payment.ProviderTransactionID = cancelResponse.Cancellation.Transaction.Id.ToString();
                            AddNoteAndSaveChanges(orderGroup, payment.TransactionType, "Order cancelled at SwedbankPay");
                            return(paymentStepResult);
                        }
                    }

                    return(paymentStepResult);
                }
                catch (Exception ex)
                {
                    payment.Status            = PaymentStatus.Failed.ToString();
                    paymentStepResult.Message = ex.Message;
                    AddNoteAndSaveChanges(orderGroup, payment.TransactionType, $"Error occurred {ex.Message}");
                    Logger.Error(ex.Message, ex);
                }
            }

            if (Successor != null)
            {
                return(Successor.Process(payment, orderForm, orderGroup, shipment));
            }

            return(paymentStepResult);
        }
        public Document CreateDocumentForPreAuthenticateRequest(IPayment payment, IOrderForm orderForm, Currency orderGroupCurrency)
        {
            var dataCashReference = payment.Properties[DataCashPaymentGateway.DataCashReferencePropertyName] as string;
            var merchantReference = payment.Properties[DataCashPaymentGateway.DataCashMerchantReferencePropertyName] as string;

            var requestDoc = CreateDocument();

            requestDoc.set("Request.Transaction.TxnDetails.merchantreference", merchantReference);
            requestDoc.set("Request.Transaction.TxnDetails.amount", payment.Amount.ToString("0.##"));
            requestDoc.set("Request.Transaction.CardTxn.method", "pre");
            requestDoc.setWithAttributes("Request.Transaction.CardTxn.card_details", dataCashReference, new Hashtable()
            {
                { "type", "from_hps" }
            });

            // 3rd Man Service
            requestDoc.setWithAttributes("Request.Transaction.TxnDetails.The3rdMan", string.Empty, new Hashtable()
            {
                { "type", "realtime" }
            });

            // Customer Information
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.CustomerInformation.customer_reference", DateTime.Now.Ticks.ToString());
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.CustomerInformation.forename", payment.BillingAddress.FirstName);
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.CustomerInformation.surname", payment.BillingAddress.LastName);
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.CustomerInformation.telephone", payment.BillingAddress.DaytimePhoneNumber);
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.CustomerInformation.email", payment.BillingAddress.Email);
            // for realtime fraud check requests
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.CustomerInformation.mobile_telephone_number", payment.BillingAddress.DaytimePhoneNumber);
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.CustomerInformation.ip_address", Utilities.GetIPAddress(HttpContext.Current.Request));

            // Delivery address
            var shippingAddress = orderForm.Shipments.First().ShippingAddress;

            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.DeliveryAddress.street_address_1", shippingAddress.Line1);
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.DeliveryAddress.street_address_2", shippingAddress.Line2);
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.DeliveryAddress.city", shippingAddress.City);
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.DeliveryAddress.country", CountryCodes.GetNumericCountryCode(shippingAddress.CountryCode));
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.DeliveryAddress.postcode", shippingAddress.PostalCode);

            // Billing address
            var billingAddress = payment.BillingAddress;

            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.BillingAddress.street_address_1", billingAddress.Line1);
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.BillingAddress.street_address_2", billingAddress.Line2);
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.BillingAddress.city", billingAddress.City);
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.BillingAddress.country", CountryCodes.GetNumericCountryCode(billingAddress.CountryCode));
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.BillingAddress.postcode", billingAddress.PostalCode);

            // Order information
            var allLineItems = orderForm.GetAllLineItems().ToList();

            requestDoc.setWithAttributes("Request.Transaction.TxnDetails.The3rdMan.OrderInformation.Products", string.Empty, new Hashtable()
            {
                { "count", allLineItems.Count.ToString() }
            });

            foreach (var xmlElement in CreateProductXmFromLineItems(requestDoc, allLineItems, orderGroupCurrency))
            {
                requestDoc.setXmlElement("Request.Transaction.TxnDetails.The3rdMan.OrderInformation.Products.Product", xmlElement);
            }

            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.http_header_fields", GetHeaders());
            // Register the consumer associated with this transaction for the consumer product
            requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.register_consumer_watch", "true");

            // Uncomment this line if you are using full account, since 3rd man fraud checking is not available for test account
            //requestDoc.set("Request.Transaction.TxnDetails.The3rdMan.Realtime.real_time_sha1", HashCode(merchantReference));

            return(requestDoc);
        }
        /* RewardDescription is "checking" whether the promotion was fulfilled (or not, or partially),
         *  ...which items the promotion was applied to...
         *  ...the fake-cart is taken care of)
         * PromotionProcessorBase has one abstract method to be implemented, Evaluate.
         * ...the method is supplied with a PromotionData, and a PromotionProcessorContext object
         * ...that contains information about the current order/fakeCart.
         */

        protected override RewardDescription Evaluate( // note: it's OrderForm now...
            MyPercentagePromotion promotionData        // the model --> look in the UI to see the properties
            , PromotionProcessorContext context)
        {
            /* A reward description contains information about if and how a reward is applied.
             * ...some properties are:
             *   - A list of redemption descriptions, one for each of the maximum amount of redemptions
             *      ...that could be applied to the current order.
             *     This does not have to take redemption limits into consideration, that is handled by the
             *      promotion engine.
             *   - A reward type. Depending on the type, the promotion value is read from the properties
             *      UnitDiscount, Percentage or Quantity.
             *   - A status flag. Indicates if a promotion is not, partially, or fully fulfilled.
             *   - A saved amount. The amount by which this reward reduces the order cost.
             *      Is set by the promotion engine; should not be set in the promotion processor*/

            IOrderForm orderForm = context.OrderForm; // OrderForm now pops in with the context
            //context. // lots of things
            IEnumerable <ILineItem> lineItemsCheck = orderForm.GetAllLineItems();
            IEnumerable <ILineItem> lineItems      = GetLineItems(context.OrderForm);

            #region Just Checking

            //var e = _contentLoader.Get<EntryContentBase>(item0);

            //should check if it's applicable... at all

            //var li = _orderFactory.Service.CreateLineItem(e.Code);
            //li.Quantity = 1;
            //li.PlacedPrice = 15;
            //orderForm.Shipments.First().LineItems.Add(li);

            #endregion

            // GetFulfillmentStatus - extension method
            FulfillmentStatus status = promotionData.MinNumberOfItems.GetFulfillmentStatus(
                orderForm, _targetEvaluator, _fulfillmentEvaluator);

            List <RewardDescription>     rewardDescriptions     = new List <RewardDescription>();
            List <RedemptionDescription> redemptionDescriptions = new List <RedemptionDescription>();

            #region NewStuff

            // The below does not see the cart, it's for landing pages for the Promotion itself (rendering)
            PromotionItems promoItems = GetPromotionItems(promotionData); // gets null

            var condition = promotionData.PercentageDiscount;             // ...in the model
            var targets   = promotionData.DiscountTargets;                // get one in any case, points to what's at "promo"

            var skuCodes = _targetEvaluator.GetApplicableCodes(
                lineItems, targets.Items, targets.MatchRecursive); // get one if kicked in, 0 if not

            var fulfillmentStatus = _fulfillmentEvaluator.GetStatusForBuyQuantityPromotion(
                skuCodes
                , lineItems
                , promotionData.MinNumberOfItems.RequiredQuantity
                , promotionData.PartialFulfillmentNumberOfItems);

            // Just checking
            // The promotion engine creates a "price matrix" for all items in the order form.
            // OrderFormPriceMatrix, is accessible through the EntryPrices property
            //   of the PromotionProcessorContext object.
            // PromotionProcessorContext is passed to the Evaluate method as one of the arguments.
            //  ...the matrix holds "codes" and quantity
            // The second ExtractEntries call starts to receive entries where the first call ended.
            //   ... makes it easy to create several redemptions by calling ExtractEntries in a loop,
            //   ... and create one RedemptionDescription inside the loop.
            // The price matrix has one public method (ExtractEntries)
            //   ... two overloads, both overloads takes entry codes and quantity as parameters.
            //   ... one contains an action for getting the entries in a specific order.
            //   ... if no specific order is specified, MostExpensiveFirst is used.
            var affectedEntries = context.EntryPrices.ExtractEntries(
                skuCodes,
                1); // get one if it kicks in, null if not

            if (affectedEntries != null)
            {
                IEnumerable <PriceEntry> priceEntries = affectedEntries.PriceEntries;
                foreach (var item in priceEntries)
                {
                    var qty     = item.Quantity;
                    var price   = item.Price;
                    var calc    = item.CalculatedTotal; // involves the Qty
                    var actuals = item.ActualTotal;     // includes rounding
                }
            }

            // could have a look here
            switch (fulfillmentStatus)
            {
            case FulfillmentStatus.NotFulfilled:
                break;

            case FulfillmentStatus.PartiallyFulfilled:
                break;

            case FulfillmentStatus.Fulfilled:
                break;

            case FulfillmentStatus.CouponCodeRequired:
                break;

            case FulfillmentStatus.Excluded:
                break;

            case FulfillmentStatus.VisitorGroupRequired:
                break;

            case FulfillmentStatus.RedemptionLimitReached:
                break;

            case FulfillmentStatus.NoMoneySaved:
                break;

            case FulfillmentStatus.InvalidCoupon:
                break;

            case FulfillmentStatus.InvalidCombination:
                break;

            case FulfillmentStatus.MissingVisitorGroup:
                break;

            case FulfillmentStatus.NoRedemptionRemaining:
                break;

            case FulfillmentStatus.Ineffective:
                break;

            default:
                break;
            }

            // ... an extension method
            return(RewardDescription.CreatePercentageReward(
                       fulfillmentStatus
                       , GetRedemptions(skuCodes, promotionData, context)
                       , promotionData
                       , promotionData.PercentageDiscount.Percentage
                       //, fulfillmentStatus.GetRewardDescriptionText()
                       , fulfillmentStatus.GetRewardDescriptionText() + " : " + promotionData.Description + " : "
                       ));

            #endregion

            #region Older stuff and debug - no show

            #region Older not in use

            //RewardDescription rewardDescription = new RewardDescription();

            //var codes = _targetEvaluator.GetApplicableCodes(lineItems,)

            //_fulfillmentEvaluator.GetStatusForBuyQuantityPromotion(
            //    )

            #endregion // new stuff

            #region Previous version

            //if (status.HasFlag(FulfillmentStatus.Fulfilled))
            //{
            //    return RewardDescription.CreateMoneyOrPercentageRewardDescription(
            //        status,
            //        redemptionDescriptions,
            //        promotionData,
            //        promotionData.PercentageDiscount,
            //        context.OrderGroup.Currency,
            //        "Custom promotion fulfilled"); // should have a more flexible way... GetDescription()

            //}
            //else
            //{
            //    return RewardDescription.CreateNotFulfilledDescription(
            //        promotionData, FulfillmentStatus.NotFulfilled);
            //}


            #endregion

            #region Debug

            //RedemptionDescription rFirst;
            //redemptionDescriptions.Add(CreateRedemptionDescriptionText(orderForm));

            // below "if-construct" is for debug
            //if (promotionData.PercentageDiscount <= 0) // ... return "sorry, no discount"
            //{
            //    return RewardDescription.CreatePercentageReward(
            //        FulfillmentStatus.NotFulfilled,
            //        redemptionDescriptions,
            //        promotionData,
            //        0,
            //        CreateRewardDescriptionText(redemptionDescriptions.First(), FulfillmentStatus.NotFulfilled, promotionData));

            //    /*RewardDescription.CreateMoneyOrPercentageRewardDescription(FulfillmentStatus.NotFulfilled,r,promotionData,null);*/
            //}

            //IEnumerable<ContentReference> targetItems = promotionData.DiscountTargets.Items.ToList(); // set by the Promo-UI

            //bool matchRecursive = true; // walking down the catalog hierarchy
            //var lineItems = GetLineItems(orderForm); // "GetLineItems" - in the base class (PromotionProcessorBase)
            //var affectedItems = _targetEvaluator.GetApplicableItems(lineItems, targetItems, matchRecursive); // in CollectionTargetEvaluator
            //var affectedItems = _targetEvaluator.GetApplicableCodes(orderForm.GetAllLineItems(), targetItems, false);


            // small class --> just to get the status by the settings
            //var status = FulfillmentEvaluator.GetStatusForBuyQuantityPromotion(affectedItems.Select(x => x.LineItem)
            //  , promotionData.MinNumberOfItems, promotionData.PartialFulfillmentNumberOfItems); // in the model
            //var s = FulfillmentEvaluator.

            //FulfillmentEvaluator ff = new FulfillmentEvaluator();

            //if (rewardDescriptions.Any())
            //{
            //    return rewardDescriptions.First();
            //}
            //else
            //{
            //    return null;
            //}


            /*return RewardDescription.CreateMoneyOrPercentageRewardDescription(
             *  status,
             *  affectedItems,
             *  promotionData,
             *  promotionData.PercentageDiscount,
             *  GetRewardDescriptionText(affectedItems, status, promotionData));*/

            #endregion

            #endregion
        } // end RewardDescription
 protected override Money CalculateSubtotal(IOrderForm orderForm, Currency currency)
 {
     var result = orderForm.GetAllLineItems().Where(x => !x.IsGift).Sum(lineItem => _lineItemCalculator.GetDiscountedPrice(lineItem, currency).Amount);
     return new Money(result, currency);
 }