academe / sagepay
用于处理 SagePay 服务器(协议 v3)服务后端处理的库
Requires
- php: >=5.3.0
- respect/validation: 0.5.x
Requires (Dev)
- phpunit/phpunit: 4.0.*
README
提供 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 数据库或对事务模型的扩展,用于持久化事务数据。事务数据可以存储在您喜欢的任何地方,但为了方便,内置了一个仅测试于 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将调用回调页面并返回结果。这一点很重要,因为用户可能无法看到这一过程。所以,如果用户需要通过电子邮件通知,或者交易结果需要与应用程序中的某些操作(例如,将发票标记为已支付或会员更新)相关联,那么这必须在回调页面中完成。不要在用户被发送到的最终.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美元(仅限美元)销售产品的商店仍会接受来自其他国家的人的支付。在这种情况下,购买者的卡供应商将计算以当地货币支付的金额,以确保商店收到正好10美元。
贡献
运行测试
为了运行单元测试,您需要能够在终端中调用PHPUnit。在Windows上,可以通过以下方式实现:
composer install global phpunit/phpunit:x
这将把最新版本的PHPUnit安装到: C:\Users\
然后,您可以将其目录添加到PATH环境变量中,并且应该能够在命令行中调用 phpunit.bat
。
要运行测试,请执行 phpunit.bat tests/
(如果您使用的是Console2,请创建一个名为phpunit.bat的文件,并将其放置在您的PATH中,然后填写以下内容: php /c/Users/
)