The documentation for Protocol 4.00 could be more helpful. For everyone struggling to get 3DSV2 working, I'd like the basics spelled out by anyone who has managed to get it working.
I'll summarise the flow as I understand it, please everyone, help by correcting where necessary and by adding in any issues such as removing the {} braces from VPSTx_Id.
- Make your request to SagePay to register the transaction, include
ThreeDSNotificationURL which is the url where the ACS will redirect
your customer if successful, or not.
If the response from SagePay is 3DAUTH, build a form to show to the
customer that contains an iFrame with the required fields "acsUrl"
"creq" and "threeDSSessionData", fields filled in from the SagePay
response. (Page 16 in the Protocal 4 guidelines). Note *
The documentation says to make this an auto-submit form, but it may
be better to inform the customer why they are authenticating to
their bank and get them to click a button to proceed. YMMV
- Customer fills in the authentication form in the iFrame.
- Card issuing bank sends back successful or not response to your
ThreeDSNotificationURL
- threeDSSessionData or (MD) and CRes or (PARes) are sent back in this
response depending on 3DSv2 or (3DSv1) responded.
- Your ThreeDSNotificationURL code must then post to the SagePay
callback page VPSTxId and Cres or MD and PARes again depending on
3DSv2 or 3DSv1
- SagePay will request card authorisation if 3DS authentication was
successful.
- SagePay then responds with VPSTxId and 10 digit SecurityKey if
authorisation was approved.
- Display completion page and inform customer of successful or not
successful transaction.
- Some clarification needed, 'In a fallback scenario, The Sage Pay
MPI will perform a 3DSv1 enrolment request,' what is the best way
to determine from the response that 3DSV1 is being used, i.e. the
iFrame form must contain PaReq, MD and TermUrl instead of creq and
threeDSSessionData as per point 2.) above.
When testing put CHALLENGE in the card holder field to test the 3D aspects.
Are there any issues to be aware of when setting up your account for 3DSv2 on MySagePay?
Sorry for the long post but I'm sure there are a lot of people trying to make this work before the deadline.
Check that the PaRes you send to SagePay has no spaces. I found that some PaRes I got back had spaces. I replace spaces (" ") with a + sign like this:
PaRes = PaRes.Replace(" ", "+")
Then I use the altered value in the URL.
You are talking about 3DSecure version 1, right?
3DSecure version 2 looks for cres (cres in small letters).
Hope this helps.
I think it's v1 as that is in the URLs. This is all new to me. I've seen no mention of V2 anywhere yet ?
There isn't any spaces in the PaRes returned anyway.
WRT "the deadline" mentioned, my understanding of the timeline is as follows:
- On 14th September 2019, all ecommerce transactions need to go through some form of 3-D Secure. So integrating Direct API v3.00 or v4.00 with 3-D Secure would be fine (consumers will get a better experience with v4.00). SagePay also switch on their v4.00 API around this time, which I sort of anticipate might have teething problems.
- Some point in 2020: All transactions should go through 3-D Secure V2 (i.e Direct API v4.00), as card schemes (i.e Visa / MasterCard) will stop supporting 3-D Secure V1. So all merchants that have a Direct integration with SagePay for ecommerce payments will have to upgrade to v4.00
Smitthhy - To test the flow on the SagePay test system where the user has to do something, you have to send through a CardHolder
of CHALLENGE
(I can't reply to the other post because I don't have a SO reputation of 50 yet - sigh).
Well written Smitthhy. I do agree that the documentation could be more straight forward. Did my head in the past weeks.
To your 1) you need to post all required fields mentioned in the documentation.
To 8) you get more data back from SagePay. Looks like below:
VPSProtocol=4.00
Status=OK
StatusDetail=0000 : The Authorisation was Successful.
VPSTxId={1B19CB3F-E553-0E69-CFD5-6D75B53753C1}
SecurityKey=UAW4ZETUK7
TxAuthNo=2261559
AVSCV2=SECURITY CODE MATCH ONLY
AddressResult=NOTMATCHED
PostCodeResult=NOTMATCHED
CV2Result=MATCHED
3DSecureStatus=OK
CAVV=Q042ZUZRWndDbjAyWHRjYUFkZ2c=
DeclineCode=00
ExpiryDate=1035
BankAuthCode=999778
To 9) You can differentiate between 3DSecure v1 and 3DSecure v2 by checking the StatusDetails you get from SagePay.
3DSv1 returns StatusDetail=2007
3DSv2 returns StatusDetail=2021
I do the Payment Processing in a class file which is called by my Checkout page. The class returns the response to the Checkout page with the details I need. They are different for v1 and v2.
For v1 I return:
return "v1" + Status + "&3DSecureStatus=" + s3DSecureStatus + "&MD=" + sMD + "&ACSURL=" + sACSURL + "&PAReq=" + sPAReq + "&VendorTxCode=" + o.OrderID;
For v2 I return:
return "v2" + Status + "&3DSecureStatus=" + s3DSecureStatus + "&VPSTxId=" + sVPSTxId + "&ACSURL=" + sACSURL + "&CReq=" + sCReq + "&VendorTxCode=" + o.OrderID;
This allows me to act accordingly in the Checkout code.
I redirect v1 to a page with the iFrame. The iFrame loads the ACSURL received in the reponse earlier. This shows straight away the "challenge" window.
This is mainly down to the fact that SagePay accepts a URL with a query string for 3DSv1 and expects a form post for 3DSv2.
v2 redirects to another page with an iFrame. The iFrame loads a page on my website first:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ChallengeiFrame.aspx.cs" Inherits="ac_ChallengeiFrame" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title></title>
</head>
<body>
<div id="content">
<div id="contentHeader">
Your Bank requires Authentication
</div>
<p>
Please click the button below to continue.
</p>
<form action="<%= sACSUrl %>" method="post">
<input type="hidden" name="creq" value="<%= sCReq %>" />
<input type="hidden" name="threeDSSessionData" value="<%= sVPSTxId %>" />
<input type="submit" value="Click to continue" />
</form>
</div>
</body>
</html>
Clicking the button posts to the ACSURL and the challenge window shows.
The customer fills in the password and submits.
The bank responds to the page you assigned for v1 it's the TermUrl and for v2 it's the ThreeDSNotificationURL.
On those pages you post what you need to post and handle the response from SagePay. If all goes well the payment has been received and you can redirect your customer to the Thank you page and finish the order.
Basically, I use 2 sets of code files to handle 3DSv1 and 3DSv2. Like to keep it seperated and once 3DSv1 gets discontinued I simply can delete those files and remove the code blocks in my payment processing page and checkout page. Shouldbe straight forward then.
Hope this helps.
Here the link to the 3DSecure v2 documentation:
DIRECT_Integration_and_Protocol_4_Guidelines.pdf
EDIT
ThreeDSNotificationURL code for the WebRequest and HttpWebResponse:
/////////////////////////////////////////////////////////
//// This is to get the posted results from the bank
/////////////////////////////////////////////////////////
NameValueCollection coll;
coll = Request.Form;
/////////////////////////////////////////////////////////
string sSagePayUrl = "";
if (EcommerceSettings.bLiveTransactions()) //That's just some logic so I can control live or test at one place in a settings class file.
{
sSagePayUrl = "https://live.sagepay.com/gateway/service/direct3dcallback.vsp?";
}
else
{
sSagePayUrl = "https://test.sagepay.com/gateway/service/direct3dcallback.vsp?";
}
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
WebRequest request = WebRequest.Create(sSagePayUrl + "cres=" + coll["cres"] + "&VPSTxId=" + coll["threeDSSessionData"]);
// Get the response.
HttpWebResponse getResponse = (HttpWebResponse)request.GetResponse();
// Display the status.
// Get the stream containing content returned by the server.
Stream dataStream = getResponse.GetResponseStream();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd();
// Cleanup the streams and the response.
reader.Close();
dataStream.Close();
getResponse.Close();
//Check the response and act accordingly
You need to build the request differently for 3DSv1:
Here my way in the TermUrl page:
vendorTxCode = Request.QueryString["VendorTx"];
NameValueCollection coll;
coll = Request.Form;
sMd = coll["MD"];
sPaRes = coll["PaRes"];
////////////////////////////////////////////////////
//// Build post to SagePay
////////////////////////////////////////////////////
StringBuilder sb = new StringBuilder();
if (EcommerceSettings.bLiveTransactions())
{
sb.Append("https://live.sagepay.com/gateway/service/direct3dcallback.vsp?");
}
else
{
sb.Append("https://test.sagepay.com/gateway/service/direct3dcallback.vsp?");
}
sb.Append("VendorTxCode=");
sb.Append(sOrderID);
sb.Append("&MD=");
sb.Append(sMD);
sb.Append("&PaRes=");
sPaRes = sPaRes.Replace(" ", "+");//HttpUtility.UrlEncode(sPaRes);
sb.Append(sPaRes);
string sRequestQuery = sb.ToString();
/////////////////////////////////////////////////////
//// Post To SagePay 3DCallback page
/////////////////////////////////////////////////////
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
WebRequest request = WebRequest.Create(sRequestQuery);
Handle the response as shown for v2.
EDIT
I found that you best do a post for 3DSv1 and not using the URL parameters as some banks do not accept this. Pretty much the same way as for 3DSv2
This never came up on the test server and happened only when live.
It looks like the test server is not providing the various possibilities we encounter once we are live.