/// <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(); } } }