academe/sagepay

用于处理SagePay服务器(协议v3)服务的后端处理的库

0.9.7 2017-04-03 17:28 UTC

This package is auto-updated.

Last update: 2024-09-09 23:04:57 UTC


README

Build Status Latest Stable Version Total Downloads

提供SagePay服务器和SagePay Direct服务的协议3的功能。它故意不支持所有协议v2接口特性(例如非XML购物车,因为XML购物车更加灵活),但如果人们真的需要,可以添加。V3确实是V2协议的超集,因此没有功能特性丢失。

主要要求

需要一个商店来跟踪交易。这由Academe\SagePay\Model\TransactionAbstract.php的子类处理。这个库允许你使用任何你喜欢的商店,例如一个活动的数据库记录,一个WP文章类型,一个REST资源。

限制

这个库的第一个工作版本将专注于支付交易。它尚未与重复交易、延迟或认证交易类型或其他众多服务进行测试。然而,这些都在进行中。

这个库目前只处理"SagePay服务器"。这个服务通过后台通道将交易详情推送到SagePay,然后将用户发送到SagePay输入他们的信用卡详情。信用卡详情不需要在你的网站上获取,这极大地帮助了PCI认证。你也不需要除了加密地址详情的简单SSL证书之外的SSL证书。

"SagePay Direct"允许你保持用户在你的网站上,同时至少获取支付详情。你在你的网站上获取所有信用卡详情,并通过后台通道将完整的支付详情发送到SagePay。你需要一个好的SSL证书,PCI认证也更为复杂,因为你直接处理最终用户的信用卡详情。这实际上并没有像最初看起来那样有优势,因为你仍然需要将访客发送到其他网站进行3DSecure认证和PayPal认证。这些网站都可以嵌入到iframe中以提高用户体验,但这也适用于SagePay服务器。这个库目前不支持这项服务,尽管它正在开发中。

状态

这个库正在积极开发。话虽如此,它已经准备好投入生产,并且现在正在为SagePay服务器提供服务。SagePay Direct仍在开发中,这将在稍作重构后发生。目标是创建一个后端库,用于SagePay协议版本3,可以使用你喜欢的任何存储机制,并且没有与输入相关的副作用(即不读取背后的POST,因此你的应用程序控制所有路由和输入验证)。

到目前为止,有一个存储模型抽象,以及一个PDO存储实现的示例。有购物车、地址、客户和额外费用的模型。

这个维基页面列出了最终将由库支持的消息。

安装

核心库不依赖于任何其他composer库,如果你想使用验证方法或运行单元测试,你需要运行composer install

最简单的方法是从composer获取最新稳定版本

composer require academe/sagepay

如果你使用composer开发这个库,你可以使用以下在composer.json中安装库

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

或者如果你在这个存储库的克隆中工作,在vendor/sagepay中

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

官方版本也可在 Packageist 和通过 composer 获取。

您还需要什么吗?

此库仅处理后端处理。您需要

  • 前端表单以捕获用户的姓名和地址(SagePay Direct 不需要)。
  • 对这些表单进行验证,在我们将详细信息发送给 SagePay 之前充当守门员。
  • 路由和处理 SagePay 将执行的回调通知(处理程序对于 SagePay Direct 更为复杂,因为您需要在您的网站上处理更多协议)。
  • MySQL 数据库或 Transaction 模型的扩展,用于持久化交易数据。交易数据可以存储在任何您喜欢的地方,但为了方便,内置了一个仅在 MySQL 上测试过的简单 PDO 扩展。

关于如何实现这一点的更多详细示例将在稍后提供。如果您想将此库包装到一个更广泛的库中,如 OmniPay,那么这是一个很好的起点——它处理了 SagePay 的所有细微差别,因此应该更容易集成到多网关支付网站上。在我看来,OmniPay 太过于庞大,是一个旨在成为万能工具的单一库,但作为一个将多个支付网关拉入一个统一界面的框架,是一个很好的想法。但这将是另一天的争论。请告诉我您的看法。

使用方法

非常粗略地,注册 [支付] 交易请求看起来像这样

// 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();

响应将提供下一步操作的详细信息:可能是失败,提供一个 SagePay URL 跳转,或者只是一个简单的数据验证错误需要纠正。如果 $server->getField('Status') 是 "OK",则将用户重定向到 $server->getField('NextURL'),否则处理错误。

