Giter Club home page Giter Club logo

sagepay's Introduction

Client Library for SagePay Direct/Server - Protocol V3.00

Build Status Latest Stable Version Total Downloads

Provides the functionality for Protocol 3 of the SagePay Server and SagePay Direct services. It purposefully does not fully support all protocol v2 interface features (e.g. the non-XML basket, because the XML basket is a lot more flexible) but that could be added if people really desire it. V3 is truly a superset of the V2 protocol, so no functional features are lost.

Main Requirements

A store is needed to track the transaction. This is handled by a descemndant class of Academe\SagePay\Model\TransactionAbstract.php. This library allows you you use any store you like, e.g. an active database record, a WP post type, a REST resource.

Limitations

The first working release of this library will focus on paying PAYMENT transactions. It has not been tested with repeating transactions or DEFERRED or AUTHENTICATE transaction types, or the myriad other services. However, these are all being worked on.

This library is only handling "SagePay Server" at present. This service pushes details of the transaction to SagePay via a back-channel, then sends the user to SagePay to enter their credit card details. Credit card details do not have to be taken on your own site, and that helps immensely with PCI accreditation. You also do not need a SSL certificate beyond a simple one for encrypting address details as they are entered.

"SagePay Direct" allows you to keep the user on your own site, while taking payment details at least. You take all credit card details on your site and send the full payment details via a back-channel to SagePay. You need a good SSL certificate, and PCI certification is a lot more involved, since you are directly handling end-user credit card details. This is not really as big an advantage as it first appears, as you still need to send visitors to other sites for 3DSecure authorisation and PayPal authentication. These sites can all be embdded into an iframe to improve the user experience, but that also applies to SagePay Server. This library does not support this service at present, though it is being worked on.

Status

This library is being actively worked on. Having said that, is is production-ready and is in service now for SagePay Server. SagePay Direct is still being developed, which will happen after a little refactoring. The intention is for a back-end library for SagePay protocol version 3, that can use any storage mechanism you like and does not have side-effects related to input (i.e. does not read POST behind your back, so your application controls all routing and input validation).

So far there is a storage model abstract, with an example PDO storage implementation. There are models for the basket, addresses, customers, and surcharges.

This wiki page lists the messages that will ultimately be supported by the library.

Installation

The core library does not depend on any other composer libraries, you will need to run composer install if you wish to use the Validation methods or to run the unit tests.

The simplest method is to get the latest stable version from composer:

composer require academe/sagepay

If developing this library and using composer, you can install the library into your project using this in composer.json:

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/academe/SagePay"
        }
    ],
    "require": {
        "php": ">=5.3.0",
        "academe/sagepay": "dev-master"
    }
}

Or if working on a clone of this repository in in vendor/sagepay:

{
    "autoload": {
        "psr-0": { "Academe\\SagePay": "vendor/sagepay/src" }
    }
}

The official releases are also available on Packageist and so through composer.

What else do you need?

This library handles the back-end processing only. You will need:

  • Front-end forms to capture the user's name and address (not required for SagePay Direct).
  • Validation on those forms to act as a gatekeeper before we sent those details to SagePay.
  • Routeing and a handler for the notification callback that SagePay will perform (the handler is more complex for SagePay Direct, as you need to handle more of the protocol on your site).
  • A MySQL database or an extension to the Transaction model for persisting the transaction data. The transaction data can be stored anywhere you like, but a simple PDO extension, only tested on MySQL, is built in for convenience.

Some more detailed examples of how this could work, will follow later. If you want to wrap this library up in a more diverse library, such as OmniPay, then this would be a good start - it handles all the nuances of SagePay and so should be easier to incorporate into a multi-gateway payment site. IMO OmniPay is too monolithic, a single library that aims to be the jack of all trades, but as a framework to pull together many payment gateways into one unified interface, is a great idea. But that's an argument for another day. Please let me know what you think.

Usage

Very roughly, registering a [payment] transaction request will look like this:

// In all the code examples here, I will assume a PSR-0 autoloader is configured.
// e.g. for composer this may be included like this, taking the path to the vendor
// directory into account:

