Skip to content

Commit

Permalink
Issue #64 futher 3DS v2 requests, responses and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
judgej committed Aug 31, 2021
1 parent 45251b5 commit abe520c
Show file tree
Hide file tree
Showing 16 changed files with 982 additions and 117 deletions.
226 changes: 178 additions & 48 deletions README.md

Large diffs are not rendered by default.

28 changes: 22 additions & 6 deletions src/Factory/GuzzleFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
* @deprecated use any PSR-17 factory instead
*/

use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;
use GuzzleHttp\Psr7\Utils;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\StreamInterface;

class GuzzleFactory implements RequestFactoryInterface
{
Expand All @@ -26,12 +27,14 @@ class GuzzleFactory implements RequestFactoryInterface
* @param null $body
* @param string $protocolVersion
* @return Request
*
* @deprecated no longer used since the request classes are now native PSR-7 requests
*/
public function jsonRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1')
{
// If we are sending a JSON body, then the recipient needs to know.

$headers['Content-Type'] = 'application/json';
$headers['Content-Type'] = ['application/json'];

// If the body is already a stream or string of some sort, then it is
// assumed to already be a JSON stream.
Expand All @@ -52,8 +55,8 @@ public function jsonRequest($method, $uri, array $headers = [], $body = null, $p
}

/**
* Create a PSR-7 UriInterface object.
* Experimental.
* Create a PSR-7 UriInterface object from a URL string.
*
* @param string $uri
* @return Uri
*/
Expand All @@ -62,10 +65,23 @@ public function uri($uri)
return new Uri($uri);
}

/**
* Create a PSR-7 stream from a string.
*
* @param [type] $stringData
* @return void
*/
public function stream($stringData): StreamInterface
{
return Utils::streamFor($stringData);
}

/**
* Check whether Guzzle PSR-7 is installed so this factory can be used.
* Note: Guzzle does not support everything (e.g. not ServerRequestInterface at this time).
*
* @return bool
* @todo this needs looking at again
*/
public static function isSupported()
{
Expand Down
16 changes: 15 additions & 1 deletion src/Factory/ResponseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,37 +96,51 @@ public static function fromData($data, $httpCode = null)
return Response\Secure3D::fromData($data, $httpCode);
}

// A 3D Secure redirect.
// A 3D Secure v1 redirect.
// Like Secure3D, this one does not have a TransactionType, though shares many fields
// with the abstract transaction response.

if (Helper::dataGet($data, 'statusCode') == '2007') {
if (Helper::dataGet($data, 'status') == AbstractTransaction::STATUS_3DAUTH) {
return Response\Secure3DRedirect::fromData($data, $httpCode);
}
}

// A 3D Secure v2 challenge (aka a redirect)

if (Helper::dataGet($data, 'statusCode') == '2021') {
if (Helper::dataGet($data, 'status') == AbstractTransaction::STATUS_3DAUTH && Helper::dataGet($data, 'cReq')) {
return Response\Secure3Dv2Redirect::fromData($data, $httpCode);
}
}

// A void instruction.

if (Helper::dataGet($data, 'instructionType') == AbstractRequest::INSTRUCTION_TYPE_VOID) {
return Response\VoidInstruction::fromData($data, $httpCode);
}

// An abort instruction.

if (Helper::dataGet($data, 'instructionType') == AbstractRequest::INSTRUCTION_TYPE_ABORT) {
return Response\Abort::fromData($data, $httpCode);
}

// A release instruction.

if (Helper::dataGet($data, 'instructionType') == AbstractRequest::INSTRUCTION_TYPE_RELEASE) {
return Response\Release::fromData($data, $httpCode);
}

// A list of instructions.

if (Helper::dataGet($data, 'instructions') && is_array(Helper::dataGet($data, 'instructions'))) {
return Response\InstructionCollection::fromData($data, $httpCode);
}

// A 204 with an empty body is a quiet accpetance that what was send is successful.
// e.g. returned when a CVV is linked to a card.

if ($httpCode == 204 && empty($data)) {
return Response\NoContent::fromData($data, $httpCode);
}
Expand Down
44 changes: 8 additions & 36 deletions src/Request/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
// use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;

abstract class AbstractRequest extends AbstractMessage implements JsonSerializable
abstract class AbstractRequest extends AbstractMessage implements JsonSerializable, RequestInterface
{
use RequestPsr7Trait;

// Transaction types.
const TRANSACTION_TYPE_PAYMENT = 'Payment';
const TRANSACTION_TYPE_REPEAT = 'Repeat';
Expand Down Expand Up @@ -132,36 +134,20 @@ public function getUrl()
return $this->getEndpoint()->getUrl($this->getResourcePath());
}

/**
* Use this if your transport tool does not do "Basic Auth" out of the box.
* @returns array Headers for the request, usually the authentication headers.
*/
public function getHeaders()
{
return $this->getBasicAuthHeaders();
}

/**
* @returns string The HTTP method that the request will use.
*/
public function getMethod()
{
return $this->method;
}

/**
* The HTTP Basic Auth header, as an array.
* Use this if your transport tool does not do "Basic Auth" out of the box.
*
* @return array
*/
protected function getBasicAuthHeaders()
protected function getAuthHeaders()
{
return [
'Authorization' => 'Basic '
'Authorization' => ['Basic '
. base64_encode(
$this->getAuth()->getIntegrationKey()
. ':' . $this->getAuth()->getIntegrationPassword()
),
)],
];
}

