Closed judgej closed 8 years ago
The ACSURL and the MD are supplied in neither the response body nor the headers.
Word from SagePay is that 3D Secure is not implemented in the API v1. It will be rolled out in a later version.
3D Secure is planned for the very next release of this API.
3D Secure development seems to be in progress in the API. It is not yet documented, but the redirect URL and PA request token is now being returned when 3D Secure is selected for the transaction.
New API release today:
28-10-2015 BETA Include 3-D Secure object in transaction response.
So we have a new message to implement.
The 3D Secure response returns a "202 Accepted" response, which is not yet listed in the documentation. The W3 says about a 202:
The request has been accepted for processing, but the processing has not been completed.
The reasoning sounds about right, but I'm not sure this was the original intention of a 202 (we'll see if it remains).
A transaction status code of 2007 is returned. This is an old Sage Pay Direct status with message "Please redirect your customer to the ACSURL, passing the MD". The current message in the beta API is:
Please redirect your customer to the ACSURL to complete the 3DS Transaction.
https://github.com/academe/SagePay/blob/master/src/Academe/SagePay/Metadata/error-codes.tsv#L75
Sage Pay returns a 3DSecure object with just one field: status
. It returns other 3D Secure data in separate fields at the top level of the response (e.g. acsUrl
and paReq
).
This seems a little bizarre. I think we should reorganise those field levels a bit so the acsUrl and paReq are also put into the 3DSecure object (aka Secure3D
to comply with naming syntax).
If 3DSecure is enforced, and a 202 is returned, Sage Pay may not return a 3DSecure object at all (it's not in beta at the moment). So we will scan the entire transaction result when creating a 3DSecure object.
The beta API is not returning the MD at present. For the user to enter their 3DSecure code, we need to POST the user to acsUrl
with parameters:
paReq
- go thisMD
- not yet suppliedTermUrl
- the return URL once the 3DSecure code has been enteredThese details can all (except for TermUrl
) be supplied by the Secure3D
object, perhaps with the POST parameters as an array. I'll leave implementation of this for when the API is a little more complete (the docs have been updated today, but I see tweaks still being made to the API response data structures).
Asked Sage what the status of 3DSecure is. It feels like it's kind of 80% there, but the status it not at all clear from the documentation.
Got response: yes, it's 80% there. watch this space over the coming days to see progress, but turn off 3DSecure in the meantime if using this for production (though TBH I wouldn't use this in production until 3DSecure is implemented, as it puts more risk on the merchant - personal opinion).
This form will now take you to the 3DSecure passcode page:
if ($transaction_response->getStatus() == '3DAuth') {
$url = $transaction_response->get3DSecure()->getAcsUrl();
$TermUrl = 'https://example.com/callback/route';
$paRequestFields = $transaction_response->get3DSecure()->getPaRequestFields($txnId, $TermUrl);
echo "<form method='post' action='$url'>";
foreach($paRequestFields as $field_name => $field_value) {
echo "<p>$field_name <input type='text' name='$field_name' value='$field_value' /></p>";
}
echo "<button type='submit' />";
echo "</form>";
}
We can now look at what gets returned to the callback and create appropriate messages for that.
Note the TermUrl must be SSL, otherwise the browser will issue a warning when returning from the SSL ACS.
Example capture of the return result from the ACS (where the user enters their 3DSecure details). This would be the https://example.com/callback/route
controller method.
<?php
include "vendor/autoload.php";
$PaRes = \Academe\SagePayMsg\Message\Secure3DResponse::fromData($_POST);
var_dump($PaRes);
// object(Academe\SagePayMsg\Message\Secure3DResponse)#2 (2) {
// ["MD":protected]=>
// string(5) "36064"
// ["PaRes":protected]=>
// string(1928) "eJylV1lzqkgUfvdXpLyPV...snip...mn+Ov95U8N6zFa"
// }
You would use the MD to get the transaction, then pass details of the transaction and the PaRes to Sage Pay (through an endpoint TBC) to finally complete the transaction (depending on the 3DSecure result and the merchant site policy - 3DSecure may fail, but if the shop wishes to take the risk of accepting it, maybe based on the amount of the transaction, or perhaps not shipping until the money has cleared, then that is its business).
The MD is used to identify the transaction on the callback from the user's 3D Secure password entry. I will guess that this should not be the internal ID of the transaction, but a hash of some kind, stored along with the transaction for easy access. The internal transaction ID is something that should not be exposed to the end user, and the MD is certainly exposed in the callback, as that is POSTed to the merchant site via the user's browser.
If this is correct, then it needs a mention in the documentation.
See also Issue #22 - identify what the MDX is for and whether we should be supporting it.
With a bit more understanding, the Secure3D object is returned only when 3D Secure is finally finished. This either in the initial transaction response if no further 3D Secure action is needed (e.g. it is disabled, or not available) OR in the transaction response after the resulting PaRes and MD is submitted to Sage Pay after the user returns from entering their 3D Secure PIN.
So with this in mind, the Secure3D
class needs to have the ACS URL
and the PA Req
stripped out and put back into the TransactionResponse
class. Only the status will be left.
Okay, the MD - it is not generated by Sage Pay after all. It is a free-format string that can be passed to the ACS URL (where the user is sent to fill out their PIN) and will be returned to the merchant site when the user is brought back (via POST) with the PaRes
. The merchant site can use it how it wishes, but it is optional.
It is recommended that the transactionId
is put in here for reference. Personally, I would rather keep the transactionId
away from end users, and would create a random token for the transaction instead.
Just to clarify the Message\Secure3DResponse
message object: it holds the PaRes
and the optional MD
returned from the remote user's bank directly (after taking the user there).
The Model\Secure3D
contains the 3D Secure final status, either directly from Sage Pay, embedded into the original transaction response OR (now) from a later call to Sage Pay with the PaRes
from the end user's bank.
So we need some re-arrangement of the models and messages, as we have a new message+response for passing the PaRes
to SagePay.
Note that Message\Secure3dResponse
has been moved to Message\Secure3DAcsResponse
, and its constructor parameters have been swapped.
Model\Secure3D
has moved to Message\Secure3DResponse
.
The readme will be updated with an example of sending the PaRes to Sage Pay to get the final 3D Secure status. But for now, it's time for tea.
This is just about done. Need some examples on the README to show how it works:
Secure3DAcsResponse
containing PaRes.PaRes
to Sage Pay in a Secure3DRequest
message.Secure3DResponse
with the 3D Secure result; orTransactionRequest
and getting back the TransactionResponse
with the final response (including the Secure3DResponse
child resource embedded.Supported and now documented.
If a 3D Secure redirect is needed, SagePay will return this response to the transaction request:
Status code 2007 is not documented for this API (though is listed in SagePay's master list of error codes), and it is not clear where the URL comes from. The HTTP response is 200.