diff --git a/README.md b/README.md index f4c9d7f8..4fee04b4 100644 --- a/README.md +++ b/README.md @@ -771,8 +771,8 @@ admin panel. * `refund()` * `void()` - void a purchase * `abort()` - abort an authorization before it is captured -* `repeatAuthorize()` - new authorization based on past transaction -* `repeatPurchase()` - new purchase based on past transaction +* `repeatAuthorize()` - new authorization based on past transaction (deprecated, use authorize with cardReference) +* `repeatPurchase()` - new purchase based on past transaction (deprecated, use purchase with cardReference) * `deleteCard()` - remove a cardReference or token from the account ### Repeat Authorize/Purchase @@ -782,10 +782,14 @@ You will need the `transactionReference` of the original transaction. The `transactionReference` will be a JSON string containing the four pieces of information the gateway needs to reuse the transaction. +Although a separate repeatPurchase/repeatAuthorize request exists it is +recommended you use purchase along with the transactionReference for +interoperability with other Omnipay gateways. + ```php -// repeatAuthorize() or repeatPurchase() +// use authorize or purchase to process a repeat payment. -$repeatRequest = $gateway->repeatAuthorize([ +$repeatRequest = $gateway->authorize([ 'transactionReference' => $originalTransactionReference, // or 'securityKey' => $originalSecurityKey, diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php index 31f02334..c52d7a2c 100644 --- a/src/Message/AbstractRequest.php +++ b/src/Message/AbstractRequest.php @@ -648,4 +648,25 @@ public function setTransactionReference($value) return parent::setTransactionReference($value); } + + /** + * Is this a repeat transaction. + * + * For an account to repeat transactions based on an + * initial approval they need to get continuous authorization + * enabled by Sagepay. + * + * https://www.opayo.co.uk/support/12/36/transaction-types + * + * These can be identified because the details from the previous + * transaction are passed in to be re-used. In this instance + * the txn_type is altered and the params sent include the details + * of the previous transaction. + * + * @return bool + */ + protected function isRepeatTransaction() + { + return !empty($this->getRelatedTransactionId()); + } } diff --git a/src/Message/DirectAuthorizeRequest.php b/src/Message/DirectAuthorizeRequest.php index 19325f24..01d07c53 100644 --- a/src/Message/DirectAuthorizeRequest.php +++ b/src/Message/DirectAuthorizeRequest.php @@ -21,7 +21,9 @@ class DirectAuthorizeRequest extends AbstractRequest */ public function getTxType() { - if ($this->getUseAuthenticate()) { + if ($this->isRepeatTransaction()) { + return static::TXTYPE_REPEATDEFERRED; + } elseif ($this->getUseAuthenticate()) { return static::TXTYPE_AUTHENTICATE; } else { return static::TXTYPE_DEFERRED; @@ -41,7 +43,12 @@ public function getService() */ protected function getBaseAuthorizeData() { - $this->validate('amount', 'card', 'transactionId'); + $this->validate('amount', 'transactionId'); + if (!$this->isRepeatTransaction()) { + // If we have a related transation id the card is not required + // as we are using the cardReference. + $this->validate('card'); + } // Start with the authorisation and API version details. $data = $this->getBaseData(); @@ -63,18 +70,27 @@ protected function getBaseAuthorizeData() $data['ReferrerID'] = $this->getReferrerId(); } - // Billing details - - $data = $this->getBillingAddressData($data); - - // Shipping details - - $data = $this->getDeliveryAddressData($data); + if ($this->getRelatedTransactionId()) { + // The following parameters allow the related transaction ID to be re-used. + $data['RelatedVendorTxCode'] = $this->getRelatedTransactionId(); + $data['RelatedVPSTxId'] = $this->getVpsTxId(); + $data['RelatedSecurityKey'] = $this->getSecurityKey(); + $data['RelatedTxAuthNo'] = $this->getTxAuthNo(); + } $card = $this->getCard(); + // Card is optional for repeatPurchase so only process the following + // if it is present. + if ($card) { + // Billing details + $data = $this->getBillingAddressData($data); + + // Shipping details + $data = $this->getDeliveryAddressData($data); - if ($card->getEmail()) { - $data['CustomerEMail'] = $card->getEmail(); + if ($card->getEmail()) { + $data['CustomerEMail'] = $card->getEmail(); + } } if ((bool)$this->getUseOldBasketFormat()) { diff --git a/src/Message/ServerAuthorizeRequest.php b/src/Message/ServerAuthorizeRequest.php index 90bddb33..01aa020d 100644 --- a/src/Message/ServerAuthorizeRequest.php +++ b/src/Message/ServerAuthorizeRequest.php @@ -9,7 +9,7 @@ class ServerAuthorizeRequest extends DirectAuthorizeRequest { public function getService() { - return static::SERVICE_SERVER_REGISTER; + return $this->isRepeatTransaction() ? static::SERVICE_REPEAT : static::SERVICE_SERVER_REGISTER; } /** @@ -20,9 +20,7 @@ public function getService() */ public function getData() { - if (! $this->getReturnUrl()) { - $this->validate('notifyUrl'); - } + $this->checkRequiredFields(); $data = $this->getBaseAuthorizeData(); @@ -82,4 +80,27 @@ protected function getBaseAuthorizeData() return $data; } + + /** + * Check necessary fields are passed in. + * + * @throws \Omnipay\Common\Exception\InvalidRequestException + */ + protected function checkRequiredFields() + { + if (!$this->isRepeatTransaction()) { + if (!$this->getReturnUrl()) { + $this->validate('notifyUrl'); + } + } else { + $this->validate( + 'relatedTransactionId', + 'vpsTxId', + 'securityKey', + 'txAuthNo', + 'currency', + 'description' + ); + } + } } diff --git a/src/Message/ServerPurchaseRequest.php b/src/Message/ServerPurchaseRequest.php index 1618881f..50a910ef 100644 --- a/src/Message/ServerPurchaseRequest.php +++ b/src/Message/ServerPurchaseRequest.php @@ -19,6 +19,6 @@ public function getData() */ public function getTxType() { - return static::TXTYPE_PAYMENT; + return $this->getRelatedTransactionId() ? static::TXTYPE_REPEAT : static::TXTYPE_PAYMENT; } } diff --git a/tests/Message/ServerRepeatPurchaseViaPurchaseRequestTest.php b/tests/Message/ServerRepeatPurchaseViaPurchaseRequestTest.php new file mode 100644 index 00000000..12cda37b --- /dev/null +++ b/tests/Message/ServerRepeatPurchaseViaPurchaseRequestTest.php @@ -0,0 +1,57 @@ +request = new ServerPurchaseRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->initialize( + array( + 'amount' => '12.00', + 'currency' => 'EUR', + 'transactionId' => '123', + ) + ); + } + + public function testSettingOfRelatedTransaction() + { + $relatedTransactionRef = + '{"SecurityKey":"F6AF4AIB1G","TxAuthNo":"1518884596","VPSTxId":"{9EC5D0BC-A816-E8C3-859A-55C1E476E7C2}","VendorTxCode":"D6429BY7x2217743"}'; + $this->request->setTransactionReference($relatedTransactionRef); + $this->request->setDescription('testSettingOfRelatedTransaction'); + $data = $this->request->getData(); + + $this->assertEquals('12.00', $data['Amount'], 'Transaction amount does not match'); + $this->assertEquals('EUR', $data['Currency'], 'Currency code does not match'); + $this->assertEquals('123', $data['VendorTxCode'], 'Transaction ID does not match'); + $this->assertEquals('F6AF4AIB1G', $data['RelatedSecurityKey'], 'Security Key does not match'); + $this->assertEquals('{9EC5D0BC-A816-E8C3-859A-55C1E476E7C2}', $data['RelatedVPSTxId'], + 'Related VPSTxId does not match'); + $this->assertEquals('D6429BY7x2217743', $data['RelatedVendorTxCode'], 'Related VendorTxCode does not match'); + $this->assertEquals('1518884596', $data['RelatedTxAuthNo'], 'Related TxAuthNo does not match'); + + $this->assertEquals('REPEAT', $data['TxType']); + $this->assertEquals('repeat', $this->request->getService()); + } + +}