academe / SagePay-Integration

HTTP Messages for the Sage Pay REST (Pi) gateway.
GNU General Public License v3.0
9 stars 5 forks source link

Handle 3D Secure #10

Closed judgej closed 8 years ago

judgej commented 8 years ago

If a 3D Secure redirect is needed, SagePay will return this response to the transaction request:

array(3) {
  ["statusCode"]=>
  string(4) "2007"
  ["statusDetail"]=>
  string(70) "Please redirect your customer to the ACSURL, passing the MD and PaReq."
  ["status"]=>
  string(6) "3DAuth"
}

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.

judgej commented 8 years ago

The ACSURL and the MD are supplied in neither the response body nor the headers.

judgej commented 8 years ago

Word from SagePay is that 3D Secure is not implemented in the API v1. It will be rolled out in a later version.

judgej commented 8 years ago

3D Secure is planned for the very next release of this API.

judgej commented 8 years ago

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.

judgej commented 8 years ago

New API release today:

28-10-2015 BETA Include 3-D Secure object in transaction response.

So we have a new message to implement.

judgej commented 8 years ago

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

judgej commented 8 years ago

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).

judgej commented 8 years ago

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.

judgej commented 8 years ago

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:

These 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).

judgej commented 8 years ago

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.

judgej commented 8 years ago

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).

judgej commented 8 years ago

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.

judgej commented 8 years ago

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).

judgej commented 8 years ago

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.

judgej commented 8 years ago

See also Issue #22 - identify what the MDX is for and whether we should be supporting it.

judgej commented 8 years ago

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.

judgej commented 8 years ago

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.

judgej commented 8 years ago

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.

judgej commented 8 years ago

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.

judgej commented 8 years ago

This is just about done. Need some examples on the README to show how it works:

  1. Transaction response containing PaReq.
  2. Redirect to bank.
  3. Bank POST back to merchant site with Secure3DAcsResponse containing PaRes.
  4. Merchant site sending the PaRes to Sage Pay in a Secure3DRequest message.
  5. Merchant site getting back Secure3DResponse with the 3D Secure result; or
  6. Merchant site making a request with the TransactionRequest and getting back the TransactionResponse with the final response (including the Secure3DResponse child resource embedded.
judgej commented 8 years ago

Supported and now documented.