Expand Down Expand Up @@ -233,21 +219,7 @@ public function getFactory($exception = false): RequestFactoryInterface
*/
public function createHttpRequest(): RequestInterface
{
// If the data is protected from accidental serialisation, then
// pull it out through the protected method.

if (method_exists($this, 'jsonSerializePeek')) {
$body = json_encode($this->jsonSerializePeek());
} else {
$body = json_encode($this);
}

return $this->getFactory(true)->JsonRequest(
$this->getMethod(),
$this->getUrl(),
$this->getHeaders(),
$body
);
return $this; // The requests now are now native PSR-7 requests.
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/Request/CreateCardIdentifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,13 @@ public function jsonSerializePeek()
* This request does not use the HTTP Basic Auth, but the temporary session
* key token instead. This is because it will accessible to end users, and
* the secure integration key and password cannot be exposed here.
*
* @return array
*/
public function getHeaders()
public function getAuthHeaders()
{
return [
'Authorization' => 'Bearer ' . $this->sessionKey,
'Authorization' => ['Bearer ' . $this->sessionKey],
];
}
}
44 changes: 37 additions & 7 deletions src/Request/CreatePayment.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
*/

use UnexpectedValueException;
use Academe\Opayo\Pi\Model\Endpoint;
use Academe\Opayo\Pi\Model\Auth;
use Academe\Opayo\Pi\PaymentMethod\PaymentMethodInterface;
use Academe\Opayo\Pi\Model\Endpoint;
use Academe\Opayo\Pi\Money\AmountInterface;
use Academe\Opayo\Pi\Request\Model\PersonInterface;
use Academe\Opayo\Pi\Request\Model\AddressInterface;
use Academe\Opayo\Pi\Request\Model\PaymentMethodInterface;
use Academe\Opayo\Pi\Request\Model\StrongCustomerAuthentication;

