Prestashop payment module 返回 500 server status
结论
支付平台的服务器回调不应使用 Tools::redirect()。订单处理成功后,应直接输出简短响应并立即结束当前请求:
http_response_code(200);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['status' => 'ok']);
exit;
exit/die 可以用于生产环境。订单处理完成后,需要终止 PrestaShop 后续的控制器渲染流程。否则,后面发生的 PHP 异常、模板错误或未初始化变量,仍可能让最终响应变成 500。
为什么会出现 302 或 500
Tools::redirect() 会向客户端发送重定向响应,因此返回 302 属于正常行为。它适合让顾客的浏览器跳转,不适合处理支付网关发来的服务器到服务器通知。
仅调用:
http_response_code(200);
或者:
header('HTTP/1.1 200 OK');
只能设置当前状态码,不能结束程序。后续代码依然会执行。如果 PrestaShop 随后尝试渲染不存在的模板、触发异常或访问未定义变量,最终响应仍可能变成 500。
当前代码还有几个可能引发异常的问题:
$paymentId只在confirmed和received两种状态下赋值。getRow()可能返回false,此时直接访问$row['current_state']并不安全。Tools::redirect(__FILE__, 'payment_confirmation.tpl')不能用于渲染模板。- 重定向只在数据库更新成功后执行。如果更新失败,程序会继续进入控制器的后续流程。
- 直接修改
orders.current_state会绕过 PrestaShop 的订单状态处理机制。
推荐的回调处理方式
回调接口应区分成功和失败,并在每个响应出口立即终止请求:
<?php
class YourModuleValidationModuleFrontController extends ModuleFrontController
{
public function postProcess()
{
header('Content-Type: application/json; charset=utf-8');
try {
$hash = (string) Tools::getValue('hash');
$status = (string) Tools::getValue('status');
$optDataRaw = (string) Tools::getValue('optData');
$expectedHash = hash('sha256', $this->module->secret_key);
if (!hash_equals($expectedHash, $hash)) {
$this->sendJsonResponse(401, [
'status' => 'error',
'message' => 'Invalid signature',
]);
}
parse_str($optDataRaw, $optData);
$idCart = isset($optData['cartId']) ? (int) $optData['cartId'] : 0;
if ($idCart <= 0) {
$this->sendJsonResponse(400, [
'status' => 'error',
'message' => 'Invalid cart ID',
]);
}
if (!in_array($status, ['confirmed', 'received'], true)) {
$this->sendJsonResponse(400, [
'status' => 'error',
'message' => 'Unsupported payment status',
]);
}
$orderId = (int) Order::getOrderByCartId($idCart);
if ($orderId > 0) {
// 回调可能重复发送。订单已经存在时按幂等规则处理。
$this->sendJsonResponse(200, [
'status' => 'ok',
'message' => 'Order already processed',
]);
}
$cart = new Cart($idCart);
if (
!Validate::isLoadedObject($cart)
|| !$cart->id_customer
|| !$cart->id_address_delivery
|| !$cart->id_address_invoice
|| !$this->module->active
) {
$this->sendJsonResponse(400, [
'status' => 'error',
'message' => 'Invalid cart',
]);
}
$customer = new Customer((int) $cart->id_customer);
if (!Validate::isLoadedObject($customer)) {
$this->sendJsonResponse(400, [
'status' => 'error',
'message' => 'Invalid customer',
]);
}
$currency = new Currency((int) $cart->id_currency);
if (!Validate::isLoadedObject($currency)) {
$this->sendJsonResponse(400, [
'status' => 'error',
'message' => 'Invalid currency',
]);
}
$paymentState = $status === 'confirmed'
? (int) Configuration::get('PS_OS_PAYMENT')
: 11;
$orderAmount = (float) $cart->getOrderTotal(true, Cart::BOTH);
$this->module->validateOrder(
(int) $cart->id,
$paymentState,
$orderAmount,
$this->module->displayName,
'Invoice Code: ' . (string) Tools::getValue('invoiceCode'),
[],
(int) $currency->id,
false,
$customer->secure_key
);
$this->sendJsonResponse(200, [
'status' => 'ok',
]);
} catch (Throwable $e) {
PrestaShopLogger::addLog(
'Payment callback failed: ' . $e->getMessage(),
3
);
$this->sendJsonResponse(500, [
'status' => 'error',
'message' => 'Internal server error',
]);
}
}
private function sendJsonResponse($statusCode, array $data)
{
http_response_code((int) $statusCode);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data);
exit;
}
}
如果旧版 PHP 不支持 Throwable,可以改为捕获 Exception。具体写法取决于当前 PrestaShop 使用的 PHP 版本。
已存在订单时不要直接更新数据库
不建议执行:
UPDATE ps_orders SET current_state = 2 WHERE id_cart = ...
这种写法会绕过订单状态历史、邮件通知、库存处理和相关钩子。需要修改已有订单的状态时,应使用 PrestaShop 的订单历史机制:
$order = new Order($orderId);
if (!Validate::isLoadedObject($order)) {
$this->sendJsonResponse(404, [
'status' => 'error',
'message' => 'Order not found',
]);
}
$history = new OrderHistory();
$history->id_order = (int) $order->id;
$history->changeIdOrderState(
(int) Configuration::get('PS_OS_PAYMENT'),
(int) $order->id
);
$history->addWithemail();
状态 ID 不应直接写死为 2 或 11。如果这些 ID 对应模块创建的自定义状态,应从模块配置中读取。如果代表标准的“已付款”状态,应优先使用:
Configuration::get('PS_OS_PAYMENT')
如果支付平台只接受纯文本
有些支付平台要求返回固定字符串,不接受 JSON。此时可以简化为:
http_response_code(200);
header('Content-Type: text/plain; charset=utf-8');
echo 'OK';
exit;
在控制请求流程方面,这与 die('OK') 没有本质区别。使用 exit 能更直接地表示响应已经完成,PrestaShop 控制器不再继续运行。
还需要检查 500 的具体原因
设置状态码为 200 后仍然返回 500,说明后续代码发生了错误,并非状态码设置失败。应检查 Web 服务器、PHP-FPM 和 PrestaShop 日志,重点排查:
$paymentId未定义$row为falsevalidateOrder()抛出异常- 重复回调导致同一购物车重复创建订单
- 模板路径或控制器渲染错误
- 输出响应前已经发送 HTTP headers
- 支付金额、币种或签名字段不合法
支付回调只负责验证请求、以幂等方式处理订单、输出协议要求的响应,并立即 exit。顾客支付完成后的页面跳转,应通过单独的浏览器返回地址处理。
备注:内容仅供参考。
