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 数据库或对事务模型的扩展,用于持久化事务数据。事务数据可以存储在您喜欢的任何地方,但为了方便,内置了一个仅测试于 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\\AppData\Roaming\Composer\vendor\phpunit\phpunit

然后,您可以将其目录添加到PATH环境变量中,并且应该能够在命令行中调用 phpunit.bat

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

(如果您使用的是Console2,请创建一个名为phpunit.bat的文件,并将其放置在您的PATH中,然后填写以下内容: php /c/Users//AppData/Roaming/Composer/vendor/phpunit/phpunit/phpunit.php $*