class CreatePayment extends AbstractRequest
{
Expand Down Expand Up @@ -51,6 +54,11 @@ class CreatePayment extends AbstractRequest
*/
protected $shippingAddressFieldPrefix = 'shipping';

/**
* @var StrongCustomerAuthentication
*/
protected $strongCustomerAuthentication;

/**
* Valid values for enumerated input types.
*/
Expand Down Expand Up @@ -87,14 +95,14 @@ class CreatePayment extends AbstractRequest
public function __construct(
Endpoint $endpoint,
Auth $auth,
Model\PaymentMethodInterface $paymentMethod,
PaymentMethodInterface $paymentMethod,
$vendorTxCode,
AmountInterface $amount,
$description,
Model\AddressInterface $billingAddress,
Model\PersonInterface $customer,
Model\AddressInterface $shippingAddress = null,
Model\PersonInterface $shippingRecipient = null,
AddressInterface $billingAddress,
PersonInterface $customer,
AddressInterface $shippingAddress = null,
PersonInterface $shippingRecipient = null,
array $options = []
) {
// Access details.
Expand Down Expand Up @@ -164,6 +172,23 @@ public static function getEntryMethods()
return static::constantList('ENTRY_METHOD');
}

public function setStrongCustomerAuthentication(StrongCustomerAuthentication $strongCustomerAuthentication)
{
$this->strongCustomerAuthentication = $strongCustomerAuthentication;
return $this;
}

public function withStrongCustomerAuthentication(StrongCustomerAuthentication $strongCustomerAuthentication)
{
$copy = clone $this;
return $copy->setStrongCustomerAuthentication($strongCustomerAuthentication);
}

public function getStrongCustomerAuthentication()
{
return $this->strongCustomerAuthentication;
}

/**
* @param $giftAid
* @return $this
Expand Down Expand Up @@ -333,6 +358,7 @@ public function jsonSerialize()
// The mandatory fields.
// The amount must be cast to an int. Sending an integer as a string will result in
// a complaint from the remote gateway.

$result = [
'transactionType' => $this->transactionType,
'paymentMethod' => $this->paymentMethod,
Expand Down Expand Up @@ -386,6 +412,10 @@ public function jsonSerialize()
$result['referrerId'] = $this->referrerId;
}

if (! empty($this->strongCustomerAuthentication)) {
$result['strongCustomerAuthentication'] = $this->strongCustomerAuthentication->jsonSerialize();
}

return $result;
}
}
71 changes: 71 additions & 0 deletions src/Request/CreateSecure3Dv2Challenge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace Academe\Opayo\Pi\Request;

/**
* Send the 3DS v2 cres, returned by the ACS, to Opayo,
* to get the final transaction result.
*/

use Academe\Opayo\Pi\Model\Auth;
use Academe\Opayo\Pi\Model\Endpoint;
use Academe\Opayo\Pi\ServerRequest\Secure3DAcs;
use Academe\Opayo\Pi\ServerRequest\Secure3Dv2Notification;

class CreateSecure3Dv2Challenge extends AbstractRequest
{
protected $cRes;
protected $transactionId;

protected $resource_path = ['transactions', '{transactionId}', '3d-secure-challenge'];

/**
* @param Endpoint $endpoint
* @param Auth $auth
* @param string|Secure3Dv2Notification $cRes The Result returned by the user's bank (or their agent)
* @param string $transactionId The ID that Sage Pay gave to the transaction in its intial response
*/
public function __construct(Endpoint $endpoint, Auth $auth, $cRes, $transactionId)
{
$this->setEndpoint($endpoint);
$this->setAuth($auth);

if ($cRes instanceof Secure3Dv2Notification) {
$this->cRes = $cRes->getCRes();
} else {
$this->cRes = $cRes;
}

$this->transactionId = $transactionId;
}

/**
* Get the message body data for serializing.
*
* @return array
*/
public function jsonSerialize()
{
return [
'cRes' => $this->getCRes(),
];
}

/**
* @return Secure3Dv2Notification|string
*/
public function getCRes()
{
return $this->cRes;
}

/**
* Used to construct the URL.
*
* @return string
*/
public function getTransactionId()
{
return $this->transactionId;
}
}
3 changes: 0 additions & 3 deletions src/Request/Model/AbstractCard.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
* Common functionality between all types of request card payment methods.
*/

use Academe\Opayo\Pi\Response\CardIdentifier;
use Academe\Opayo\Pi\Response\SessionKey;

abstract class AbstractCard implements PaymentMethodInterface
{
/**
Expand Down
4 changes: 3 additions & 1 deletion src/Request/Model/PaymentMethodInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* Interface for a payment method request.
*/

interface PaymentMethodInterface extends \JsonSerializable
use JsonSerializable;

interface PaymentMethodInterface extends JsonSerializable
{
}
Loading

0 comments on commit abe520c

Please sign in to comment.