PAYATHON 2026

PHP callback 重定向时如何向 Paysera 返回 OK

支付老李

结论

Paysera 的 callback 是服务器之间的通知,请求必须直接返回 HTTP 200,响应正文只能是:

OK

同一个 HTTP 响应无法同时返回 OK 和执行 Location 重定向。调用:

header('Location: ...');

等于要求 Paysera 的服务器访问另一个页面。如果 Paysera 跟随重定向,最终收到的是目标 WordPress 页面生成的 HTML 和 JavaScript,而不是 OK,因此会把 callback 判定为失败。

callback 应先完成订单状态更新,再输出 OK 并立即结束程序。如果确实需要调用另一个 URL,应由 callback 在服务器端发起请求,不能使用浏览器重定向。

推荐方案:直接更新订单状态

把 update_status() 改成普通的业务函数,不再发送 Location 响应:

<?php

try {
    $response = WebToPay::checkResponse($_GET, [
        'projectid'     => '######',
        'sign_password' => '#################',
    ]);

    if ($response['test'] !== '0') {
        throw new Exception('Testing, real payment was not made');
    }

    if ($response['type'] !== 'macro') {
        throw new Exception('Only macro payment callbacks are accepted');
    }

    $orderId = $response['orderid'];
    $amount = $response['amount'];
    $currency = $response['currency'];

    // 必须根据数据库中的订单重新核对,不能只相信回调参数。
    // 1. 查询订单是否存在
    // 2. 核对金额和币种
    // 3. 如果订单尚未支付,更新订单状态
    update_order_status($orderId, $amount, $currency);

    http_response_code(200);
    header('Content-Type: text/plain; charset=UTF-8');
    echo 'OK';
    exit;
} catch (Throwable $e) {
    http_response_code(400);
    header('Content-Type: text/plain; charset=UTF-8');

    // 对外不建议返回详细异常,以免泄露内部信息。
    echo 'ERROR';
    exit;
}

function update_order_status($orderId, $amount, $currency)
{
    // 在此处调用 WordPress/WooCommerce API 或更新数据库。
    // 该操作必须具备幂等性,因为 Paysera 可能重复发送 callback。
}

输出 OK 后要立即 exit,否则 WordPress 可能继续渲染主题模板、Gravity Forms 脚本、emoji 脚本或其他页面内容。

如果必须访问另一个 URL

如果 /uzsakymo-uzskaitymas 已经包含订单更新逻辑,callback 可以从服务器端调用它。在 WordPress 环境中,可以使用 wp_remote_post():

function update_status(array $response): void
{
    $result = wp_remote_post(
        'https://www.example.lt/uzsakymo-uzskaitymas',
        [
            'timeout' => 10,
            'body' => [
                'orderid' => $response['orderid'],
                'amount' => $response['amount'],
                'currency' => $response['currency'],
            ],
        ]
    );

    if (is_wp_error($result)) {
        throw new RuntimeException(
            'Status update request failed: ' . $result->get_error_message()
        );
    }

    $statusCode = wp_remote_retrieve_response_code($result);

    if ($statusCode < 200 || $statusCode >= 300) {
        throw new RuntimeException(
            'Status update endpoint returned HTTP ' . $statusCode
        );
    }
}

验证 Paysera callback 后调用该函数:

update_status($response);

http_response_code(200);
header('Content-Type: text/plain; charset=UTF-8');
echo 'OK';
exit;

更稳妥的设计是把订单更新代码提取为 PHP 函数或服务,供 callback 和其他页面直接调用。通过 HTTP 请求自己的网站,会带来超时、认证、重试和重复处理等问题。

用户付款后的页面跳转应单独处理

Paysera callback 与用户浏览器的返回页面属于两种不同的请求:

  • callback URL:供 Paysera 服务器通知支付结果,需要返回 OK。
  • accept/return URL:供用户付款后跳转到结果页。
  • cancel URL:供用户取消付款后返回。

如果需要让付款用户看到 /uzsakymo-uzskaitymas 页面,应将它配置为用户返回地址,而不是在 callback 中重定向。

不能根据用户返回页面确认付款成功,因为用户可能关闭浏览器、刷新页面或伪造请求。订单是否支付成功,仍应以通过签名验证的 callback 为准。

注意事项

  • 不要直接使用未经验证的 $_GET['data'] 更新订单。应先调用 Paysera 提供的签名校验方法,再使用解析后的 $response。
  • callback 可能重复到达,因此更新逻辑必须具备幂等性。已支付的订单再次收到同一通知时,不应重复扣减库存、发送邮件或记账。
  • 必须核对订单号、实际金额、币种和订单当前状态。
  • callback 响应中不能包含 WordPress 页面、调试输出、PHP warning、BOM 或额外空白。
  • 不要先 echo 'OK' 再重定向。即使某些环境允许继续发送 header,最终响应也不是 Paysera 要求的纯 OK。
  • 如果服务器端的状态更新失败,不应返回 OK,否则 Paysera 可能认为通知已经处理成功,不再重试。具体的重试行为应以当前 Paysera 文档为准。

备注:内容仅供参考。