/// <summary>
        /// Builds the basket.
        /// </summary>
        /// <param name="connector">The connector.</param>
        /// <param name="order">The order.</param>
        /// <returns>
        /// A string containing the contents of the order
        /// </returns>
        private static string BuildBasket(CrmConnector connector, OrderProvider orderProvider, SalesOrder order)
        {
            StringBuilder basket = new StringBuilder();

            // A basket item comprises of Product Name, Quantity, Unit Cost, Tax, Cost inc. Tax, and Total Cost (Cost*Quantity).  These are all seperated with colons (i.e. %3A).
            string basketItem = String.Empty;
            string seperator  = "%3A";

            // Get a list of the names for the level Option Set.
            MetaDataProvider         metaDataProvider     = new MetaDataProvider(connector);
            OptionMetadataCollection levelOptionSetLabels = metaDataProvider.RetrieveOptionSetMetaDataCollection(SalesOrderDetail.EntityLogicalName, "lss_level");

            int basketCount = 0;
            List <SalesOrderDetail> productExtras;
            List <SalesOrderDetail> orderDetails = orderProvider.GetOrderDetail(order.Id, ProductType.Course);

            foreach (SalesOrderDetail detail in orderDetails)
            {
                string basketItemName = GetBasketNameOfProduct(detail, levelOptionSetLabels);

                // Add name of basket item with a hard-coded Quantity of 1
                basketItem = seperator + basketItemName + seperator + "1" + seperator;

                // For each product check if it has any related products
                productExtras = orderProvider.GetProductExtras(detail.Id);
                decimal extraUnitCost = 0;
                decimal extraTax      = 0;
                decimal extraTotal    = 0;
                foreach (SalesOrderDetail extra in productExtras)
                {
                    extraUnitCost += extra.BaseAmount == null ? 0 : extra.BaseAmount.Value;
                    extraTax      += extra.Tax == null ? 0 : extra.Tax.Value;
                    extraTotal    += extra.ExtendedAmount == null ? 0 : extra.ExtendedAmount.Value;
                }

                // Add unit cost
                basketItem += (detail.BaseAmount.Value + extraUnitCost).ToString("#.##") + seperator;

                // Add Tax
                basketItem += detail.Tax == null ? "0" : (detail.Tax.Value + extraTax).ToString("#.##") + seperator;

                // Add Cost inc. Tax
                basketItem += (detail.ExtendedAmount.Value + extraTotal).ToString("#.##") + seperator;

                // Add Total Cost
                basketItem += (detail.ExtendedAmount.Value + extraTotal).ToString("#.##");

                basket.Append(basketItem);
                basketCount++;
            }

            basket.Insert(0, "&Basket=" + basketCount.ToString());

            return(basket.ToString());
        }
        /// <summary>
        /// Creates the post request.
        /// </summary>
        /// <param name="configuration">The configuration.</param>
        /// <param name="connector">The connector.</param>
        /// <param name="paymentId">The payment id.</param>
        /// <returns></returns>
        private static string CreatePostRequest(ConfigurationProvider configuration, CrmConnector connector, lss_payment payment)
        {
            OrderProvider   orderProvider   = new OrderProvider(connector);
            ContactProvider contactProvider = new ContactProvider(connector);
            PaymentProvider paymentProvider = new PaymentProvider(connector);

            var order   = orderProvider.GetOrder(payment.lss_orderid.Id);
            var contact = contactProvider.GetContact(order.CustomerId.Id);

            StringBuilder postRequest = new StringBuilder();

            payment.lss_vendortxcode = CreateVendorTransactionCode(order.Name);

            // Save the record so the payment has a name
            payment.lss_name = "Payment: " + payment.lss_vendortxcode + " - £" + payment.lss_amount.Value.ToString("#.##");
            paymentProvider.SavePayment(payment);

            // Check the Contact has the relevant details for a sucessful transaction
            if (String.IsNullOrEmpty(contact.FirstName) ||
                String.IsNullOrEmpty(contact.LastName) ||
                String.IsNullOrEmpty(contact.Address1_Line1) ||
                String.IsNullOrEmpty(contact.Address1_City) ||
                String.IsNullOrEmpty(contact.Address1_PostalCode))
            {
                return("INVALIDCONTACT");
            }

            postRequest.Append("VPSProtocol=2.23&TxType=PAYMENT&Vendor=");
            postRequest.Append(configuration.VendorName);
            postRequest.Append("&VendorTxCode=" + payment.lss_vendortxcode);
            postRequest.Append("&Amount=" + payment.lss_amount.Value.ToString("#.##"));
            postRequest.Append("&Currency=GBP");
            postRequest.Append("&Description=" + order.Name);
            postRequest.Append("&NotificationURL=" + configuration.NotificationUrl);
            postRequest.Append(BuildContactDetails(contact));
            postRequest.Append("&CustomerEMail=" + (String.IsNullOrEmpty(contact.EMailAddress1) ? String.Empty : contact.EMailAddress1.Trim()));
            postRequest.Append(BuildBasket(connector, orderProvider, order));
            postRequest.Append("&AllowGiftAid=0&ApplyAVSCV2=0&Apply3DSecure=0&Profile=NORMAL&AccountType=M");

            return(postRequest.ToString());
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            ConfigurationProvider configuration = new ConfigurationProvider();

            Guid paymentId;

            if (!Guid.TryParse(Request["paymentid"], out paymentId))
            {
                Response.Redirect(configuration.OrderFailedUrl + "?errorcode=" + (int)ErrorCodes.PaymentIdMissing);
            }

            CrmConnector    connector       = new CrmConnector(Properties.Settings.Default.ConnectionString);
            PaymentProvider paymentProvider = new PaymentProvider(connector);
            var             payment         = paymentProvider.GetPayment(paymentId);

            if (payment == null)
            {
                throw new ArgumentException("PaymentId is incorrect.");
            }

            try
            {
                WebRequest request = WebRequest.Create(configuration.SagePayWebServiceAddress);
                request.Method = "Post";

                string postData = CreatePostRequest(configuration, connector, payment);

                // If the postData in invalid contact then ensure the Contact has the right details.
                if (postData == "INVALIDCONTACT")
                {
                    Response.Redirect(configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.InvalidContactDetails).ToString());
                }

                byte[] byteArray = Encoding.UTF8.GetBytes(postData);

                request.ContentType   = "application/x-www-form-urlencoded";
                request.ContentLength = byteArray.Length;

                Stream dataStream = request.GetRequestStream();
                dataStream.Write(byteArray, 0, byteArray.Length);
                dataStream.Close();

                WebResponse response = request.GetResponse();
                dataStream = response.GetResponseStream();

                StreamReader reader = new StreamReader(dataStream);
                string       responseFromSagePay = reader.ReadToEnd();

                reader.Close();
                dataStream.Close();
                response.Close();

                string nextUrl = CheckSagePayResponse(configuration, connector, payment, responseFromSagePay);
                if (!String.IsNullOrEmpty(nextUrl))
                {
                    Response.Redirect(nextUrl);
                }
            }
            catch (WebException webException)
            {
                string     error = String.Empty;
                ErrorCodes errorCode;
                if (webException.Status == WebExceptionStatus.NameResolutionFailure)
                {
                    errorCode = ErrorCodes.NameResolutionFailure;
                    error     = @"Your server was unable to register this transaction with Sage Pay.
Check that you do not have a firewall restricting the POST and 
that your server can correctly resolve the address " + configuration.SagePayWebServiceAddress;
                }
                else
                {
                    errorCode = ErrorCodes.GeneralError;
                    error     = @"An Error has occurred whilst trying to register this transaction.<BR>
The Error is: " + webException;
                }

                payment.lss_responsestatus       = errorCode.ToString();
                payment.lss_responsestatusdetail = (error.Length > 2000) ? error.Substring(0, 2000) : error;
                payment.lss_paystatus.Value      = (int)PaymentProvider.PaymentStatus.Failed;
                paymentProvider.SavePayment(payment);

                Response.Redirect(configuration.OrderFailedUrl + "?errorcode=" + ((int)errorCode).ToString());
            }
        }
        /// <summary>
        /// Checks the response from SagePay
        /// </summary>
        /// <param name="configuration">The configuration.</param>
        /// <param name="connector">The connector.</param>
        /// <param name="payment">The payment.</param>
        /// <param name="responseFromSagePay">The response from sage pay.</param>
        /// <returns>
        /// Returns the next url to go to if the status is ok
        /// </returns>
        private string CheckSagePayResponse(ConfigurationProvider configuration, CrmConnector connector, lss_payment payment, string responseFromSagePay)
        {
            string nextUrl = String.Empty;

            string[] responses = responseFromSagePay.Split(new string[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string response in responses)
            {
                int splitIndex = response.IndexOf("=");
                if (splitIndex != -1)
                {
                    string name  = response.Substring(0, splitIndex);
                    string value = response.Substring(splitIndex + 1);

                    switch (name)
                    {
                    case "VPSProtocol":
                        payment.lss_vpsprotocol = value;
                        break;

                    case "Status":
                        payment.lss_responsestatus = value;
                        break;

                    case "StatusDetail":
                        payment.lss_responsestatusdetail = value;
                        break;

                    case "VPSTxId":
                        payment.lss_vpstxid = value;
                        break;

                    case "SecurityKey":
                        payment.lss_securitykey = value;
                        break;

                    case "NextURL":
                        payment.lss_nexturl = value;
                        break;
                    }
                }
            }

            if (payment.lss_responsestatus != null)
            {
                switch (payment.lss_responsestatus)
                {
                case "OK":
                    // If the status is OK then we can return the Next Url
                    nextUrl = payment.lss_nexturl == null ? String.Empty : payment.lss_nexturl;
                    break;

                case "MALFORMED":
                    payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Failed;
                    nextUrl = configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.MalformedPost).ToString();
                    break;

                case "INVALID":
                    payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Failed;
                    nextUrl = configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.InvalidPost).ToString();
                    break;

                default:
                    payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Failed;
                    nextUrl = configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.GeneralPostError).ToString();
                    break;
                }
            }

            PaymentProvider paymentProvider = new PaymentProvider(connector);

            paymentProvider.SavePayment(payment);

            return(nextUrl);
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            string vendorTxCode = Request.Form["VendorTxCode"];
            string vpstxId      = Request.Form["VPSTxId"];

            ConfigurationProvider configuration   = new ConfigurationProvider();
            CrmConnector          connector       = new CrmConnector(Properties.Settings.Default.ConnectionString);
            PaymentProvider       paymentProvider = new PaymentProvider(connector);

            // Check we have a payment for the transaction code and id
            var payment = paymentProvider.GetPayment(vendorTxCode, vpstxId);

            if (payment == null)
            {
                HandleError(configuration, ErrorCodes.TransactionNotFound);
                return;
            }
            else
            {
                ReadFormFields(payment);

                // Before we check that the signatures are correct, we should just check if the user cancelled the transaction or an error occurred
                string      returnStatus = String.Empty;
                string      redirectURL  = String.Empty;
                StatusCodes statusCode   = HandleStatus(payment.lss_notificationstatus);
                switch (statusCode)
                {
                case StatusCodes.Abort:
                    payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Unpaid;
                    returnStatus = "OK";
                    redirectURL  = configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.Aborted).ToString();
                    break;

                case StatusCodes.Unspecified:
                    payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Failed;
                    returnStatus = "OK";
                    redirectURL  = configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.UnspecifiedPaymentError).ToString();
                    break;

                case StatusCodes.Error:
                    payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Failed;
                    returnStatus = "INVALID";
                    redirectURL  = configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.PaymentError).ToString();
                    break;
                }

                // Return the status if one has occurred already
                if (!String.IsNullOrEmpty(returnStatus))
                {
                    paymentProvider.SavePayment(payment);
                    Response.Write("Status=" + returnStatus + System.Environment.NewLine);
                    Response.Write("RedirectURL=" + redirectURL);
                    Response.End();
                    return;
                }

                // Rebuild the post message, so we can then hash it with the security key, and then check against VPSSignature
                string postMessage = vpstxId + vendorTxCode + payment.lss_notificationstatus + payment.lss_txauthno.ToString() + configuration.VendorName + payment.lss_avscv2 + payment.lss_securitykey +
                                     payment.lss_addressresult + payment.lss_postcoderesult + payment.lss_cv2result + payment.lss_giftaid + payment.lss_securestatus3d + payment.lss_cavv +
                                     payment.lss_addressstatus + payment.lss_payerstatus + payment.lss_cardtype + payment.lss_last4digits;

                string hashedPostMessage = FormsAuthentication.HashPasswordForStoringInConfigFile(postMessage, "MD5");
                if (payment.lss_vpssignature != hashedPostMessage)
                {
                    // The signatures don't match up, so this could indicate the order has been tampered with.
                    payment.lss_paystatus.Value          = (int)PaymentProvider.PaymentStatus.Failed;
                    payment.lss_notificationstatus       = "INVALID";
                    payment.lss_notificationstatusdetail = "TAMPER WARNING! Signatures do not match for this Payment.  The Payment was Cancelled.";
                    paymentProvider.SavePayment(payment);

                    HandleError(configuration, ErrorCodes.UnmatchedSignatures);
                    return;
                }
                else
                {
                    Response.Clear();
                    Response.ContentType = "text/plain";

                    // Signatures match, so this is Good :)  Now let's find out what actually happened
                    switch (statusCode)
                    {
                    case StatusCodes.Ok:
                    case StatusCodes.Authenticated:
                    case StatusCodes.Registered:
                        payment.lss_datepaid        = DateTime.Now;
                        payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Successful;
                        Response.Write("Status=OK" + System.Environment.NewLine);
                        Response.Write("RedirectURL=" + configuration.OrderSuccessfulUrl);
                        break;

                    case StatusCodes.Abort:
                        payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Failed;
                        Response.Write("Status=OK" + System.Environment.NewLine);
                        Response.Write("RedirectURL=" + configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.Aborted).ToString());
                        break;

                    case StatusCodes.NotAuthed:
                        payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Declined;
                        Response.Write("Status=OK" + System.Environment.NewLine);
                        Response.Write("RedirectURL=" + configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.NotAuthorised).ToString());
                        break;

                    case StatusCodes.Rejected:
                        payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Rejected;
                        Response.Write("Status=OK" + System.Environment.NewLine);
                        Response.Write("RedirectURL=" + configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.Rejected).ToString());
                        break;

                    case StatusCodes.Unspecified:
                        payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Failed;
                        Response.Write("Status=OK" + System.Environment.NewLine);
                        Response.Write("RedirectURL=" + configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.UnspecifiedPaymentError).ToString());
                        break;

                    case StatusCodes.Error:
                        payment.lss_paystatus.Value = (int)PaymentProvider.PaymentStatus.Failed;
                        Response.Write("Status=INVALID" + System.Environment.NewLine);
                        Response.Write("RedirectURL=" + configuration.OrderFailedUrl + "?errorcode=" + ((int)ErrorCodes.PaymentError).ToString());
                        break;
                    }

                    paymentProvider.SavePayment(payment);

                    Response.End();
                }
            }
        }