inspirecz/csob-eapi-paygate

PHP 客户端库,用于轻松集成 ČSOB 支付网关

dev-master 2022-08-09 13:19 UTC

This package is auto-updated.

Last update: 2024-09-11 18:12:36 UTC


README

使用这个库可以轻松地将 ČSOB 支付网关 集成到您的 e-shop 或其他 PHP 应用程序中,而无需直接与 API 交互、调用方法、验证签名等。

英文说明在此.

有关支付网关 API、密钥生成和处理的详细信息,请访问 https://github.com/csob/paymentgateway。测试卡信息请在此 wiki 查看

注意!经常有人在这里犯错误,我最好在这里指出。这里使用的是您的私钥和 银行的公钥 不是反过来。不是您的公钥。

更新

ČSOB API 1.8

2019 年秋季,银行发布了 API 版本 1.8,除了添加了几个新功能外,还引入了一些小的、但向后不兼容的更改。因此,如果您在自己的应用程序中使用此库并且使用的是最新版本的地址 GatewayUrl::PRODUCTION_LATEST,那么请注意,将库更新到 1.8 版本也会更新到 API 1.8,因此某些功能可能会有所不同。

  • 库现在支持 ČSOB eAPI 1.8 和 SHA 256 签名。对于库中有自己方法的 API 方法,例如 paymentInit 或 customerInfo,API 1.8 中的所有更改都已考虑在内。如果您通过 customRequest() 手动调用某些更高级的方法,请确保 API 没有发生更改。
  • 在 Config 中可以显式设置库要使用的 API 地址和版本。如果不指定任何内容,则将自动设置。
  • 由于无法充分测试新的支付方式(MallPay,ApplePay),因此库中没有针对它们的特定方法。因此,请使用通用方法 customRequest(),手动输入参数,库将至少发送请求并验证响应。如果有人为这些支付开发并测试了库中的单个方法,请将其提交为 PR,我非常愿意将其纳入。
  • 从版本 1.8 开始,针对 ČSOB 和 ERA 的支付按钮有一个新的方法 - buttonInit()。对于 API < 1.8,请继续使用 paymentButton()。在 1.8 和 1.8.1 版本中,buttonInit() 存在一个错误,该错误已在 1.8.2 版本中修复。
  • 在 Config 类中添加了设置 $sslVersion 的选项,用于显式指定与银行通信时使用的 SSL/TLS 版本。

默认地址是当前版本的测试支付网关(现在为 1.8 - GatewayUrl::TEST_1_8)。

建议使用 GatewayUrl 类的常量,该常量包含各个版本 API 的 URL。

$config->url = GatewayUrl::TEST_1_7;
$config->url = GatewayUrl::PRODUCTION_1_8;
$config->url = GatewayUrl::PRODUCTION_LATEST;

安装

最简单的方法是使用 Composer 安装

composer require ondrakoupil/csob-eapi-paygate

如果您不使用 Composer,只需将 dist/csob-client.php 文件复制到某个位置并包含它即可 - 它包含所有必要的类。

使用

除了这个库之外,您还需要

  • 商户 ID - 可以在 keygen 页面上生成匿名 ID,或者使用银行分配的 ID
  • 用于签名和验证签名的密钥 - 您可以在 keygen 上获取。在实施过程中,您只需使用自己的私钥。将公钥通过 keygen 发送给银行,然后您可以将其忘记。
  • 银行公钥 - 可从 ČSOB的Github 下载。注意,测试环境和生产环境的公钥不同。

库由以下类组成

  • Client - 我们将主要使用的顶层类
  • Config - 用于与门通信的参数设置类,包括密钥、Merchant ID等,以及各种默认值
  • Payment - 表示一笔支付
  • Crypto - 负责签名和验证签名
  • Extension - 表示扩展的类。可以直接使用Extension类,或者使用单个专门的类。

所有类都在namespace OndraKoupil\Csob 中,因此需要在文件开头使用use或始终使用包含namespace的完整类名。以下示例假设您已经使用了use

