/// <summary>
        /// Processes the callback from Authorize.NET after a customer authorizes a payment request.
        /// </summary>
        /// <param name="payment">The payment.</param>
        /// <remarks>Method communicates with Authorize.NET server and processes the auth_codes from the response.
        /// Normally the callback would do a redirect at the end.
        /// Instead we write the page directly to them.
        /// </remarks>
        public override void ProcessCallback(Payment payment)
        {
            if (payment.PaymentStatus.PaymentStatusId != (int)PaymentStatusCode.PendingAuthorization)
            {
                return;
            }

            var paymentStatus = PaymentStatusCode.Declined;

            var responseCodeParameter       = GetParameter("x_response_code", "\"{0}\" cannot be null or empty");
            var responseReasonCodeParameter = GetParameter("x_response_reason_code", "\"{0}\" cannot be null or empty");
            var responseReasonTextParameter = GetParameter("x_response_reason_text", "\"{0}\" cannot be null or empty");

            string transactParameter = HttpContext.Current.Request["x_trans_id"];

            if (string.IsNullOrEmpty(transactParameter))
            {
                throw new ArgumentException(@"transact must be present in query string.");
            }

            string transact = transactParameter;

            // If payment received OK, proceed with processing
            if (responseCodeParameter == "1")
            {
                const string format = "When using md5 \"{0}\" cannot be null or empty";
                payment["auth_code"] = GetParameter("x_auth_code", format);

                var md5KeyParameter = GetParameter("x_MD5_Hash", format).ToLower();
                var amountParameter = GetParameter("x_amount", format);

                // Configuration values
                string md5Hash        = payment.PaymentMethod.DynamicProperty <string>().Md5Hash;
                string apiLogin       = payment.PaymentMethod.DynamicProperty <string>().ApiLogin;
                bool   instantAcquire = payment.PaymentMethod.DynamicProperty <bool>().InstantAcquire;

                var hashComputer = new AuthorizedotnetMd5Computer();
                if (hashComputer.IsMatch(md5Hash, apiLogin, transact, amountParameter, md5KeyParameter))
                {
                    paymentStatus = instantAcquire ? PaymentStatusCode.Acquired : PaymentStatusCode.Authorized;
                }

                payment.PaymentStatus = PaymentStatus.Get((int)paymentStatus);
                payment.TransactionId = transact;
                ProcessPaymentRequest(new PaymentRequest(payment.PurchaseOrder, payment));
            }

            // Configuration values
            string declineUrl = payment.PaymentMethod.DynamicProperty <string>().DeclineUrl;
            string acceptUrl  = payment.PaymentMethod.DynamicProperty <string>().AcceptUrl;

            HttpContext.Current.Response.Write(paymentStatus == PaymentStatusCode.Declined
                                                                        ? DownloadPageContent(new Uri(_absoluteUrlService.GetAbsoluteUrl(declineUrl))
                                                                                              .AddQueryStringParameter("x_response_reason_code", responseReasonCodeParameter)
                                                                                              .AddQueryStringParameter("x_response_reason_text", responseReasonTextParameter), payment)

                                                                        : DownloadPageContent(new Uri(_absoluteUrlService.GetAbsoluteUrl(acceptUrl)), payment));
        }
        protected override void BuildBody(StringBuilder page, PaymentRequest paymentRequest)
        {
            // Configuration values
            bool   sandboxMode    = paymentRequest.PaymentMethod.DynamicProperty <bool>().SandboxMode;
            bool   testMode       = paymentRequest.PaymentMethod.DynamicProperty <bool>().TestMode;
            bool   instantAcquire = paymentRequest.PaymentMethod.DynamicProperty <bool>().InstantAcquire;
            bool   itemizeReceipt = paymentRequest.PaymentMethod.DynamicProperty <bool>().ItemizeReceipt;
            string apiLogin       = paymentRequest.PaymentMethod.DynamicProperty <string>().ApiLogin;
            string transactionKey = paymentRequest.PaymentMethod.DynamicProperty <string>().TransactionKey;
            string callbackUrl    = paymentRequest.PaymentMethod.DynamicProperty <string>().CallbackUrl;
            string payType        = paymentRequest.PaymentMethod.DynamicProperty <string>().PayType;
            string logoUrl        = paymentRequest.PaymentMethod.DynamicProperty <string>().LogoUrl;

            page.Append(string.Format(@"<form method=""post"" action=""{0}"">", sandboxMode ? "https://test.authorize.net/gateway/transact.dll" : "https://secure.authorize.net/gateway/transact.dll"));

            AddHiddenField(page, "x_login", apiLogin);
            AddHiddenField(page, "x_type", instantAcquire ? "AUTH_CAPTURE" : "AUTH_ONLY");
            AddHiddenField(page, "x_show_form", "PAYMENT_FORM");
            AddHiddenField(page, "x_relay_response", "true");
            AddHiddenField(page, "x_relay_url", _callbackUrl.GetCallbackUrl(callbackUrl, paymentRequest.Payment));
            AddHiddenField(page, "x_delim_data", "FALSE");
            AddHiddenField(page, "x_version", "3.1");
            AddHiddenField(page, "x_method", payType);
            AddHiddenField(page, "x_invoice_num", paymentRequest.Payment.ReferenceId);


            OrderAddress billingAddress = paymentRequest.Payment.PurchaseOrder.BillingAddress;

            AddHiddenField(page, "x_first_name", billingAddress.FirstName);
            AddHiddenField(page, "x_last_name", billingAddress.LastName);
            AddHiddenField(page, "x_address", billingAddress.Line1 + (string.IsNullOrEmpty(billingAddress.Line2) ? "" : (", " + billingAddress.Line2)));
            AddHiddenField(page, "x_city", billingAddress.City);
            AddHiddenField(page, "x_zip", billingAddress.PostalCode);
            AddHiddenField(page, "x_country", billingAddress.Country.Name);
            AddHiddenField(page, "x_email", billingAddress.EmailAddress);
            AddHiddenField(page, "x_phone", billingAddress.PhoneNumber);
            AddHiddenField(page, "x_company", billingAddress.CompanyName);

            if (!string.IsNullOrEmpty(billingAddress.State))
            {
                AddHiddenField(page, "x_state", billingAddress.State);
            }

            if (itemizeReceipt)
            {
                foreach (OrderLine line in paymentRequest.PurchaseOrder.OrderLines)
                {
                    string fullSku = string.Format("{0}{1}", line.Sku, !string.IsNullOrEmpty(line.VariantSku) ? "-" + line.VariantSku : "");
                    AddHiddenField(page, "x_line_item", string.Format("{0}<|>{1}<|>{2}<|>{3}<|>{4}<|>N",
                                                                      HttpUtility.HtmlEncode(MaxThirtyChars(fullSku)),
                                                                      HttpUtility.HtmlEncode(MaxThirtyChars(fullSku)),
                                                                      HttpUtility.HtmlEncode(MaxThirtyChars(line.ProductName)),
                                                                      line.Quantity,
                                                                      (line.Price - line.UnitDiscount).ToInvariantString()));
                }
            }

            AddHiddenField(page, "x_header_html_payment_form", "<style type='text/css'>#imgMerchantLogo{}</style>");

            if (!string.IsNullOrEmpty(logoUrl))
            {
                AddHiddenField(page, "x_logo_url", logoUrl);
            }

            var amount = paymentRequest.Payment.Amount.ToInvariantString();

            AddHiddenField(page, "x_amount", amount);

            AddHiddenField(page, "x_duplicate_window", 28800.ToString());
            AddHiddenField(page, "x_customer_ip", HttpContext.Current.Request.UserHostAddress);

            // To get a transactionsid for authorizations Sandbox mode requires testmode not to be enabled
            if (testMode && !sandboxMode)
            {
                AddHiddenField(page, "x_test_request", "TRUE");
            }

            var    hashComputer = new AuthorizedotnetMd5Computer();
            string sequence     = paymentRequest.Payment.ReferenceId;

            AddHiddenField(page, "x_fp_sequence", sequence);

            TimeSpan timeSinceCreate = (paymentRequest.Payment.Created.ToUniversalTime() - new DateTime(1970, 1, 1));
            string   timestamp       = ((int)timeSinceCreate.TotalSeconds).ToString();

            AddHiddenField(page, "x_fp_timestamp", timestamp);
            AddHiddenField(page, "x_fp_hash", hashComputer.GetPreMd5Key(transactionKey, apiLogin, sequence, timestamp, amount));

            if (Debug)
            {
                AddSubmitButton(page, "ac", "Post it");
            }

            page.Append("</form>");
        }