SagePay 对数据验证非常严格。如果邮政编码过长,或者地址中包含无效字符,则将拒绝注册,但不会非常清楚地说明为什么被拒绝,当然也不会以可以用来通知最终用户的形式。因此,不要仅仅将地址扔给这个库,而要确保您根据 SagePay 验证规则对其进行验证,并提供一个预提交表单供用户进行更正(例如,从地址字段中删除无效字符——这在地址来源的框架中可能是完全有效的,在其他支付网关中也可能完全有效)。只需习惯它——这是 Sage 的方式——总有一些意想不到的笨拙。

此库的字段元数据提供了有关验证规则的信息。库应在发送给 SagePay 之前验证一切,但还应将这些规则提供给最终用户交互的框架。在这里还需要做更多的工作。

一旦成功提交交易注册,并将用户发送到 SagePay 以输入其卡详情,SagePay 将将结果发送到回调 URL。这很容易处理,mycallback.php 看起来像这样

// 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();

现在,在这个时候,用户将被发送回 mysite/final.php

在这里,用户需要了解结果,这将取决于交易的结果和内容。页面需要 VendorTxCode 来获取交易,如下所示

// 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.";
}

问题是 VendorTxCode 从何而来。它可以通过通过在回调页面的最终 URL 中设置 mysite/final.php?txcode={{VendorTxCode}} 通过 URL 传递。然而,您可能不希望将该 ID 暴露给用户。此外,这个最终页面将永久有效——交易代码将始终保留在存储中,直到清除。

您可以在首次进行支付注册时将VendorTxCode保存到会话中,然后在最后一页检索它(并删除它)。这样,这将成为对交易结果的唯一访问。如果用户同时支付多个交易,则第一个开始交易的交易结果将会丢失,但其他处理应该可以正确进行。

需要关注的PENDING状态。对于该状态,交易既没有成功也没有失败。它可能在某家银行的某个队列中等待处理。当它被处理时,SagePay会调用回调页面并返回结果。这很重要,因为用户可能不在那里看到这个过程。所以如果用户需要通过电子邮件通知,或者交易结果需要与应用程序中的某些操作(例如标记发票已支付或会员已续费)关联,那么必须在回调页面完成。请不要在用户被发送到的final.php页面上依赖此类操作。

您可以使用交易中的CustomerData字段将支付与要执行的应用程序中的资源关联起来。

验证

提供了几个验证类,它们与核心类完全解耦,这意味着您可以选择使用它们或使用自己的。服务器不会捕获许多无效数据类型并阻止您将它们发送到SagePay。SagePay会返回错误(以抛出异常的形式),这些验证类的设计是为了更方便地处理这些错误。

可用的类包括

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

用法简单

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

$serverValidator->getErrors()返回一个关联数组,字段名作为键,可读的错误信息作为值。

您可以使用验证器进行重复使用,但可能需要调用clearErrors()

您可以为验证器的每个实例单独自定义错误信息

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

所有验证器的当前/默认消息为

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";

ServerValidator具有

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";

AddressValidator具有

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

关于货币的说明

此库将支持任何ISO 4217货币,由其三位代码标识。然而,连接到SagePay账户的商家账户通常只会支持这些货币的子集。此页面列出了当前商家账户及其支持的货币

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

一些商家账户支持数十种货币,而有些则只有少量。SagePay账户可以被设置为进一步限制商家账户支持的货币列表。

没有服务器或直接API可以列出支持的货币。报告和管理API确实提供了getCurrencies()来列出供应商账户支持的货币。此库尚未支持报告和管理API,但很可能添加。这是一个现在与报告和管理API通信的库

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

通过支持支付货币,意味着可以接受该货币的支付。一家商店通常基于一个国家,只支持当地货币。如果您的商店支持多种货币,那么您有责任根据汇率在每个货币中设置正确的价格。例如,如果您是一家基于英国的公司,并选择以20美元的价格出售一个小工具,那么银行和卡操作商会处理汇率转换。您不知道那20美元将值多少英镑,直到它进入您的银行账户。

以10美元(仅限USD)的价格销售产品的商店仍会接受来自其他国家的人的支付。在这种情况下,购买者的卡供应商将计算在本国货币中应支付的金额,以确保商店获得正好10美元。

贡献

运行测试

为了运行单元测试,您需要能够在终端中调用PHPUnit。在Windows上,可以通过以下方式实现:

composer install global phpunit/phpunit:x

这将安装最新版本的PHPUnit到:C:\Users\\AppData\Roaming\Composer\vendor\phpunit\phpunit

然后,您可以将该目录添加到您的PATH中,您应该能够在命令行中调用phpunit.bat

要运行测试,请运行phpunit.bat tests/

(如果您正在使用Console2,请在您的PATH中创建一个名为phpunit.bat的文件,并在其中填写:php /c/Users//AppData/Roaming/Composer/vendor/phpunit/phpunit/phpunit.php $*