PAYATHON 2026

如何用 PHP 创建 payment gateway return URL

支付阿杰

结论

将 return URL 设置为服务器上的 PHP 文件,例如:

https://example.com/payment/return.php

PHP 可以直接接收 payment gateway 通过 POST 表单或 GET 查询参数发送的数据,不需要 JavaScript,也不要继续使用普通的 something.html 文件。

如果暂时无法确认 gateway 使用哪种请求方式,可以先同时兼容 POST 和 GET。不过在正式上线前,仍需向 payment gateway 确认请求方式、checkSum 算法以及成功响应的具体要求。

可直接使用的 PHP 示例

<?php
declare(strict_types=1);

header('Content-Type: text/html; charset=UTF-8');

/**
 * 优先读取 POST,其次读取 GET。
 * 不使用 $_REQUEST,避免 Cookie 或 PHP 配置影响参数来源。
 */
function getGatewayParameter(string $name): ?string
{
    if (isset($_POST[$name]) && !is_array($_POST[$name])) {
        return trim((string) $_POST[$name]);
    }

    if (isset($_GET[$name]) && !is_array($_GET[$name])) {
        return trim((string) $_GET[$name]);
    }

    return null;
}

$status              = getGatewayParameter('status');
$customerReferenceNo = getGatewayParameter('customerReferenceNo');
$referenceNo         = getGatewayParameter('referenceNo');
$merchantId          = getGatewayParameter('merchantId');
$amount              = getGatewayParameter('amount');
$checkSum             = getGatewayParameter('checkSum');

$requiredValues = [
    $status,
    $customerReferenceNo,
    $referenceNo,
    $merchantId,
    $amount,
    $checkSum,
];

$hasMissingData = in_array(null, $requiredValues, true)
    || in_array('', $requiredValues, true);

$message = 'ERROR. NO DATA FOUND';
$messageClass = 'error';

if (!$hasMissingData) {
    /*
     * 必须先按照 payment gateway 文档验证 checkSum。
     * 下面只能演示处理位置,不能代替真实验签。
     */
    $isValidCheckSum = true;

    if (!$isValidCheckSum) {
        $message = 'Invalid payment response.';
    } elseif (!preg_match('/^-?\d{1,4}$/', $status)) {
        $message = 'Invalid transaction status.';
    } elseif ($status === '0') {
        /*
         * 验签成功后再保存数据库。
         *
         * 建议使用 referenceNo 或 gateway 提供的唯一交易编号建立
         * UNIQUE 约束,防止刷新页面或重复回调造成重复入库。
         */

        $message = sprintf(
            'Your transaction is successful and your transaction reference no for any further communication is %s.',
            $referenceNo
        );
        $messageClass = 'success';
    } elseif ((int) $status < 0) {
        $message = sprintf(
            'Your transaction failed and your transaction reference no for any further communication is %s.',
            $referenceNo
        );
    } else {
        $message = 'Unknown transaction status.';
    }
}

