I want to add 3D secure authentication to credit card payments taken through a website. I am using Sitefinity 8, the e-commerce plug-in and SagePay as the payment processor.
I have created a custom payment provider and can successfully redirect users to the 3D secure page. I am able to perform the second authentication call to SagePay using the SagePay integration kit (i.e. externally from the e-commerce plugin). However, I am struggling to find a way to complete the payment due the way the internal e-commerce classes function.
The difficulty is that the order processor treats the payment as declined if 3D secure authentication is required, but there does not seem to be a way to process the order correctly without using the inbuilt functionality. From my inspections of the ecommerce libraries, there seems to be no way to extend or modify these classes due to internal
modifiers and concrete implementations.
How can I process the order once I have completed authentiation? Has anyone successfully implemented 3D secure with ecommerce? Or know if it is possible?
This is my custom payment provider at the moment.
public class CustomSagePayProvider : SagePayProvider
{
// Rest of code...
protected override IPaymentResponse ParseReponse(string uniqueTransactionCode, string responseXml)
{
var paymentResponse = base.ParseReponse(uniqueTransactionCode, responseXml);
if (Requires3DSecure(paymentResponse))
{
var responseFields = GetResponseAsDictionary(responseXml);
Set3DSecureFields(responseFields, paymentResponse);
}
return paymentResponse;
}
private bool Requires3DSecure(IPaymentResponse paymentResponse)
{
return paymentResponse.GatewayCSCResponse == "OK";
}
private void Set3DSecureFields(Dictionary<string, string> responseFields, IPaymentResponse paymentResponse)
{
var postValues = new NameValueCollection();
postValues.Add("MD", responseFields.ContainsKey("MD") ? responseFields["MD"] : string.Empty);
postValues.Add("PAReq", responseFields.ContainsKey("PAReq") ? responseFields["PAReq"] : string.Empty);
paymentResponse.GatewayRedirectUrlPostValues = postValues;
paymentResponse.GatewayRedirectUrl = responseFields.ContainsKey("ACSURL") ? responseFields["ACSURL"] : string.Empty;
}
}
And this is the 3D secure payment process using the .NET SagePay integration kit
using SagePay.IntegrationKit;
using SagePay.IntegrationKit.Messages;
// Rest of code
var sagePay = new SagePayIntegration();
IThreeDAuthRequest request = new DataObject();
request.Md = Request.Form["MD"];
request.PaRes = Request.Form["PaRes"];
sagePay.RequestQueryString = sagePay.BuildQueryString(request, ProtocolMessage.THREE_D_AUTH_REQUEST, ProtocolVersion.V_223);
sagePay.ResponseQueryString = sagePay.ProcessWebRequestToSagePay("https://test.sagepay.com/gateway/service/direct3dcallback.vsp", sagePay.RequestQueryString);
var result = sagePay.GetDirectPaymentResult(sagePay.ResponseQueryString);
if (result.Status == ResponseStatus.OK)
{
// Process order
}
I was able to add 3D secure authentication by treating the 2nd authentication call as an offsite payment and adding the IOffsitePaymentProcessorProvider
interface to my payment provider class
public class CustomSagePayProvider : SagePayProvider, IOffsitePaymentProcessorProvider
{
// Triggered after payments that have been 3D Secure authenticated
public IPaymentResponse HandleOffsiteNotification(int orderNumber, HttpRequest request, PaymentMethod paymentMethod)
{
var paymentResponse = new PaymentResponse() { IsOffsitePayment = true };
var sagePay = new SagePayIntegration();
var result = sagePay.GetDirectPaymentResult(request.Params.ToString());
if (result.ThreeDSecureStatus == ThreeDSecureStatus.OK)
{
paymentResponse.IsSuccess = true;
paymentResponse.GatewayTransactionID = result.TxAuthNo.ToString();
}
return paymentResponse;
}
public IPaymentResponse HandleOffsiteReturn(int orderNumber, HttpRequest request, PaymentMethod paymentMethod)
{
throw new NotImplementedException();
}
I pass the notification url as a query string parameter in the termUrl
value posted to SagePay when first requesting the authentication
(The url must be /Ecommerce/offsite-payment-notification/
to use the inbuilt offside payment notification handler).
var notificationUrl = request.Url.GetLeftPart(UriPartial.Authority) + "/Ecommerce/offsite-payment-notification/";
In the callback from SagePay after the user completes authentication, I use the SagePay integration kit to process the result of the authentication.
var sagePay = new SagePayIntegration();
IThreeDAuthRequest request = new DataObject();
request.Md = md;
request.PaRes = paRes;
sagePay.RequestQueryString = sagePay.BuildQueryString(request, ProtocolMessage.THREE_D_AUTH_REQUEST, ProtocolVersion.V_223);
sagePay.ResponseQueryString = sagePay.ProcessWebRequestToSagePay("https://test.sagepay.com/gateway/service/direct3dcallback.vsp", sagePay.RequestQueryString);
return sagePay.GetDirectPaymentResult(sagePay.ResponseQueryString);
Finally, I trigger the HandleOffsiteNotification
event by posting to the url www.mysite.com/Ecommerce/offsite-payment-notification/
. This marks the order as complete, updates stock levels and cleans up the user's basket. For simplicity in this example, I am using the SagePay integration kit object to build the query string and post to the url.
var sagePay = new SagePayIntegration();
var ordersManager = OrdersManager.GetManager();
var query = sagePay.ConvertSagePayMessageToNameValueCollection(ProtocolMessage.DIRECT_PAYMENT_RESULT, typeof(IDirectPaymentResult), result, ProtocolVersion.V_223);
// Required Sitefinity fields to trigger HandleOffsiteNotification in IOffsitePaymentProcessorProvider
query.Add("invoice", orderNumber.ToString());
query.Add("provider", ordersManager.Provider.Name);
var queryString = sagePay.BuildQueryString(query);
// Post 3d secure details to this site simulate an offsite payment processor response
sagePay.ProcessWebRequestToSagePay(notificationUrl, queryString);