Skip to content

Commit

Permalink
Issue thephpleague#112 and thephpleague#114 - mainly Sage Pay Form su…
Browse files Browse the repository at this point in the history
…pport.

For thephpleague#114 the `billingForShipping` flag copies the billing address
to the delivery address, so only the billing address needs to
be supplied.
  • Loading branch information
judgej committed Sep 30, 2018
1 parent 6b23fac commit 72d6a78
Show file tree
Hide file tree
Showing 15 changed files with 638 additions and 138 deletions.
76 changes: 74 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ Table of Contents
* [Server Authorize/Purchase](#server-authorizepurchase)
* [Server Create Card](#server-create-card)
* [Server Notification Handler](#server-notification-handler)
* [Sage Pay Shared Methods (for both Direct and Server):](#sage-pay-shared-methods-for-both-direct-and-server)
* [Sage Pay Form Methods](#sage-pay-form-methods)
* [Form Authorize](#form-authorize)
* [Form Purchase](#form-purchase)
* [Sage Pay Shared Methods (for both Direct and Server)](#sage-pay-shared-methods-for-both-direct-and-server)
* [Repeat Authorize/Purchase](#repeat-authorizepurchase)
* [Capture](#capture)
* [Delete Card](#delete-card)
Expand Down Expand Up @@ -69,6 +72,7 @@ The following gateways are provided by this package:

* SagePay_Direct
* SagePay_Server
* SagePay_Form

For general Omnipay usage instructions, please see the main
[Omnipay](https://github.com/thephpleague/omnipay) repository.
Expand Down Expand Up @@ -586,7 +590,75 @@ The `$nextUrl` is where you want Sage Pay to send the user to next.
It will often be the same URL whether the transaction was approved or not,
since the result will be safely saved in the database.

## Sage Pay Shared Methods (for both Direct and Server):
## Sage Pay Form Methods

Sage Pay Form requires neither a server-to-server back-channel nor
IP-based security.
The payment details are encrypted on the server before being sent to
the gateway from the user's browser.
The result is returned to the merchant site also through a client-side
encrypted message.

Capturing and voiding `Form` transactions is a manual process performed
in the "My Sage Pay" administration panel.

Supported functions are:

* authorize()
* purchase()

### Form Authorize

The authorization is intialized in a similar way to a `Server` payment,
but with an `encryptionKey`:

```php
$gateway = OmniPay::create('SagePay\Form')->initialize([
'vendor' => 'vendorname',
'testMode' => true,
'encryptionKey' => 'abcdef1212345678',
]);
```

The `encryptionKey` is generated in "My Sage Pay" when logged in as the administrator.

The authorize must be given a `returnUrl` (the return URL on success, or on failure
if no separate `failureUrl` is provided).

```php
$response = $gateway->authorize([
...all the normal details...
//
'returnUrl' => 'https://example.com/success',
'failureUrl' => 'https://example.com/failure',
]);
```

The `$response` will be a `POST` redirect, which will take use to the gateway.
At the gateway, the user will authenticate or authorise their credit card,
perform any 3D Secure actions that may be requested, then will return to the
merchant site.

To get the result, the transaction is "completed":

```php
// The result will in read and decrypted from the return URL query parameters:

$result = $gateway->completeAuthorize()->send();

$result->isSuccessful();
// etc.
```

### Form Purchase

This is the same as `authorize()`, but the `purchase()` request is used instead,
and the `completePurchase()` request is used to complete the transaction on return.

## Sage Pay Shared Methods (for both Direct and Server)

Note: these functions do not work for the `Form` API.
These actions are performed through the "My Sage Pay" admin panel.

* capture()
* refund()
Expand Down
30 changes: 30 additions & 0 deletions src/AbstractGateway.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Omnipay\SagePay;

use Omnipay\Common\AbstractGateway as OmnipayAbstractGateway;
use Omnipay\SagePay\Traits\GatewayParamsTrait;

abstract class AbstractGateway extends OmnipayAbstractGateway implements ConstantsInterface
{
use GatewayParamsTrait;

/**
* Examples for language: EN, DE and FR.
* Also supports a locale format.
*/
public function getDefaultParameters()
{
return [
'vendor' => null,
'testMode' => false,
'referrerId' => null,
'language' => null,
'useOldBasketFormat' => false,
'exitOnResponse' => false,
'apply3DSecure' => null,
'useAuthenticate' => null,
'accountType' => null,
];
}
}
25 changes: 1 addition & 24 deletions src/DirectGateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace Omnipay\SagePay;

use Omnipay\Common\AbstractGateway;
use Omnipay\SagePay\Traits\GatewayParamsTrait;
use Omnipay\SagePay\Message\DirectAuthorizeRequest;
use Omnipay\SagePay\Message\DirectCompleteAuthorizeRequest;
use Omnipay\SagePay\Message\DirectPurchaseRequest;
Expand All @@ -20,36 +18,15 @@
* Sage Pay Direct Gateway
*/

class DirectGateway extends AbstractGateway implements ConstantsInterface
class DirectGateway extends AbstractGateway
{
use GatewayParamsTrait;

// Gateway identification.

public function getName()
{
return 'Sage Pay Direct';
}

/**
* Examples for language: EN, DE and FR.
* Also supports a locale format.
*/
public function getDefaultParameters()
{
return [
'vendor' => null,
'testMode' => false,
'referrerId' => null,
'language' => null,
'useOldBasketFormat' => false,
'exitOnResponse' => false,
'apply3DSecure' => null,
'useAuthenticate' => null,
'accountType' => null,
];
}

/**
* Direct methods.
*/
Expand Down
74 changes: 15 additions & 59 deletions src/FormGateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

namespace Omnipay\SagePay;

// CHECKME: do we really need these?
use Omnipay\SagePay\Message\ServerAuthorizeRequest;
use Omnipay\SagePay\Message\ServerCompleteAuthorizeRequest;
use Omnipay\SagePay\Message\ServerPurchaseRequest;
use Omnipay\SagePay\Message\ServerNotifyRequest;
use Omnipay\SagePay\Message\SharedTokenRemovalRequest;
use Omnipay\SagePay\Message\ServerTokenRegistrationRequest;
use Omnipay\SagePay\Message\ServerTokenRegistrationCompleteRequest;
use Omnipay\SagePay\Message\Form\AuthorizeRequest;
use Omnipay\SagePay\Message\Form\CompleteAuthorizeRequest;
use Omnipay\SagePay\Message\Form\CompletePurchaseRequest;
use Omnipay\SagePay\Message\Form\PurchaseRequest;
//use Omnipay\SagePay\Message\ServerNotifyRequest;
//use Omnipay\SagePay\Message\SharedTokenRemovalRequest;
//use Omnipay\SagePay\Message\ServerTokenRegistrationRequest;
//use Omnipay\SagePay\Message\ServerTokenRegistrationCompleteRequest;

/**
* Sage Pay Server Gateway
*/
class FormGateway extends DirectGateway
class FormGateway extends AbstractGateway
{
public function getName()
{
Expand All @@ -26,74 +26,30 @@ public function getName()
*/
public function authorize(array $parameters = array())
{
return $this->createRequest(ServerAuthorizeRequest::class, $parameters);
return $this->createRequest(AuthorizeRequest::class, $parameters);
}

/**
* Authorize and capture a payment.
*/
public function purchase(array $parameters = array())
{
return $this->createRequest(ServerPurchaseRequest::class, $parameters);
return $this->createRequest(PurchaseRequest::class, $parameters);
}

/**
* Handle notification callback.
* Replaces completeAuthorize() and completePurchase()
*/
public function acceptNotification(array $parameters = array())
{
return $this->createRequest(ServerNotifyRequest::class, $parameters);
}

/**
* Accept card details from a user and return a token, without any
* authorization against that card.
* i.e. standalone token creation.
* Omnipay standard function; alias for registerToken()
*/
public function createCard(array $parameters = array())
{
return $this->registerToken($parameters);
}

/**
* Remove a card token from the account.
* Standard Omnipay function.
*/
public function deleteCard(array $parameters = array())
{
return $this->createRequest(SharedTokenRemovalRequest::class, $parameters);
}

/**
* Accept card details from a user and return a token, without any
* authorization against that card.
* i.e. standalone token creation.
* Gateway-specific function.
*/
public function registerToken(array $parameters = array())
{
return $this->createRequest(ServerTokenRegistrationRequest::class, $parameters);
}

/**
* Handle authorize notification callback.
* Please now use acceptNotification()
* @deprecated
*
*/
public function completeAuthorize(array $parameters = array())
{
return $this->createRequest(ServerCompleteAuthorizeRequest::class, $parameters);
return $this->createRequest(CompleteAuthorizeRequest::class, $parameters);
}

/**
* Handle purchase notification callback.
* Please now use acceptNotification()
* @deprecated
*
*/
public function completePurchase(array $parameters = array())
{
return $this->completeAuthorize($parameters);
return $this->createRequest(CompletePurchaseRequest::class, $parameters);
}
}
91 changes: 83 additions & 8 deletions src/Message/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public function getService()
*/
public function getTxType()
{
throw new InvalidRequestException('Transactino type not defined.');
throw new InvalidRequestException('Transaction type not defined.');
}

/**
Expand Down Expand Up @@ -103,6 +103,83 @@ protected function getBaseData()
return $data;
}

/**
* Get either the billing or the shipping address from
* the card object, mapped to Sage Pay field names.
*
* @param string $type 'Billing' or 'Shipping'
* @return array
*/
protected function getAddressData($type = 'Billing')
{
$card = $this->getCard();

// Mapping is Sage Pay name => Omnipay Nname

$mapping = [
'Firstnames' => 'FirstName',
'Surname' => 'LastName',
'Address1' => 'Address1',
'Address2' => 'Address2',
'City' => 'City',
'PostCode' => 'Postcode',
'State' => 'State',
'Country' => 'Country',
'Phone' => 'Phone',
];

$data = [];

foreach ($mapping as $sagepayName => $omnipayName) {
$data[$sagepayName] = call_user_func([$card, 'get' . $type . $omnipayName]);
}

// The state must not be set for non-US countries.

if ($data['Country'] !== 'US') {
$data['State'] = '';
}

return $data;
}

/**
* Add the billing address details to the data.
*
* @param array $data
* @return array $data
*/
protected function getBillingAddressData(array $data = [])
{
$address = $this->getAddressData('Billing');

foreach ($address as $name => $value) {
$data['Billing' . $name] = $value;
}

return $data;
}

/**
* Add the delivery (shipping) address details to the data.
* Use the Billing address if the billingForShipping option is set.
*
* @param array $data
* @return array $data
*/
protected function getDeliveryAddressData(array $data = [])
{
$address = $this->getAddressData(
(bool)$this->getBillingForShipping() ? 'Billing' : 'Shipping'
);

foreach ($address as $name => $value) {
$data['Delivery' . $name] = $value;
}

return $data;
}

/**
* Send data to the remote gateway, parse the result into an array,
* then use that to instantiate the response object.
Expand Down Expand Up @@ -170,13 +247,11 @@ public static function parseBodyData(ResponseInterface $httpResponse)
*/
public function getEndpoint()
{
$service = $this->getService();

if ($this->getTestMode()) {
return $this->testEndpoint."/$service.vsp";
}

return $this->liveEndpoint."/$service.vsp";
return sprintf(
'%s/%s.vsp',
$this->getTestMode() ? $this->testEndpoint : $this->liveEndpoint,
$this->getService()
);
}

/**
Expand Down
Loading

0 comments on commit 72d6a78

Please sign in to comment.