require 'vendor/autoload.php';

// This just half the process. This registers a payment request with the gateway.

// Create the Server registration object.

$server = new Academe\SagePay\Server();

// Create a storage model object.
// A basic PDO storage is provided, but just extend Model\TransactionAbstract and use your own.
// For example, you may have an eloquent model for storing the transaction data, so that model
// would be injected into your extension of TransactionAbstract, which would be the interface
// between the model the SagePay package uses, and the storage model that eloquent provides.
// Your framework may have active record model, or you may want to use WordPress post types, for example.
// You can write your own transaction storage model, perhaps storing the transaction data in a custom
// post type in WordPress, or a database model in your framework. This TransactionPdo model is just
// a usable example that comes with the library.

$storage = new Academe\SagePay\Model\TransactionPdo();
$storage->setDatabase('mysql:host=localhost;dbname=MyDatabase', 'MyUser', 'MyPassword');

// Within WordPress, setting the database details looks like this:

$storage->setDatabase('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME, DB_USER, DB_PASSWORD);

// Or alternatively use the storage model TransactionPdoWordpress and have the database details
// set for you automatically:

$storage = new Academe\SagePay\Model\TransactionPdoWordpress(); // No need to call setDatabase()

// Inject the storage object.

$server->setTransactionModel($storage);

// If you want to create a table ("sagepay_transactions" by default) for the PDO storage, do this.
// The table will be created from the details in Metadata\Transaction and should provide a decent
// out-of-the-box storage to get you up and running. You could execute this in the initialisation
// hook of a plugin, assuming you are not using a custom post type to track the transactions.

$storage->createTable();

// The PDO storage table may need upgrading for new releases. Call this method to do that:

$storage->updateTable();

// Note that both createTable() and updateTable() are both specific to the PDO storage model.
// You may store your data elsewhere and have your own way of setting up structures and storage.
// For example, the transactions may be stored in a model in a framework that has its own way
// to migrate database structures during releases and upgrades.
    
// Set the main mandatory details for the transaction.
// We have: payment type, vendor name, total amount, currency, note to display to user, callback URL.

$server->setMain('PAYMENT', 'vendorx', '99.99', 'GBP', 'Store purchase', 'http://example.com/mycallback.php');

// Indicate which platform you are connecting to - test or live.

$server->setPlatform('test');

// Set the addresses.
// You can just set one (e.g. billing) and the other will automatically mirror it. Or set both.

$billing_addr = new Academe\SagePay\Model\Address();
$billing_addr->setField('Surname', 'Judge');
$billing_addr->setField('Firstnames', 'Jason');
$billing_addr->setField('Address1', 'Some Street Name');
$billing_addr->setField('City', 'A City Name');
// etc.
$server->setBillingAddress($billing_addr);

// Set optional stuff, including customer details, surcharges, basket.
// Here is an example for the basket. This is a very simple example, as SagePay 3.0
// can support many more structured data items and properties in the basket.
// The currency needs to be set as it affects how the monetory amounts are formatted.

$basket = new Academe\SagePay\Model\Basket();
$basket->setCurrency('GBP');
$basket->setDelivery(32.50, 5);
$basket->addSimpleLine('Widget', 4.00, 3, 0.75, 3.75);
$server->setBasketModel($basket);

// Generate a VendorTxCode. This is the primary key of the transaction, as seen from
// the vendor site, and must be sent to SagePay so it can be used in the notification
// callback to identify your transaction. The easiest way to do this is to save the
// transaction.
// See Issue #10 for some more notes.

$server->save();

// Alternatively, set the VendorTxCode in the transaction without saving it:

$server->setField('VendorTxCode', $server->getTransactionModel()->makeVendorTxCode());

// Hopefully both the above methods can be avoided completely once Issue #10 is fixed.

// Optionally run some validations
$serverValidator = new \Academe\SagePay\Validator\Server;
$errors = $serverValidator->validate($server)->getErrors();
if (count($errors) > 0) {
    // Show the user the errors, and let them fix them
}

// Send the request to SagePay, get the response, The request and response will also
// be saved in whatever storage you are using.

$server->sendRegistration();

The response will provide details of what to do next: it may be a fail, or give a SagePay URL to jump to, or just a simple data validation error to correct. If $server->getField('Status') is "OK" then redirect the user to $server->getField('NextURL') otherwise handle the error.

SagePay is very strict on data validatin. If a postcode is too long, or an address has an invalid character in, then it will reject the registration, but will not be very clear exactly why it was rejected, and certainly not in a form that can be used to keep the end user informed. For this reason, do not just throw an address at this library, but make sure you validate it according to SagePay validation rules, and provide a pre-submit form for the user to make corrections (e.g. to remove an invalid character from an address field - something that may be perfectly valid in the framework that the address came from, and may be perfectly valid in other payement gateways). Just get used to it - this is the Sage way - always a little bit clunky in some unexpected ways.

The field metadata in this library provides information on the validation rules. The library should validate everything before it goes to SagePay, but also those rules should be available to feed into the framework that the end user interacts with. Work is still to be done here.

Once a transaction registration is submitted successfuly, and the user is sent to SagePay to enter their card details, SagePay will send the result to the callback URL. This is easily handled, with mycallback.php looking something like this:

// Gather the POST data.
// For some platforms (e.g. WordPress) you may need to do some decoding of the POST data.

$post = $_POST;

// Set up the transaction model, same as when registering. Here is a slightly shorter-hand version.

$server = new Academe\SagePay\Server();
$server->setTransactionModel(new Academe\SagePay\Model\TransactionPdo())
    ->setDatabase('mysql:host=localhost;dbname=MyDatabase', 'MyUser', 'MyPassword'');

// Handle the notification.
// The final URL sent back, which is where the user will end up. We are also passing the
// status with the URL for convenience, but don't rely on looking at that status to
// determine if the payment was successful - a user could fake it.

$result = $server->notification(
    $post, 
    'http://example.com/mysite/final.php?status={{Status}}'
);

// Other actions can be performed here, based on what we find in `$server->getField('Status')`
// For example, you may want to inform an application that an invoice has been paid.
// You may also want to send the user an email at this point (to `$server->getField('CustomerEMail')`

// Return the result to SagePay.
// Do not output *anything* else on this page. SagePay is expecting the contents of $result *only*.
// If you are calling up other code here to take action on the transaction, then it may be worth
// using ob_start()/ob_end_clean() to catch and discard any output that may be generated.

echo $result;

exit();

Now, at this point the user will be sent back to mysite/final.php

Here the user needs to be informed about the outcome, and that will depend on the result and contents of the transaction. The page will need the VendorTxCode to get hold of the transaction like this:

// Set up the transaction model, same as when registering. Here is a slightly shorter-hand version.

$server = new Academe\SagePay\Server();
$server->setTransactionModel(new Academe\SagePay\Model\TransactionPdo())
    ->setDatabase('mysql:host=localhost;dbname=foobar', 'myuser', 'mypassword');

// Fetch the transaction from storage.

$server->findTransaction($VendorTxCode);

// Look at the result and take it from there.

$status = $server->getField('Status');

if ($server->isPaymentSuccess()) {
    echo "Cool. Your payment was successful.";
} elseif ($status == 'PENDING') {
    echo "Your payment has got delayed while being processed - we will email you when it finally goes through.";
} else {
    echo "Whoops - something went wrong here. No payment has been taken.";
}

The question is where the VendorTxCode comes from. It could be passed in via the URL by setting the final URL in the callback page to mysite/final.php?txcode={{VendorTxCode}} However, you may not want that ID to be exposed to the user. Also this final page would be permanently active - the transaction code will always be there in storage until it is cleared out.

You may save VendorTxCode in the session when the payment registration is first made, and then retrieve it (and delete it) on the final page. That way this becomes a once-only access to the transaction result. If the user pays for several transactions at the same time, then the result of the transaction started first will be lost, but the processing should otherwise work correctly.

The PENDING status is one to watch. For that status, the transaction is neither successful nor failed. It is presumably on some queue at some bank somewhere to be processed. When it is processed, the callback page will be called up by SagePay with the result. This is important to note, because the user will not be around to see that happen. So if the user needs to be notified by email, or the transaction result needs to be tied back to some action in the application (e.g. marking an invoice as paid or a membership as renewed) then it MUST be done in the callback page. Do not rely on this kind of thing to be done in the final.php page that the user is sent to.

You can make use of the CustomerData field in the transaction for linking the payment to a resource in the application to be actioned.

Validation

Several validation classes are provided, they are completly decoupled from the core classes meaning you can choose to use them or use your own. The Server will not catch many invalid data types and stop you from sending them to SagePay. SagePay will return errors (in the form of thrown exceptions), the idea of these validation classes is to handle these in an easier fashion.

Available classes include:

  • Academe\SagePay\Validator\Server
  • Academe\SagePay\Validator\Model\Address

Usage is simple

$server = new \Academe\SagePay\Server;
$serverValidator = new \Academe\SagePay\Validator\Server;
$serverValidator->validate($server);
$errors = $serverValidator->getErrors();

$serverValidator->getErrors() returns an assosiative array with the field name as the key, and a human-readable error message as the value.

You can reuse a validator, but you may want to call clearErrors().

You may customise the errors messages for each instance of a validator individually:

$validator->CANNOT_BE_EMPTY = "Kindly fill in this field`;

The current/default messages for all validators are:

public $CANNOT_BE_EMPTY = "%s cannot be empty";
public $BAD_RANGE = "%s must be between %d and %d characters";
public $BAD_LENGTH = "%s must be exactly %d characters";
public $BAD_CHARACTERS = "%s cannot contain the following characters %s";

The ServerValidator has:

public $CURRENCY_INVALID = "Currency is not valid";
public $AMOUNT_BAD_FORMAT = "Amount must be in the UK currency format XXXX.XX";
public $AMOUNT_BAD_RANGE = "Amount must be between 0.01 and 100,000";

The AddressValidator has:

public $STATE_ONLY_FOR_US = "State is only valid for the US";
public $COUNTRY_VALID_CODE = "Country must be a valid country code";

A Note About Currencies

This library will support any ISO 4217 currency, identified by its three-character code. However, the merchant account connected to the SagePay account will normally only support a subset of those currencies. This page lists the current merchant accounts and the currencies they support:

http://www.sagepay.com/help/faq/merchant_number_format

Some merchant accounts support dozens of currencies, and some only a handful. A SagePay account can be set up to further restrict the list that the merchant account supports.

There is no server or direct API that will list the supported currencies. The Reporting and Admin API does provide getCurrencies() to list the currencies that the vendor account supports. This library does not yet support the Reporting and Admin API, but it is something that is likely to be added. Here is a library that talks to the Reporting and Admin API now:

https://github.com/colinbm/sagepayadminapi-php

By supporting a currency for payments, it means that payments can be taken in that currency. A shop will often be based in a single country and support just that local currency. If your shop supports multiple currencies, then it is your responsibility to set the correct prices in each currency according to the exchange rates. For example, if you are a UK based company and you choose to sell a widget to 20USD, then it will be the banks and card operators that handle the exchange. You won't know how much that 20USD will be worth in GBP until it hits your bank account.

A shop selling a product at 10 USD (and only USD) will still accept payments from people in other countries. In that case it will be the purchaser's card supplier that will calculate the amount to be paid in their local currency to ensure the shop receives exactly 10 USD.

Contributing

Running Tests

In order to run the unit tests you will need to be able to call PHPUnit from your terminal. This can be done on windows by:

composer install global phpunit/phpunit:x

This will install the latest version of phpunit into: C:\Users\<username>\AppData\Roaming\Composer\vendor\phpunit\phpunit

You can then add that directory to your PATH, and you should be able to call phpunit.bat from the command line.

To run the tests run phpunit.bat tests/

(If you are using Console2, create a file called phpunit.bat which is on your path and fill it with: php /c/Users/<username>/AppData/Roaming/Composer/vendor/phpunit/phpunit/phpunit.php $*)

sagepay's People

Contributors

ev-mark avatar judgej avatar metaturso avatar patabugen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

sagepay's Issues

possible misleading information on comment section

Hi i am integrating the package in laravel. Im going thru the comments and i see this:

// Create a storage model object.
// A basic PDO storage is provided, but just extend Model\Transaction and use your own.

I search transaction inside model folder but there's none? it is TransactionAbstract or is it Metadata\Transaction please help me understand.

Also how is it possible to using it with laravel eloquent?

Many thanks
Prajwol.

Add validation functionality

A means to validate and to transform data (into valid formats) is needed to provide a robust working environment.

Some items, such as basket descriptions, can be truncated and have invalid characters removed, and stuff will still work as expected. Other fields cannot be transformed (munged) in this way, and need to result in a failed payment request so that remedial action can be taken.

The validation actually needs to happen before anything is submitted to SagePay, as the error responses we get back are not technically useful (e.g. they don't make it easy to identify the incorrect field - "the basket format is invalid" is one cringe-worthy example than can mean anything and SagePay will halt at the first error it finds and report only that one error. If every field on my submitted address is invalid, I would want to know about all of them and not have to submit the form once for each error and slowly work through them).

Document methods

Proper header blocks on each method is required. Documentation is always the kind of last thing to do, when getting the library working for a project is the most urgent thing.

Second CardType does not contain tamper: true

I've been debugging SagePay callback errors which were giving me the error Undefined index: CardType in Academe/SagePay/Server.php on line 247

I believe this is caused by the duplicated CardType in Metadata/Transaction.php (noted in comment on line 23) because the second CardType does not contain the "tamper": true, field (the first one does).

Since the key is duplicated, the first one is ignored - I'd suggest we merge the two card type lists and consider how we might separate them again in future if we need to have different data types per source (the validation will be slightly less strict, but at the moment I'm not sure how or why it would even be working for server-notification - the first one.

Create Omnipay connector

Omipay has a SagePay V2 driver, but not V3. It would be good (once this one is completely tidied up) to create a connector to expose it as an Omnipay driver. The driver would support all the base functionality that Omnipay provides, and then could provide extensions to the additional functionality that Omnipay does not generally support (or encourage), such as additional basket information, additional customer details, separate emails for billing and shipping.

The transport would need to be abstracted properly (it is currently stuck in ServiceAbstreact::postSagePay as a curl-based method). This would allow us to use the Omnipay transport mechanisms.

The network flow diagram here would help to understand how this would all link together:

https://github.com/judgej/omnipay-sagepay/tree/patch-1/docs

See:

https://github.com/thephpleague/omnipay

Is VPSSignature utilised?

Hi, given that this class is not yet complete, does it carry out the md5 hash check using VPSSignature, please?

Fields for Refund not picked out

I don't think this is technically a bug (yet) since the library doesn't (yet) support Refunds. However I'd like it to :)

in the ServiceAbstract is a method queryData() which creates a Query String based on the $message_type, which are things like 'server-registration'.

Correct me if I'm mis-understanding something, but do the picked fields not depend on the transaction type? E.g PAYMENT or REFUND?

My thought is that the TxType should be passed into QueryData and in the Metadata\Transaction object the "source" arrays should be populated by TxTypes.

Helper::formatAmount silently sets Amount to Zero

$server->setAmount('1,234.56', 'GBP');
echo $server->getField('Amount'); // Returns numeric 0

because of

if ( ! is_numeric($amount)) $amount = 0;

I'd reckon it should return something non-numeric at least, but I'd suggest it throws an exceptions since that number should be checked elsewhere.

Write tests

Not done this for a project before, so any advice on how to get started on this would be great.

Start using constructor injection

Most of the methods supported by the Server (or Direct or Shared) require access to a Transaction model with its relevant storage. That should be moved to the constructor so that we can guarantee that it is available, instead of keep checking whether it has been set whenever we need it.

Any apps that make use of the setter would need modifying, so this is a warning :-)

Similarly, TransactionPdo should also take a database object in its constructor, since it will not work without one.

Split Registration into Server and Direct

The Registration class is going to get pretty big once SagePay Direct is added. Instead, it should be split into Direct and Server classes. We already have a Shared class for the shared services.

A Common class will be needed for the functionality that all three share.

Registration can inherit Server for backwards compatibility, but otherwise be deprecated.

Use Data Mapper model rather than up-side-down Active Record

And active record normally starts with a data storage layer, and models inherit from that layer and add their own fields and business logic.

The Transaction object here starts with the data properties and business logic, then allows you to use one of several data storage methods that inherit from the model.

That is a kind of up-side-down active record, and could make integration a little cumbersome.

Instead, we should use a data mapper. The models already are pretty much dumb data with some business rules. We just need to take the storage out of the line of inheritance and keep it as a separate mapper. Each storage type would then be an adapter for the mapper. I don't think it is a complicated change, but will make more sense for people familiar with more standard patterns, and will be easier to test (the model and the storage can be tested independently, while at the moment the storage can only be tested with a model).

Passing correct customer data to Sage Pay

HI. Thanks ever so much for publishing this project, which looks like it's going to save me a stack of work :)

I'm having a problem loading the correct customer details and am wondering what I'm doing wrong. My code is as follows:

// set the billing details
$billing_addr = new Academe\SagePay\Model\Address();
$billing_addr->setField('Surname', $data['c_surname']);
$billing_addr->setField('Firstnames', $data['c_firstname']);
$billing_addr->setField('Address1', $data['c_add1']);
$billing_addr->setField('Address2', $data['c_add2']);
$billing_addr->setField('City', $data['c_town']);
$billing_addr->setField('PostCode', $data['c_postcode']);
$billing_addr->setField('Country', 'GB');
$server->setBillingAddress($billing_addr);
// set the delivery details
$delivery_addr = new Academe\SagePay\Model\Address();
$delivery_addr->setField('Surname', $data['c_surname']);
$delivery_addr->setField('Firstnames', $data['c_firstname']);
$delivery_addr->setField('Address1', $data['c_add1']);
$delivery_addr->setField('Address2', $data['c_add2']);
$delivery_addr->setField('City', $data['c_town']);
$delivery_addr->setField('PostCode', $data['c_postcode']);
$delivery_addr->setField('Country', 'GB');
$server->setDeliveryAddress($delivery_addr);
// set the customer details
$customer_details = new Academe\SagePay\Model\Customer();
$customer_details->setField('CustomerEMail', $data['email']);
$server->setCustomerModel($customer_details);
// generate a unique VendorTxCode to identify the transaction
$server->save();

Both the local storage db and SagePay's systems record both the billing and delivery addresses with the data entered above for 'billing', and the email address is not recorded at all. If you have time, please could you explain what I'm doing wrong?

Make sure typehints are interfaces, not abstracts

This injection example type-hints an abstract:

public function setTransactionModel(Model\TransactionAbstract $tx_model)

It kind of made sense to me, because extended transactions will derive from this abstract.

However, when creating models based on third-party classes, the ancestry (being a linear line, before PHP 5.5 traits at least) may not come from the abstract declared here. However, if the abstract implements an interface, then a third party model that does not extend any of the models here, can still implement the interface and so be valid for setTransactionModel().

It has taken me a while to realise the subtlety of this, as the abstract felt a lot more "useful" than an interface, but it is clear to me now :-)

Change OriginalVendorTxCode to RelatedVendorTxCode

This will bring the parameters used for RELEASE/VOID/CANCEL/ABORT into line with the parameters defined by SagePay for AUTHORISE. More consistency will make throwing data around the APIs and the database a little easier.

This is a change to a data store field.

There will also be RelatedVPSTxId and RelatedSecurityKey to be added to the data store.

Support discounts in basket

Moved from Ticket #26

The earlier SagePay spec (2013-03-25) this package was coded from, did not document the discount records in the basket. This is now documented in the later version (2013-09-30). The API remains at version 3.00

The current workaround in the meantime is to extend the BasketAbstract and code your own discounts.

Create Omnipay Wrapper

Since Omnipay - https://github.com/omnipay/ - now supports third-party gateways, a wrapper should allow this gateway to be used as an Omnipay plugin. The advantage would be to take advantage of functionality specific to this library (even though much is yet to be completed).

Whether the wrapper is a separate library or built-in, I'm not sure. If it is compact, then building it in makes sense to me. In a similar way, a facade and provider class would make it easy to integrate with Laravel (whether you would want to do this, or go through Omnipay, is another question). Flexibility all round is a good thing, so long as it doesn't get bloated.

VendorTxCode not guaranteed to be unique or unpredictable

The current implementation of \Academe\SagePay\Model\TransactionAbstract::makeVendorTxCode is not guaranteed to be unique or unpredictable according to the PHP documentation for uniqid.

I would suggest the following function as (a) the transaction will fail if the VendorTxCode is accidentally repeated; (b) this function will not create predictable strings that a malicious user could brute force, if the VendorTxCode is used in any user input.

/**
 * This function returns a UUID.
 * Source: http://stackoverflow.com/a/15875555/1971539
 *
 * @return string A UUID following GUIDv4 spec, without braces. 36 characters long.
 */
public static function guidv4() 
{
    $data = openssl_random_pseudo_bytes(16);

    $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10

    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

However, if context is important then you could use the following function. It returns something quite similar to the existing function, but uses the full length allowed by Sagepay:

/**
 * Make a new VendorTxCode.
 * To be give the code some context, we start it with a timestamp before
 * we add on a random hex string.
 * The VendorTxCode is limited to 40 characters, so we use 12 bytes for the hex.
 * Override this method if you want a different format.
 */

public function makeVendorTxCode()
{
    $data = openssl_random_pseudo_bytes(12);

    return vsprintf('%s-%s', Array(date('Ymd-His'), bin2hex($data)));
}

I have created a pull request with the latter function. Whether you merge it or not is down to your feelings on introducing OpenSSL as a dependency.

Token field may not be send in server-notification

Unless you request it and enable (and pay for) it on your account you don't generate 'Token' emails.

Server.php around line 240 has this line:

$this->setField('Token', $post['Token']);

which throws an error if Surcharge isn't sent.

For now I've just done

if (isset($post['Token'])) {
    $this->setField('Token', $post['Token']);
}

Transaction PDO table not upgradable

The PDO transaction model has createTable() to create the database table for storage of the registered transactions. The table is create from the Transaction metadata (all fields with a "store" flag set true).

When the metadata is extended in library releases, there is no updateTable() method that can be used to bring the table into line with the current metadata. It could with one.

Handle duplicate notification callbacks

Sometimes Sage Pay does not get the initial notification response that we return, so it will send the notification again. At the moment, this library will log this as an error, so Sage Pay thinks the transaction is in error and will cancel it, but the merchant site thinks it has send a valid "OK" and has no idea Sage Pay lost that response.

The solution is to check if the notification is identical to the one stored, if the current state is not PENDING. If it is the same, then, just return OK as though it were the first time it were received, and not update anything in the database.

Main documentation

The README gives us somewhere to put a quick-start, but there will be a tonne of documentation to write for this. There are so many different ways in which it can be used, and options available, that a good set of use-case working code would be good.

VendorTxCode not set

I setup the SagePay Server by following the example in readme.md, but Sage returned with the error saying that the VendorTxCode was not set.

To resolve this, I've added:
$server->setField('VendorTxCode', $storage->makeVendorTxCode());

just before
$server->sendRegistratrion();

Surcharge XML is too long

This isn't a bug in the library so much as an annoyance in SagePay, but you may have some insight which helps.

When sending SurchargeXML there is a maximum length of the XML otherwise SagePay returns:

INVALID : 3175 : The Surcharge XML is too long.

I've not calculated exactly what the limit is (though it's around 800 characters) but there is enough space to set all but about three card types

$surcharge->addPercentage('VISA', $surchargeAmount)
    ->addPercentage('AMEX', $surchargeAmount)
    ->addPercentage('DELTA', $surchargeAmount)
    ->addPercentage('JCB', $surchargeAmount)
    ->addPercentage('DC', $surchargeAmount)
    ->addPercentage('MC', $surchargeAmount)
    ->addPercentage('UKE', $surchargeAmount)
    ->addPercentage('MAESTRO', $surchargeAmount)
//  ->addPercentage('MCDEB', $surchargeAmount)
//  ->addPercentage('IT', $surchargeAmount)
//  ->addPercentage('GIROPAY', $surchargeAmount)
    ->addPercentage('SOFORT', $surchargeAmount);

Additionally using a Fixed amount lets you specify all card types because "fixed" is a shorter word than "percentage".

It'd be good to create a validator to catch this.
If this is indeed an issue with SagePay I'd suggest workarounds are either:

  1. Use Fixed amounts always, and calculate the percentage if need be
  2. Recognise a few card types you're not going to use.

MD5 signature - change from Pending to OK for PPRO payments

The following is an email we have received from SagePay regarding issues we have been having with PPRO not updating from "pending" to "OK" status. Could anyone suggest where this needs to be corrected (if indeed it is an issue within this library)?

I am writing to you in regards to the ongoing issue with pending PPRO payments.

The issue is to do with the MD5 signature when we call back to your Notification URL. Initially the callback from your server for a Status = Pending transactions is as normal, works fine no issues there, the signatures match, but when we call back the second time to change it from pending to OK, there is a problem with the MD5 signature built by your server.

We need to send another callback to your server as we wait for PPRO to contact Sage Pay to confirm the order has authorised.

For the below example we get this confirmation from PPRO about 20 minutes after the transaction goes through:

VendorTxCode=12345678,
VPSTxId={XYZ123-XYZ123-XYZ123-XYZ123-XYZ123},
Status=OK,
GiftAid=0,
VPSSignature=0123456789ABCDE

This needs to be built like:
12345678{XYZ123-XYZ123-XYZ123-XYZ123-XYZ123}OK0
VPSSignature=0123456789ABCDE

So the order for building this is:
VPSTxId
VendorTxCode
Status
GiftAid

So we need your server to reply with the correct VPSSignature for the second callback to acknowledge they change from Pending to OK in the Sage Pay system in the format shown above and this will mean that your transactions do not stay as Pending in My Sage Pay and revert to a successfully authorised transaction.

isPaymentSuccess can return undefined property error

In a standard process, isPaymentSuccess is usually called on the notification callback URL and therefore the transaction status will have been set by SagePay. However if we test that transaction endpoint without setting the status, it returns an error: Undefined property: Academe\SagePay\Model\TransactionPdo::$Status

To fix, change line 550 in TransactionAbstract.php to:

if (isset($this->Status) && ($this->Status == 'OK' || $this->Status == 'AUTHENTICATED' || $this->Status == 'REGISTERED')) {

Sagepay callback function is not taking sagepay tx_model

Hi
public function anyCallback() {

    // Gather the POST data.
    $post = $_POST;
    $host = Config::get('database.connections.mysql.host', 'localhost');
    $dbname = Config::get('database.connections.mysql.database', 'database');
    $dbuser = Config::get('database.connections.mysql.username', 'root');
    $dbpass = Config::get('database.connections.mysql.password', '');

    $this->storage = new Academe\SagePay\Model\TransactionPdo();
    $this->storage->setDatabase('mysql:host=' . $host . ';dbname=' . $dbname, $dbuser, $dbpass);
    $dbprefix = Config::get('database.connections.mysql.prefix', '');
    $this->storage->setTablename($dbprefix . 'sagepay_transactions');

    $this->server->setTransactionModel($this->storage);
    if ($this->server->getField('Status') == 'OK') {

the callback function is not taking $this->server->getField('Status') == 'OK'.. is there something i must consider?

Regards,
Prajwol

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.