设置

首先需要创建一个Config对象并设置其中的必要值。然后将其传递给Client对象,并调用其对应API提供的方法。

$config = new Config(
	"My Merchant ID",
	"path/to/my/private/key/file.key",
	"path/to/bank/public/key.pub",
	"My shop name",

	// Adresa, kam se mají zákazníci vracet poté, co zaplatí
	"https://www.my-eshop.cz/return-path.php",

	// URL adresa API - výchozí je adresa testovacího (integračního) prostředí,
	// až budete připraveni přepnout se na ostré rozhraní, sem zadáte
	// adresu ostrého API. Nezapomeňte také na ostrý veřejný klíč banky.
	GatewayUrl::TEST_LATEST
);

$client = new Client($config);

注意 - 这里使用的是您的私钥和银行的公钥。 同时,请记住,测试API和生产API的公钥是不同的。

Config还允许设置其他参数和默认值,请参阅文档

连接测试

为了验证连接是否正常以及请求是否正确签名,可以使用testGetConnection()和testPostConnection()方法,这些方法调用API的echo方法。

try {
	$client->testGetConnection();
	$client->testPostConnection();

} catch (Exception $e) {
	echo "Something went wrong: " . $e->getMessage();
}

创建新支付(payment/init)

为了创建新的支付,需要创建一个Payment对象,设置所需的值,然后将其传递给paymentInit()。如果一切正常,API将为支付分配PayID。需要将其保存起来,稍后将用于调用其他方法。

使用$payment->addCartItem()将项目添加到订单中。在当前版本中,支付必须有一个或两个项目,在未来的版本中,这种限制将得到改变。

注意,所有字符串都应该是UTF-8编码。如果您使用的是其他编码,则需要在可能包含变音符号的地方(尤其是购物车中的项目名称)使用iconv函数进行转换。

$payment = new Payment("1234");
$payment->addCartItem("Zakoupená věcička", 1, 10000);

$response = $client->paymentInit($payment);

$payId = $payment->getPayId();
$payId = $response["payId"];

这是必要的最小值 - 在$payment对象中可以设置更多。注意,价格以货币的基本单位的十分之一表示(例如,10000表示100元)。

调用paymentInit()后,将为$payment对象设置其PayID,可以从getter中读取,或者可以从返回的数组中获取。

支付处理(payment/process)

在成功创建支付后,需要将客户的浏览器重定向到支付网关,其地址由getPaymentProcessUrl()生成。这里还有一个redirectToGateway()方法,可以自动完成重定向。

$url = $client->getPaymentProcessUrl($payment);
redirectBrowserTo($url);  // fiktivní funkce pro přesměrování

// NEBO

$client->redirectToGateway($payment);
terminateApp();  // fiktivní funkce pro ukončení skriptu

可以使用之前的调用中的$payment对象或PayID作为字符串。

客户返回

在客户在支付网关输入必要的详细信息并经过验证和批准后,网关将返回到您在Config或Payment对象中设置的Return URL。在此URL上,您应该使用paymentStatus()验证支付状态,或者简单地使用receiveReturningCustomer()方法处理传入的数据,该方法将检查传入数据的签名并从中提取有用的值。

$response = $client->receiveReturningCustomer();

if ($response["paymentStatus"] == 7) {
	// nebo také 4, záleží na nastavení closePayment
	echo "Platba proběhla, děkujeme za nákup.";

} else {
	echo "Něco se pokazilo, sakra...";
}

有关支付状态的详细信息,请参阅支付网关wiki

支付状态验证(payment/status)

任何时候都可以简单地了解支付处于何种状态

$status = $client->paymentStatus($payId);

如果需要比状态编号更多的详细信息,请将第二个参数$RETURN_STATUS_ONLY设置为false,该方法将返回包含各种详细信息的数组。

确认、取消、退款

