Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable repeat purchases to be done via the purchase (and authorize) methods #160

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
}

}