如何在 PHP 中获取 Alertpay 返回的 RETURNCODE
结论
仅凭用户被重定向到 http://mywebsite.com/success,无法确认付款是否成功。
如果 AlertPay 将 RETURNCODE 作为表单字段提交到该地址,可以通过 $_POST['RETURNCODE'] 读取。如果它通过查询字符串传递,则应使用 $_GET['RETURNCODE']。不过,在开通 premium 权限前,仍须通过 AlertPay 的 IPN(即时付款通知)或官方验证接口核实交易,不能只相信返回页面收到的参数。
获取 RETURNCODE
首先查看 AlertPay 开发文档,确认参数的提交方式。
POST 方式
<?php
$returnCode = filter_input(INPUT_POST, 'RETURNCODE');
if ($returnCode === null) {
http_response_code(400);
exit('Missing RETURNCODE');
}
$returnCode = trim($returnCode);
if ($returnCode === '202') {
echo 'Payment response indicates success.';
} else {
echo 'Payment failed or is still pending. Return code: '
. htmlspecialchars($returnCode, ENT_QUOTES, 'UTF-8');
}
也可以直接读取 $_POST:
$returnCode = isset($_POST['RETURNCODE'])
? trim((string) $_POST['RETURNCODE'])
: null;
GET 方式
如果浏览器返回的地址如下:
http://mywebsite.com/success?RETURNCODE=202
应读取 $_GET:
<?php
$returnCode = filter_input(INPUT_GET, 'RETURNCODE');
if ($returnCode !== null && trim($returnCode) === '202') {
echo 'Payment response indicates success.';
}
参数名的大小写应以实际传输内容为准。PHP 数组键区分大小写,所以 RETURNCODE、returncode 和 ReturnCode 是三个不同的键。
查看实际收到的数据
如果不确定 AlertPay 使用 POST 还是 GET,可以在测试环境中记录请求,但不要将完整的支付数据直接显示给访问者。
<?php
error_log('AlertPay GET: ' . print_r($_GET, true));
error_log('AlertPay POST: ' . print_r($_POST, true));
如果两者都是空的,RETURNCODE 可能不是返回页面中的参数,而是服务器端 API 响应里的字段。这种情况下,应解析 API 的原始响应,而不是继续从 $_GET 或 $_POST 中查找。
例如,接口返回 URL 编码的数据时,可以这样处理:
<?php
$rawResponse = 'RETURNCODE=202&DESCRIPTION=Success';
parse_str($rawResponse, $response);
$returnCode = isset($response['RETURNCODE'])
? trim((string) $response['RETURNCODE'])
: null;
如果接口返回 XML 或 JSON,则按官方文档规定的格式选择相应的解析器。
不要在返回页面直接升级账户
返回页面由会员的浏览器访问,用户可能会:
- 手动打开
/success; - 修改查询字符串中的
RETURNCODE; - 在付款尚未完成时关闭页面;
- 多次刷新或重复提交请求;
- 完成付款后未能返回网站。
因此,下面的处理方式并不安全:
if ($_GET['RETURNCODE'] === '202') {
upgradeCurrentUserToPremium();
}
攻击者只需访问:
http://mywebsite.com/success?RETURNCODE=202
便可能绕过付款流程。
推荐的付款确认流程
安全的付款确认应由服务器端通知驱动:
- 创建付款时生成唯一订单号,并以
pending状态保存订单。 - 将订单号写入 AlertPay 支持的商户参考字段。
- 付款完成后,由 AlertPay 向网站的 IPN 地址发送服务器端通知。
- 按官方协议验证通知中的签名、安全码或验证响应。
- 核对交易状态、商户账户、金额、币种和订单号。
- 检查该交易是否已经处理。
- 在同一个数据库事务中将订单标记为已支付,并升级对应用户。
/success页面只向用户展示订单状态。
下面是示意代码。字段名称必须替换为当前 AlertPay 接口文档规定的实际名称:
<?php
$expectedSecurityCode = 'YOUR_PRIVATE_SECURITY_CODE';
$securityCode = isset($_POST['ap_securitycode'])
? trim((string) $_POST['ap_securitycode'])
: '';
$status = isset($_POST['ap_status'])
? trim((string) $_POST['ap_status'])
: '';
$orderId = isset($_POST['apc_1'])
? trim((string) $_POST['apc_1'])
: '';
$amount = isset($_POST['ap_totalamount'])
? trim((string) $_POST['ap_totalamount'])
: '';
$currency = isset($_POST['ap_currency'])
? trim((string) $_POST['ap_currency'])
: '';
$reference = isset($_POST['ap_referencenumber'])
? trim((string) $_POST['ap_referencenumber'])
: '';
if (!hash_equals($expectedSecurityCode, $securityCode)) {
http_response_code(403);
exit('Invalid notification');
}
$order = findOrderById($orderId);
if ($order === null) {
http_response_code(404);
exit('Unknown order');
}
if (
$status !== 'Success'
|| $currency !== $order->currency
|| !amountsAreEqual($amount, $order->amount)
|| $reference === ''
) {
http_response_code(400);
exit('Payment validation failed');
}
if (!$order->isPaid()) {
markOrderPaidAndUpgradeUser($order, $reference);
}
http_response_code(200);
echo 'OK';
这些 ap_* 字段常见于旧式 AlertPay/Payza 集成,但不同产品和接口版本使用的字段可能并不相同,应以当前使用的开发文档和实际通知数据为准。即使响应中出现 RETURNCODE=202,也不能省略签名、安全码或服务器端验证。
金额比较与重复通知
不要直接用浮点数比较金额。可以将金额转换为最小货币单位,或者使用定点小数库:
<?php
function amountsAreEqual(string $received, string $expected): bool
{
return bccomp($received, $expected, 2) === 0;
}
这段代码依赖 PHP 的 BCMath 扩展。小数位数应按币种确定,并非所有币种都是两位小数。
支付平台通常会重试通知,因此处理逻辑必须具备幂等性。可以在数据库中为平台交易编号设置唯一约束,并在同一事务中更新订单和会员状态,防止同一笔交易重复入账。
RETURNCODE 只是支付流程中的一个响应字段,不能单独作为付款凭证。只有服务器端验证通过,并且金额、币种、订单号和交易状态全部匹配后,才能升级账户。
备注:内容仅供参考。
