Skip to content

Commit

Permalink
Issue thephpleague#89 distinguish token and cardReference by whether …
Browse files Browse the repository at this point in the history
…they are saved or not.

Documentation updated to show more clearly how this is used. Needs a
bit of a tidy-up, but I'm pushing to get some eyes on it.
  • Loading branch information
judgej committed Sep 3, 2017
1 parent ba9dae4 commit 1f823c2
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 24 deletions.
114 changes: 110 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ Table of Contents
=================

* [Omnipay: Sage Pay](#omnipay-sage-pay)
* [Table of Contents](#table-of-contents)
* [Installation](#installation)
* [Basic Usage](#basic-usage)
* [Supported Methods](#supported-methods)
* [Sage Pay Direct Methods:](#sage-pay-direct-methods)
* [Direct createCard()](#direct-createcard)
* [Sage Pay Server Methods:](#sage-pay-server-methods)
* [Server createCard()](#server-createcard)
* [Sage Pay Shared Methods (for both Direct and Server):](#sage-pay-shared-methods-for-both-direct-and-server)
* [Token Billing](#token-billing)
* [Generating a Token or CardReference](#generating-a-token-or-cardreference)
* [Using a Token or CardRererence](#using-a-token-or-cardrererence)
* [Basket format](#basket-format)
* [Sage Pay Server Notification Handler](#sage-pay-server-notification-handler)
* [Support](#support)
Expand Down Expand Up @@ -58,15 +63,65 @@ repository.

* authorize() - with completeAuthorize for 3D Secure and PayPal redirect
* purchase() - with completeAuthorize for 3D Secure and PayPal redirect
* createCard() - explicit creation of a cardReference
* createCard() - explicit "standalone" creation of a cardReference or token
* deleteCard() - remove a card cardReference from the accout

### Direct createCard()

This will create a card reference with no authorisation.
If you want to authorise an amount on the card *and* get a cardReference
for repeated use of the card, then use the `authorize()` method with the
`createToken` flag set.

Sample code using Sage Pay Direct to create a card reference:

```php
use Omnipay\Omnipay;
use Omnipay\CreditCard;

$gateway = OmniPay::create('SagePay\Direct');

$gateway->setVendor('your-vendor-code');
$gateway->setTestMode(true); // For test account

// The minimal card details to save to the gateway.
// The CVV is optional. However it can be supplied later when
// transactions are being initiated, though that is not advised
// as the CVV will need to go through your site to be added to
// the transaction.

$card = new CreditCard([
'firstName' => 'Joe',
'lastName' => 'Bloggs',
'number' => '4929000000006',
'expiryMonth' => '12',
'expiryYear' => '2018',
'cvv' => '123',
]);

// Send the request.
$request = $gateway->createCard([
'currency' => 'GBP',
'card' => $card,
]);

$response = $request->send();

// There will be no need for any redirect (e.g. 3D Secure), since no
// authorisation is being done.
if ($response->isSuccessful()) {
$cardReference = $response->getCardReference();;
// or if you prefer to treat it as a single-use token:
$token = $response->getToken();
}
```

## Sage Pay Server Methods:

* authorize()
* purchase()
* acceptNotification() - Notification Handler for authorize, purchase and explicit cardReference registration
* createCard() - explicit creation of a cardReference
* createCard() - explicit "standalone" creation of a cardReference or token
* deleteCard() - remove a card cardReference from the accout

### Server createCard()
Expand All @@ -85,6 +140,7 @@ $gateway->setVendor('your-vendor-code');
$gateway->setTestMode(true); // For test account

// The transaction ID is used to store the result in the notify callback.
// Create a storage record for this transaction now.
$transactionId = {create a unique transaction id};

$request = $gateway->createCard([
Expand All @@ -100,6 +156,11 @@ if ($response->isSuccessful()) {
} elseif ($response->isRedirect()) {
// Redirect to offsite payment gateway to capture the users credit card
// details. Note that no address details are needed, nor are they captured.

// Here add the $response->getTransactionReference() to the stored transaction,
// as the notification handler will need it for checking the signature of the
// notification it receives.

$response->redirect();
} else {
$reason = $response->getMessage();
Expand All @@ -109,8 +170,8 @@ if ($response->isSuccessful()) {
At this point the user will be redirected to enter their CC details.
The details will be held by the gateway and a token sent to the notification
handler, along with the `transactionId`.
The notification handler needs to store the `cardReference` referenced by the
`transactionId` then acknowledge the acceptance and provide a final URL the user
The notification handler needs to store the `cardReference` or `token` referenced by
the `transactionId` then acknowledge the acceptance and provide a final URL the user
is taken to.

## Sage Pay Shared Methods (for both Direct and Server):
Expand All @@ -122,6 +183,51 @@ is taken to.
* repeatPurchase() - new purchase based on past transaction
* void() - void a purchase

# Token Billing

Sage Pay Server and Direct support the ability to store a credit card detail on
the gateway, referenced by a token, for later use or reuse.
The token can be single-use, or permanently stored (until its expiry date or
explicit removal).

Whether a token is single-use or permanent, depends on how it is *used*, and not
on how it is generated. This is important to understand, and is explained in more
detail below.

## Generating a Token or CardReference

A token can be generated explicitly, with no authorisation, or it can be generated
as a part of a transaction:

* `$gateway->createCard()` - message used to create a card token explicitly.
* `$request->CreateToken()` - transaction option to generate a token with a transaction.

The transaction response (or notification request for Sage Pay Server) will provide
the generated token. This is accessed using:

* `$response->getToken()` or
* `$response->getCardReference()`

These are equivalent since there is no difference in the way tokens or cardRererences
are generated.

## Using a Token or CardRererence

To use a token, you must leave the credit card details blank in the `CreditCard` object.
To use the token as a single-use token, add it to the transaction request as a token:

`request->setToken($saved_token);`

Once authorised, this token will be deleted by the gateway and so cannot be used again.
Note that if the transaction is not authorised, then the token will remain.
You should then delete the token explicitly to make sure it does not remain in the gateway.

To use the token as a permanent cardReference, add it to the transaction request as a token:

`request->setCardReference($saved_token);`

This token will remain active on the gateway whether this transaction is authorised or not.

# Basket format

Sagepay currently supports two different formats for sending cart/item information to them:
Expand Down
13 changes: 10 additions & 3 deletions src/Message/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ abstract class AbstractRequest extends \Omnipay\Common\Message\AbstractRequest
const APPLY_3DSECURE_NONE = 2;
const APPLY_3DSECURE_AUTH = 3;

const STORE_TOKEN_YES = 1;
const STORE_TOKEN_NO = 0;

protected $liveEndpoint = 'https://live.sagepay.com/gateway/service';
protected $testEndpoint = 'https://test.sagepay.com/gateway/service';

Expand Down Expand Up @@ -209,15 +212,19 @@ public function setCreateToken($createToken)

public function getCreateToken()
{
return $this->parameters->get('createToken', 0);
return $this->getParameter('createToken');
}

/**
* An optional flag to indicate if you wish to continue to store the Token in the SagePay
* token database for future use.
*
* Note: this is just an override method. It is best to leave this unset, and
* use either setToken or setCardReference to derive the StoreToken flag
* automatically.
*
* @param bool|int $storeToken 0 = The Token will be deleted from the SagePay database if
* authorised by the bank (default).
* authorised by the bank.
* 1 = Continue to store the Token in the SagePay database for future use.
*/
public function setStoreToken($storeToken)
Expand All @@ -229,7 +236,7 @@ public function setStoreToken($storeToken)

public function getStoreToken()
{
return $this->parameters->get('storeToken', 0);
return $this->getParameter('storeToken');
}

protected function createResponse($data)
Expand Down
21 changes: 18 additions & 3 deletions src/Message/DirectAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,27 @@ protected function getBaseAuthorizeData()

// Creating a token should not be permissible at
// the same time as using a token.
if (! $data['CreateToken'] && $this->getToken()) {
if (! $data['CreateToken'] && ($this->getToken() || $this->getCardReference())) {
// If a token has been supplied, and we are NOT asking to generate
// a new token here, then use this token and optionally store it
// again for further use.
$data['Token'] = $this->getToken();
$data['StoreToken'] = $this->getStoreToken();

$data['Token'] = $this->getToken() ?: $this->getCardReference();

// If we don't have a StoreToken override, then set it according to
// whether we are dealing with a token or a cardReference.

$storeToken = $this->getStoreToken();

if ($storeToken === null) {
// If we are using the token as a cardReference, then keep it stored
// after this transaction for future use.
$storeToken = $this->getCardReference()
? static::STORE_TOKEN_YES
: static::STORE_TOKEN_NO;
}

$data['StoreToken'] = $storeToken;
}

if ($this->getReferrerId()) {
Expand Down
16 changes: 12 additions & 4 deletions src/Message/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ public function getMessage()
return isset($this->data['StatusDetail']) ? $this->data['StatusDetail'] : null;
}

/**
* @return string URL to 3D Secure endpoint.
*/
public function getRedirectUrl()
{
if ($this->isRedirect()) {
Expand All @@ -110,6 +113,10 @@ public function getRedirectMethod()
return 'POST';
}

/**
* The usual reason for a redirect is for a 3D Secure check.
* @return array 3D Secure POST data.
*/
public function getRedirectData()
{
if ($this->isRedirect()) {
Expand All @@ -122,20 +129,21 @@ public function getRedirectData()
}

/**
* Alias of getToken()
* Get the cardReference generated when creating a card reference
* during an authorisation or payment, or as an explicit request.
*/
public function getCardReference()
{
return $this->getDataItem('Token', null);
return $this->getToken();
}

/**
* Alias of getCardReference()
* Deprecated (see issue #89)
* Both single-use tokens and permanent card references are stored and
* acccessed in the same way.
*/
public function getToken()
{
return $this->getCardReference();
return $this->getDataItem('Token', null);
}
}
8 changes: 5 additions & 3 deletions src/Message/ServerNotifyRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ protected function getDataItem($name)
public function buildSignature()
{
// Re-create the VPSSignature
if ($this->getTxType() == 'TOKEN') {
if ($this->getTxType() === 'TOKEN') {
$signature_data = array(
// For some bizarre reason, the VPSTxId is hashed at the Sage Pay gateway
// without its curly crackets, so we must do the same to validate the hash.
Expand Down Expand Up @@ -162,10 +162,12 @@ public function buildSignature()

/**
* Check whether the ignature is valid.
*
* @return bool True if the signature is valid; false otherwise.
*/
public function isValid()
{
return $this->getSignature() == $this->buildSignature();
return $this->getSignature() === $this->buildSignature();
}

/**
Expand Down Expand Up @@ -321,7 +323,7 @@ public function getTxType()
public function getTransactionStatus()
{
// If the signature check fails, then all bets are off - the POST cannot be trusted.
if (!$this->isValid()) {
if (! $this->isValid()) {
return static::STATUS_FAILED;
}

Expand Down
28 changes: 22 additions & 6 deletions src/Message/ServerNotifyResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class ServerNotifyResponse extends Response

/**
* Whether to exit immediately on responding.
* For 3.0 it will be worth switching this off by default to
* provide more control to the application.
*/
protected $exit_on_response = true;

Expand Down Expand Up @@ -141,24 +143,38 @@ public function setExitOnResponse($value)
}

/**
* Respond to SagePay confirming or rejecting the notification.
* Construct the response body.
*
* @param string The status to send to Sage Pay, one of static::RESPONSE_STATUS_*
* @param string URL to forward the customer to.
* @param string Optional human readable reasons for this response.
* @param string Optional human readable reason for this response.
*/
public function sendResponse($status, $nextUrl, $detail = null)
public function getResponseBody($status, $nextUrl, $detail = null)
{
$message = array(
$body = array(
'Status=' . $status,
'RedirectUrl=' . $nextUrl,
);

if ($detail !== null) {
$message[] = 'StatusDetail=' . $detail;
$body[] = 'StatusDetail=' . $detail;
}

echo implode(static::LINE_SEP, $message);
return implode(static::LINE_SEP, $body);
}

/**
* Respond to SagePay confirming or rejecting the notification.
*
* @param string The status to send to Sage Pay, one of static::RESPONSE_STATUS_*
* @param string URL to forward the customer to.
* @param string Optional human readable reason for this response.
*/
public function sendResponse($status, $nextUrl, $detail = null)
{
$message = $this->getResponseBody($status, $nextUrl, $detail);

echo $message;

if ($this->exit_on_response) {
exit;
Expand Down
Loading

0 comments on commit 1f823c2

Please sign in to comment.