方法 paymentReverse() 用于取消尚未处理的支付,paymentClose() 用于确认支付,而 paymentRefund() 则用于将已完成的支付退还给付款人。

注意,支付必须处于正确的状态,否则将引发错误并抛出异常。如果将第二个参数 $ignoreWrongPaymentStatusError 设置为 true,则将忽略此特定错误,并仅返回 null。其他所有错误仍然会抛出异常。

$client->paymentReverse($payId);
$client->paymentClose($payId);
$client->paymentRefund($payId);

从 API 1.5 开始,支付网关允许使用 paymentRefund() 方法仅部分退款,或者使用 paymentClose() 方法确认小于原始授权金额的交易。这些方法的第三个参数可以指定要退回的基础货币单位的金额(注意!)

// Potvrdit transakci jen na 100 Kč
$client->paymentClose($payId, false, 10000);

// Vrátit 100 Kč
$client->paymentRefund($payId, false, 10000);

paymentRefund() 有时在测试环境中返回 HTTP 状态码 500,导致抛出异常。根据 此问题,这是支付网关测试环境中的一个 bug,目前尚未解决。

客户信息(customer/info)

方法 customerInfo() 用于验证具有指定 ID(例如电子邮件)的客户是否曾使用信用卡支付,如果是,则可以采取某些措施(例如打印个性化消息)。

$hasCards = $client->customerInfo($someCustomerId);
if ($hasCards) {
	echo "Chcete zase zaplatit kartou?";
} else {
	echo "Nabízíme tyto možnosti platby: ...";
}

API 1.8 将此方法重命名为 echo/customer,库将自动选择合适的端点,但在 PHP 中始终调用 customerInfo()

Payment/checkout

这是一个尚未公开的功能。它允许在您的网站上显示最小化 iframe 并在 iframe 中完成整个支付和订单处理,而不是跳转到支付网关。

要激活此功能,请联系 ČSOB 并请求激活此功能。在此过程中,他们还将向您发送更详细的文档。

功能激活后,您可以调用 payment/checkout 而不是 payment/process

如果您的 merchantId 没有启用此功能,网关将显示错误消息。

$url = $client->getPaymentCheckoutUrl($payment, $oneClickPaymentCheckbox);
redirect($url);

$payment 可以是 Payment 对象或简单的 PayID,与其他方法类似。

对于 $oneClickPaymentCheckbox,请使用 PHPDoc 源代码方法中描述的选项之一。

其他参数在方法文档中有所描述。

感谢 @rootpd。

支付按钮

对于 API < 1.8,使用 paymentButton() 方法,对于 API >= 1.8,使用 buttonInit() 方法,这些方法用于所谓的支付按钮。更详细的参数在两个方法的文档中都有描述。

这些方法返回包含不同数据的数组,包括 redirect,其中包含要将用户重定向到的地址。因此,不要使用 redirectToGateway() 或类似的方法,而是直接将用户重定向到银行返回的地址。

重复支付

从 API 1.5 开始,可以进行重复支付。具体如何操作,请参阅 ČSOB Wiki。大致过程如下

  • 您将让客户授权支付模板,就像正常进行整个支付过程一样,但是在调用 paymentInit() 之前,将 $payOperation 设置为 Payment::OPERATION_ONE_CLICK,最好是调用 $payment->setOneClickPayment(true)
  • 然后客户将输入卡号,密码并进行 3D 验证,就像正常支付一样
  • 您将保存 PayID,以便可以引用此授权交易
  • 从 API 版本 1.8 开始,存在 paymentOneClickEcho() 用于验证原始 PayID 是否仍然可用。
  • 然后您可以随时调用带有原始交易 PayID 和新 Payment 对象的 paymentOneClickInit() 方法。这将创建新的支付。通过调用 paymentOneClickStart() 来执行支付。
  • 新支付将获得自己的 PayID,可以像处理任何其他支付一样处理它
  • API 1.8 修改了端点名称,库会自动处理,在 PHP 中始终调用 paymentOneClickInit() 或 paymentOneClickStart()。同时,在 paymentOneClickInit 中增加了必填参数 clientIp

