如何使用自定义 PHP 集成 POLi payment?
结论
GenerateURL Request 是 POLi 支付流程的第一步,只负责创建 Payment URL,不能代表整个 PHP 集成已经完成。
完整流程通常包括:
- 在服务端创建订单并调用
GenerateURL。 - 解析 POLi 返回的 Payment URL。
- 将用户重定向到该 URL。
- 接收支付完成后的浏览器返回请求或服务器通知。
- 调用 POLi 的交易查询或验证接口,确认支付状态。
- 核对金额、币种和商户订单号,确认一致后再将本地订单标记为已支付。
不能因为用户回到了成功页面就认定付款成功,因为返回地址可能被直接访问,也可能被伪造。
先确认接口资料是否仍然有效
问题中的 PDF 很可能对应某个特定时期的 POLi PaymentAPI。开发前,需要通过当前有效的商户后台或官方技术支持确认以下信息:
- PaymentAPI REST URL
- XML 根节点及各字段名称
- 身份验证方式
- 测试和生产环境地址
- 返回 XML 的结构
- 支付状态查询接口
- 通知、取消和返回地址的配置方式
- 当前账户所在地区是否仍支持该产品
示例里的 PriceBusterDVD、MerchantPassword 和接口地址都只是占位内容,不能直接用于生产环境。原问题给出的 XML 还缺少标签,本身并不是可以直接发送的完整 XML 文档。
PHP 实现方式
可以使用 PHP 的 cURL 扩展从服务端调用接口。商户密码不能暴露在浏览器 JavaScript、HTML 表单或其他前端代码中。
下面是一套完整的请求框架。XML 节点名称需要替换为当前 POLi 文档规定的准确名称。如果 PDF 中提供了完整 XML,应以其中的 schema 为准。
<?php
declare(strict_types=1);
$endpoint = 'https://YOUR-POLI-ENDPOINT.example/GenerateURL';
$merchantCode = getenv('POLI_MERCHANT_CODE');
$merchantPassword = getenv('POLI_MERCHANT_PASSWORD');
if (!$merchantCode || !$merchantPassword) {
throw new RuntimeException('POLi credentials are not configured.');
}
$orderReference = 'ORDER-20260901-0001';
$amount = '123.11';
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><GenerateURLRequest/>');
/*
* 以下节点名称仅展示构造 XML 的方法。
* 请按照当前 POLi 接口文档替换为准确的节点名称和层级。
*/
$xml->addChild('MerchantCode', htmlspecialchars($merchantCode, ENT_XML1));
$xml->addChild('MerchantPassword', htmlspecialchars($merchantPassword, ENT_XML1));
$xml->addChild('RequestType', 'Manual');
$xml->addChild('Amount', $amount);
$xml->addChild(
'MerchantReference',
htmlspecialchars($orderReference, ENT_XML1)
);
$xml->addChild('Notification', 'No');
$xml->addChild('SelectedFICode', 'No');
$requestBody = $xml->asXML();
if ($requestBody === false) {
throw new RuntimeException('Unable to build POLi request XML.');
}
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $requestBody,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: text/xml; charset=UTF-8',
'Accept: text/xml',
'Content-Length: ' . strlen($requestBody),
],
]);
$responseBody = curl_exec($ch);
if ($responseBody === false) {
$message = curl_error($ch);
curl_close($ch);
throw new RuntimeException('POLi request failed: ' . $message);
}
$httpStatus = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($httpStatus < 200 || $httpStatus >= 300) {
throw new RuntimeException(
sprintf('POLi returned HTTP %d: %s', $httpStatus, $responseBody)
);
}
libxml_use_internal_errors(true);
$responseXml = simplexml_load_string($responseBody);
if ($responseXml === false) {
throw new RuntimeException('POLi returned invalid XML.');
}
/*
* PaymentURL 的实际 XPath 取决于接口返回结构。
* 请根据当前文档调整这里的节点路径。
*/
$paymentUrl = trim((string) ($responseXml->PaymentURL ?? ''));
if ($paymentUrl === '') {
throw new RuntimeException(
'POLi response does not contain a Payment URL.'
);
}
if (filter_var($paymentUrl, FILTER_VALIDATE_URL) === false) {
throw new RuntimeException('POLi returned an invalid Payment URL.');
}
header('Location: ' . $paymentUrl, true, 303);
exit;
如果当前接口使用 HTTP Basic Authentication、API Token 或自定义认证头,需要按文档补充相应的认证选项。例如,只有文档明确要求使用 Basic Authentication 时,才应加入:
curl_setopt($ch, CURLOPT_USERPWD, $merchantCode . ':' . $merchantPassword);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
XML 中包含商户密码,并不代表接口一定还需要 Basic Authentication。两种认证方式是否同时使用,应以对应版本的官方接口说明为准。
正确处理金额和订单号
不要通过浮点运算直接生成金额。例如:
$amount = number_format(123.11, 2, '.', '');
更稳妥的方式是让订单系统用最小货币单位保存金额。例如,将 123.11 保存为 12311,发送请求时再进行格式化:
$amountInCents = 12311;
$amount = sprintf('%d.%02d', intdiv($amountInCents, 100), $amountInCents % 100);
商户参考号必须对应一笔唯一的本地订单,并由服务端生成。发起支付前,应先在数据库中保存:
- 本地订单号
- 预期金额及币种
- 初始状态,例如
pending - POLi 返回的交易标识
- 创建时间和后续状态变化
支付完成后必须再次验证
用户完成操作后,POLi 可能会将浏览器重定向回商户页面,也可能向商户服务器发送通知。无论采用哪种方式,都不能直接信任请求中的 status=success、金额或订单号。
回调可以按以下逻辑处理:
<?php
declare(strict_types=1);
$transactionToken = $_GET['token'] ?? '';
if ($transactionToken === '') {
http_response_code(400);
exit('Missing transaction token.');
}
/*
* 1. 使用服务端保存的凭据调用 POLi 状态查询接口。
* 2. 从官方响应中取得交易状态、金额、币种和商户参考号。
* 3. 查找对应的本地订单。
* 4. 对比预期金额、币种和订单号。
* 5. 仅在官方状态明确表示支付成功时更新订单。
* 6. 使用数据库事务或条件更新,保证重复通知不会重复入账。
*/
现有信息没有提供状态接口 URL、Token 参数名称和表示支付成功的状态值,因此这些内容不能自行补全。应直接采用对应 PDF 或商户后台给出的字段。
安全和上线注意事项
- 将商户密码保存在环境变量或密钥管理服务中,不要写入代码仓库。
- 生产环境必须使用 HTTPS,并保留证书校验,不要设置
CURLOPT_SSL_VERIFYPEER => false。 - 正确转义 XML 内容,避免订单备注中的
&、<等字符破坏请求。 - Payment URL 必须取自 POLi 的成功响应,不要自行拼接。
- 记录 HTTP 状态码、POLi 交易号和错误码,但不要记录密码、完整认证头或敏感银行信息。
- 回调处理需要支持重复调用,避免同一笔交易被重复发货或记账。
- 为支付请求设置连接超时和总超时,并为失败情况设计安全的重试机制。
- 支付失败、超时或状态未知时,不能自动按支付成功处理。
- 正式上线前,需要分别测试成功、取消、失败、超时、重复通知、金额不一致和网络中断等情况。
PDF 中的 GenerateURL Request 可以作为发起支付的依据,但还需要补齐准确的 XML 标签、接口地址、认证方式、响应解析和支付状态验证,才能形成可用的 PHP 集成。
备注:内容仅供参考。
