diff --git a/README.md b/README.md index 853cdf2..554ea43 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,15 @@ 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. +If created explicitly, then a CVV can be provided, and that will be stored against the token +until the token is first used to make a payment. If reused after the first payment, then +a CVV must be supplied each time (if your rules require the CVV to be checked). +If using Sage Pay Server, then the user will be prompted for a CVV on subsequent uses of +the cardReference. + +If creating a token or cardReference with a transaction, then the CVV will never be +stored against the token. + The transaction response (or notification request for Sage Pay Server) will provide the generated token. This is accessed using: @@ -247,7 +256,8 @@ 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 a token with Sage Pay Direct, you must leave the credit card details blank in +the `CreditCard` object. Sage Pay Server does not use the credit card details anyway. To use the token as a single-use token, add it to the transaction request as a token: `request->setToken($saved_token);` diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php index ca51e32..9be38ea 100644 --- a/src/Message/AbstractRequest.php +++ b/src/Message/AbstractRequest.php @@ -182,6 +182,9 @@ public function setApply3DSecure($value) return $this->setParameter('apply3DSecure', $value); } + /** + * Basic authorisation, rtransaction type and protocol version. + */ protected function getBaseData() { $data = array(); diff --git a/src/Message/DirectAuthorizeRequest.php b/src/Message/DirectAuthorizeRequest.php index 2f8684f..b8aa750 100644 --- a/src/Message/DirectAuthorizeRequest.php +++ b/src/Message/DirectAuthorizeRequest.php @@ -13,48 +13,29 @@ class DirectAuthorizeRequest extends AbstractRequest 'diners_club' => 'dc' ); + /** + * The required fields concerning what is being authorised and who + * it is being authorised for. + */ protected function getBaseAuthorizeData() { $this->validate('amount', 'card', 'transactionId'); $card = $this->getCard(); + // Start with the authorisation and API version details. $data = $this->getBaseData(); + $data['Description'] = $this->getDescription(); $data['Amount'] = $this->getAmount(); $data['Currency'] = $this->getCurrency(); + $data['VendorData'] = $this->getVendorData(); $data['VendorTxCode'] = $this->getTransactionId(); $data['ClientIPAddress'] = $this->getClientIp(); + $data['ApplyAVSCV2'] = $this->getApplyAVSCV2() ?: 0; $data['Apply3DSecure'] = $this->getApply3DSecure() ?: 0; - $data['CreateToken'] = $this->getCreateToken(); - - // Creating a token should not be permissible at - // the same time as using a token. - 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() ?: $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()) { $data['ReferrerID'] = $this->getReferrerId(); } @@ -126,32 +107,91 @@ public function getCardholderName() return $this->getParameter('cardholderName'); } + /** + * If a token or cardReference is being used, then include the details + * of the token in the data. + */ + public function getTokenData($data = array()) + { + // Are there token details to add? + if ($this->getToken() || $this->getCardReference()) { + // A card token or reference has been provided. + $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. + // Overriding the default token storage flag is for legacy support. + + $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; + } + + return $data; + } + + /** + * Add the credit card or token details to the data. + */ public function getData() { $data = $this->getBaseAuthorizeData(); - $this->getCard()->validate(); - if ($this->getCardholderName()) { - $data['CardHolder'] = $this->getCardholderName(); + if ($this->getToken() || $this->getCardReference()) { + // If using a token, then set that data. + $data = $this->getTokenData($data); } else { - $data['CardHolder'] = $this->getCard()->getName(); - } + // Otherwise, a credit card has to have been provided. + $this->getCard()->validate(); - // Card number should not be provided if token is being provided instead - if (!$this->getToken()) { - $data['CardNumber'] = $this->getCard()->getNumber(); - } + if ($this->getCardholderName()) { + $data['CardHolder'] = $this->getCardholderName(); + } else { + $data['CardHolder'] = $this->getCard()->getName(); + } + + // Card number should not be provided if token is being provided instead + if (! $this->getToken()) { + $data['CardNumber'] = $this->getCard()->getNumber(); + } + + $data['ExpiryDate'] = $this->getCard()->getExpiryDate('my'); + $data['CardType'] = $this->getCardBrand(); + + if ($this->getCard()->getStartMonth() and $this->getCard()->getStartYear()) { + $data['StartDate'] = $this->getCard()->getStartDate('my'); + } - $data['CV2'] = $this->getCard()->getCvv(); - $data['ExpiryDate'] = $this->getCard()->getExpiryDate('my'); - $data['CardType'] = $this->getCardBrand(); + if ($this->getCard()->getIssueNumber()) { + $data['IssueNumber'] = $this->getCard()->getIssueNumber(); + } + + // If we want the card details to be saved on the gateway as a + // token or card reference, then request for that to be done. + $data['CreateToken'] = $this->getCreateToken(); - if ($this->getCard()->getStartMonth() and $this->getCard()->getStartYear()) { - $data['StartDate'] = $this->getCard()->getStartDate('my'); + if ($this->getCard()->getCvv() !== null) { + $data['CV2'] = $this->getCard()->getCvv(); + } } - if ($this->getCard()->getIssueNumber()) { - $data['IssueNumber'] = $this->getCard()->getIssueNumber(); + // A CVV may be supplied whether using a token or credit card details. + // On *first* use of a token for which a CVV was provided, that CVV will + // be used when making a transaction. The CVV will then be deleted by the + // gateway. For each *resuse* of a cardReference, a new CVV must be provided, + // if the security rules require it. + + if ($this->getCard()->getCvv() !== null) { + $data['CV2'] = $this->getCard()->getCvv(); } return $data; diff --git a/src/Message/ServerAuthorizeRequest.php b/src/Message/ServerAuthorizeRequest.php index 8a3d0da..bcb84fd 100644 --- a/src/Message/ServerAuthorizeRequest.php +++ b/src/Message/ServerAuthorizeRequest.php @@ -8,7 +8,8 @@ class ServerAuthorizeRequest extends DirectAuthorizeRequest { /** - * The returnUrl is supported for legacy applications. + * Add the optional token details to the base data. + * The returnUrl is supported for legacy applications not using the notifyUrl. */ public function getData() { @@ -18,6 +19,11 @@ public function getData() $data = $this->getBaseAuthorizeData(); + // If a token is being used, then include the token data. + // With a valid token or card reference, the user is just asked + // for the CVV and not any remaining card details. + $data = $this->getTokenData($data); + // ReturnUrl is for legacy usage. $data['NotificationURL'] = $this->getNotifyUrl() ?: $this->getReturnUrl(); diff --git a/tests/Message/DirectAuthorizeRequestTest.php b/tests/Message/DirectAuthorizeRequestTest.php index 888786a..6967195 100644 --- a/tests/Message/DirectAuthorizeRequestTest.php +++ b/tests/Message/DirectAuthorizeRequestTest.php @@ -323,6 +323,10 @@ public function testExistingCardReferenceCanBeSet() $this->assertSame(1, $data['StoreToken']); } + /** + * This has been turned on its head: if a token is provided, then that + * takes priority and the "createToken" flag is ignored. + */ public function testExistingTokenCannotBeSetIfCreateTokenIsTrue() { $this->request->setCreateToken(true); @@ -330,8 +334,8 @@ public function testExistingTokenCannotBeSetIfCreateTokenIsTrue() $data = $this->request->getData(); - $this->assertArrayNotHasKey('Token', $data); - $this->assertSame(1, $data['CreateToken']); + $this->assertArrayNotHasKey('CreateToken', $data); + $this->assertSame('{ABCDEF}', $data['Token']); } public function testStoreTokenCanOnlyBeSetIfExistingTokenIsSetInRequest()