Skip to content

Commit

Permalink
Enable repeat purchases to be done via the purchase (and authorize) m…
Browse files Browse the repository at this point in the history
…ethods

This is more compatible with other processors that accept a token or card reference
against the purchase and authorize actions in order to process a new payment using information
stored on file from previous interactions
  • Loading branch information
eileenmcnaughton committed Jan 11, 2021
1 parent ecc048d commit 92a6ca9
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 20 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions src/Message/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
38 changes: 27 additions & 11 deletions src/Message/DirectAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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()) {
Expand Down
29 changes: 25 additions & 4 deletions src/Message/ServerAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -20,9 +20,7 @@ public function getService()
*/
public function getData()
{
if (! $this->getReturnUrl()) {
$this->validate('notifyUrl');
}
$this->checkRequiredFields();

$data = $this->getBaseAuthorizeData();

Expand Down Expand Up @@ -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'
);
}
}
}
2 changes: 1 addition & 1 deletion src/Message/ServerPurchaseRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ public function getData()
*/
public function getTxType()
{
return static::TXTYPE_PAYMENT;
return $this->getRelatedTransactionId() ? static::TXTYPE_REPEAT : static::TXTYPE_PAYMENT;
}
}
57 changes: 57 additions & 0 deletions tests/Message/ServerRepeatPurchaseViaPurchaseRequestTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Omnipay\SagePay\Message;

use Omnipay\Tests\TestCase;

/**
* Class SharedRepeatPurchaseViaPurchaseRequestTest
*
* Tests for using the purchase action to process repeat
* purchases.
*
* @package Omnipay\SagePay\Message
*/
class ServerRepeatPurchaseViaPurchaseRequestTest extends TestCase
{
/**
* @var \Omnipay\SagePay\Message\SharedPurchaseRequest $request
*/
protected $request;

public function setUp()
{
parent::setUp();

$this->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());
}

}

0 comments on commit 92a6ca9

Please sign in to comment.