$safeMessage = htmlspecialchars(
    $message,
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Payment Result</title>
    <style>
        body {
            margin: 0;
            padding: 40px 20px;
            font-family: Arial, sans-serif;
            background: #f5f5f5;
            color: #222;
        }

        .result {
            max-width: 700px;
            margin: 60px auto;
            padding: 30px;
            text-align: center;
            background: #fff;
            border-radius: 8px;
        }

        .success {
            color: #16794a;
        }

        .error {
            color: #b42318;
        }
    </style>
</head>
<body>
    <main class="result">
        <p class="<?= $messageClass ?>">
            <?= $safeMessage ?>
        </p>
    </main>
</body>
</html>

将文件保存为 return.php,部署到能够运行 PHP 的 Web 服务器,然后把该文件的 HTTPS 地址提供给 payment gateway。

POST 和 GET 分别如何接收

如果 gateway 使用 POST,可以从 $_POST 中读取参数:

$status = $_POST['status'] ?? null;

如果使用 GET,例如:

https://example.com/payment/return.php?status=0&referenceNo=ABC123

则从 $_GET 中读取参数:

$status = $_GET['status'] ?? null;

示例里的 getGatewayParameter() 会先读取 POST,如果没有找到参数,再读取 GET,因此可以兼容两种方式。

不要直接使用 $_REQUEST。它可能同时包含 GET、POST 和 Cookie 数据,参数的优先级还会受到 PHP 配置影响。

排查接入问题时,最好记录请求方法,但不要将完整的支付参数或敏感信息写入公开日志:

$requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'UNKNOWN';

为什么不能直接使用 something.html

普通的 .html 文件通常只会作为静态内容返回,服务器不会执行其中的 PHP。应将页面改为:

something.php

同时确认服务器已经正确配置 PHP。

有些服务器确实可以配置为在 .html 文件中执行 PHP,但通常没有必要这样做,也会增加维护和安全方面的问题。这里同样不需要 JavaScript,gateway 提交的数据可以直接由服务器端 PHP 接收。

field specification 的可能含义

这些缩写通常用来描述字段的最大长度和允许使用的字符:

  • 4N:通常表示最多 4 位数字。
  • 30AN:通常表示最多 30 个字母或数字。
  • 32AN:通常表示最多 32 个字母或数字。
  • 10AN:通常表示最多 10 个字母或数字。
  • 64AN:通常表示最多 64 个字母或数字。
  • 9.2AN:这种写法并不标准,可能表示金额包含 9 位整数和 2 位小数,也可能是该 gateway 自己定义的格式。

N 一般表示 numeric,AN 一般表示 alphanumeric。不过,不同 payment gateway 对字符范围的定义可能不同,有些还允许空格、连字符或其他符号。

特别是 amount 的 9.2AN,必须以该 gateway 的正式文档或技术支持答复为准,不能只根据缩写推断。

数据入库前必须验证

收到参数并不代表交易确实成功。任何人都可以通过浏览器自行构造请求,例如:

https://example.com/payment/return.php?status=0&referenceNo=FAKE

因此,不能只检查:

$status === '0'

就更新订单状态。至少要完成以下检查:

  1. 验证 checkSum,并严格使用 gateway 规定的字段顺序、密钥和哈希算法。
  2. 检查 merchantId 是否属于当前商户。
  3. 根据 customerReferenceNo 查找本地订单。
  4. 比较回传金额与订单应付金额。
  5. 确认订单尚未成功处理,避免重复回调。
  6. 如果 gateway 提供订单查询 API,必要时主动查询并再次确认交易状态。

校验 checksum 时,应使用常量时间比较:

$isValidCheckSum = hash_equals($expectedCheckSum, $checkSum);

$expectedCheckSum 必须严格按照 gateway 文档规定的方式生成。字段拼接顺序、大小写、金额格式或字符编码只要有一处不同,都可能导致校验失败。

数据库处理建议

写入数据库时应使用预处理语句,不要直接拼接 SQL:

$stmt = $pdo->prepare(
    'INSERT INTO payment_transactions
        (status, customer_reference_no, reference_no, merchant_id, amount, checksum)
     VALUES
        (:status, :customer_reference_no, :reference_no, :merchant_id, :amount, :checksum)'
);

$stmt->execute([
    ':status'                => $status,
    ':customer_reference_no' => $customerReferenceNo,
    ':reference_no'          => $referenceNo,
    ':merchant_id'           => $merchantId,
    ':amount'                => $amount,
    ':checksum'              => $checkSum,
]);

不要直接用浮点数比较金额。可以按照 gateway 规定的精度使用数据库 DECIMAL 类型,也可以统一换算为最小货币单位,例如“分”,再按整数处理。

还应为 gateway 的唯一交易编号设置唯一约束。payment gateway 可能重复发送通知,用户也可能刷新 return 页面,因此回调处理必须具备幂等性,避免重复记账。

return URL 和后台通知地址可能不是一回事

有些 payment gateway 会将用户浏览器重定向到 return URL,另一些则会由服务器直接请求 callback、webhook 或 notification URL。它们的用途不同:

  • return URL:主要用于向用户展示支付结果,但浏览器可能被关闭,请求也可能中断。
  • callback/webhook URL:用于向服务器发送较为可靠的通知,以便更新订单状态。
  • 查询 API:用于主动核实最终的交易结果。

如果 gateway 同时提供这些地址,应以经过验签的服务器通知或交易查询结果作为入账依据,不要只依赖用户浏览器返回的页面。

还要确认 gateway 是否要求回调处理完成后返回固定文本,例如 OK、SUCCESS,或者返回特定的 HTTP 状态码。具体要求必须遵循其接口文档。

备注:内容仅供参考。