日志记录

Client 包含内置的简单日志记录功能。一条日志用于记录业务级别的消息(例如:“支付 XYZ 成功”),另一条日志(traceLog)记录与 API 的详细通信和各种技术细节。

可以简单地指定消息记录到文件中的路径,或者提供回调函数,将消息转发到应用程序使用的任何日志记录器。可以在 Client 对象的构造器中或使用 setter 设置日志记录。

$client->setLog("some/file/log.txt");
$client->setTraceLog(function($message) use ($myLogger) {
	$myLogger->log($message);
});

自定义请求

如果需要发送 API 方法的请求,该方法在此库中没有特别实现(例如,Masterpass、ApplePay 或 MallPay 方法),可以使用 customRequest() 方法。只需注意输入数据的顺序和响应数据在字符串中拼接的顺序,用于验证响应签名的顺序。

$client->customRequest(

    // URL, jenom konec za společnou adresou API, např. "payment/init"
    $methodUrl,                              
    
    // Array se vstupními daty. Pořadí položek v array je důležité.
    // Na vhodná místa lze vložit prázdné dttm a merchantId, doplní se automaticky.
    $inputPayload,                          
    
    // Array s názvy políček v odpovědi v požadovaném pořadí dle dokumentace.
    // U vnořených objektů a polí lze pracovat s tečkou.
    // Například: array('payId', 'dttm', 'resultCode', 'resultMessage', 'redirect.method', 'redirect.url')
    $expectedOutputFields = array(), 
    
    // Volitelně nějaké extensions
    $extensions = array(), 
    
    $method = "POST",     
    
    // Zalogovat vždy podrobně celou návratovou hodnotu z API?
    $logOutput = false,     
    
    // Pokud z nějakého důvodu selhává ověření podpisu, lze ho takto úplně deaktivovat.
    // Nicméně pak je nutné ručně takovou situaci ošetřit.
    $ignoreInvalidReturnSignature = false
)

$expectedOutputFields 中,如果某个值以 ? 开头,则将其视为非必需的,如果银行响应中未包含它,则不会将其包含在基本签名中。如果不以 ? 开头,则可以将空字符串放入基本签名中。

扩展

扩展通过 Extension 类和可选的独立类(目前仅适用于 EET)实现。可以将此类对象附加到每个调用的方法。然后将在请求中添加额外的数据,并自动验证响应签名的有效性。

每个扩展都有自己的扩展 ID(由银行文档定义)。

如果扩展需要向 请求 添加一些数据,则需要调用 setInputData() 并将额外数据作为数组传递给请求。数组中元素的顺序很重要,它决定了签名字符串和签名的顺序。始终查看文档,了解参数的顺序,并遵守它。可以随意将 dttmextension 留空(false 或 null),值会自动填充,但必须将它们放在数组中的适当位置。

作为替代,可以分离类并实现自己的 getRequestSignatureBase() 方法,该方法应返回用于签名的字符串。

如果扩展需要向 API 响应 添加一些数据,则可以通过 getResponseData() 方法访问这些数据。

可以使用 setExpectedResponseKeysOrder() 设置响应签名验证。将包含响应字段名称的数组传递给此方法,这些字段按签名字符串中的顺序排列。或者,可以分离 Extension 到自己的类中,并实现自己的 verifySignature() 方法。

如果无法验证响应签名,则可以通过 setStrictSignatureVerification(false) 禁用特定扩展的签名验证。调用 API 方法后,可以通过 isSignatureCorrect() 检查签名是否正确,如果不正确,可以自行处理。

对于一次方法调用,可以传递多个扩展。只需将 Extension 对象的数组传递给 Client 对象的相应参数即可,而不仅仅是单个对象。

DatesExtension

如果激活了 trxDates 扩展,则可以在 paymentStatus() 方法中传递 DatesExtension 类的实例。调用该方法后,可以从 DatesExtension 中读取所需数据,作为 DateTime 对象。

$extension = new DatesExtension();
$status = $client->paymentStatus($payment, true, $extension);
echo $extension->getCreatedDate()->format("j. n. Y");

可用的方法有 getCreatedDate()getSettlementDate()getAuthDate(),它们返回 DateTime 或 null(如果响应中未提供该日期),请注意,settlementDate 的精度仅到天,不是秒。

CardNumberExtension

如果激活了 maskClnRP 扩展,则可以在 paymentStatus() 方法中传递 CardNumberExtension 类的实例。调用该方法后,可以从 CardNumberExtension 中读取卡号的掩码及其过期日期。但请记住,此扩展仅适用于“一键”支付。

$extension = new CardNumberExtension();
$status = $client->paymentStatus($payment, true, $extension);
echo $extension->getMaskedCln() . ' ' . $extension->getExpiration();

可用的方法有 getMaskedCln()getLongMaskedCln()getExpiration()

EET

由于EET扩展较为复杂,因此已经为各个API方法准备好了专门的类。

  • EETInitExtension用于payment/init和payment/oneclick/init。
  • EETCloseExtension用于payment/close。
  • EETRefundExtension用于payment/refund。
  • EETStatusExtension用于payment/status。

在通过payment/init初始化支付时,在创建扩展对象时需要传递EETData类的对象。填充其公共变量所需的价值(三个是必填的,需要在构造函数中填写)。各个变量的意义在Wiki ČSOB这篇文章中有详细描述。您还可以使用$verificationMode选择是否仅在验证(测试)模式下将数据发送到EET。

注意,EETData中的价格以克朗为单位,而Payment类中的价格以欧分为单位。

确认或退款(close和refund)现在可以不使用参数完成,在这种情况下将使用init方法中传递的数据。

要查询支付状态,请使用EETStatusExtension扩展,它与payment/status一起发送。调用paymentStatus()后,您可以通过getReport()和可选的getCancels()从扩展对象中读取结果。它们返回包含详细信息的EETReport类对象或对象。还提供了快捷方式getFIK()getEETStatus()getBPK()getPKP(),通过这些方式您可以快速获取最常见的响应数据。

请记住,要使用EET扩展,您需要在银行中启用此服务。

示例(省略命名空间)

// $client mám vytvořený podle postupu v předchozích bodech

// Vytvoříme si payment, klasicky
$payment = new Payment(12345);
$payment->addCartItem('Jedna položka', 1, 50000); 
$payment->addCartItem('Druhá s nižší DPH', 1, 50000); 

// Vytvoříme data pro EET. Jen parametry v konstruktoru jsou povinné.
$eetData = new EETData(123, 'abc123', 1000);
$eetData->priceStandardVat = 413.22;
$eetData->vatStandard = 86.78;
$eetData->priceFirstReducedVat = 454.55;
$eetData->vatFirstReduced = 45.45;

// Vytvoříme extension pro payment/init v ověřovacím režimu
$extensionInit = new EETInitExtension($eetData, true);

// Zavoláme payment/init a odešleme prohlížeč na bránu
$client->paymentInit($payment, $extensionInit);
$url = $client->getPaymentProcessUrl($payment);

// Nyní bychom měli přesměrovat prohlížeč na $url, nechat
// zákazníka zadat platbu, přijmout vrácená data atd.
// To nyní jakoby přeskakuji.

$extensionStatus = new EETStatusExtension();
$status = $client->paymentStatus($payment, true, $extensionStatus);

echo "<p>Stav platby je: $status</p>";
echo "<p>Stav odeslání do EET: " . $extensionStatus->getEETStatus() . "</p>";
echo "<p>FIK: " . $extensionStatus->getFIK() . "</p>";

// Mnoho dalších dat najdete v $extensionStatus->getReport()

有问题?

如果您遇到了bug,某些功能不正常或有改进建议,请添加issue或直接联系我 :-)