PrestaShop自定义外部支付网关:无用户交互WebHook响应处理
解决PrestaShop外部支付网关的无用户交互订单确认问题
问题背景
基于PrestaShop外部支付示例框架开发支付模块时,原流程依赖用户支付后跳转回validation.php完成订单确认,存在以下问题:
- 用户关闭页面、退出登录时,订单无法自动确认
- 完全依赖用户端交互,可靠性不足
网关支持服务器端WebHook回调,但不清楚如何配置和实现无用户交互的订单处理逻辑。
解决方案步骤
1. 创建独立的WebHook处理控制器
在模块目录下新建webhook.php,专门接收网关的服务器端回调,无需依赖用户上下文直接处理订单确认:
<?php require_once _PS_MODULE_DIR_ . 'your_module_name/your_module_name.php'; class YourModuleNameWebhookModuleFrontController extends ModuleFrontController { // 关闭所有页面渲染,仅返回API响应 public $ssl = true; public $display_header = false; public $display_footer = false; public $display_column_left = false; public $display_column_right = false; public function postProcess() { // 1. 验证网关请求合法性(必须实现,防止伪造请求) if (!$this->validateGatewayRequest()) { http_response_code(403); exit('Invalid request'); } // 2. 从网关回调参数中获取购物车ID(需根据网关实际返回字段调整) $cartId = (int)Tools::getValue('cart_id'); $cart = new Cart($cartId); if (!Validate::isLoadedObject($cart)) { http_response_code(404); exit('Cart not found'); } // 3. 验证购物车归属用户 $customer = new Customer($cart->id_customer); if (!Validate::isLoadedObject($customer)) { http_response_code(400); exit('Invalid customer'); } // 4. 检查订单是否已处理,避免重复回调 $existingOrderId = Order::getOrderByCartId($cartId); if ($existingOrderId) { http_response_code(200); echo json_encode(['status' => 'success', 'order_id' => $existingOrderId]); exit; } // 5. 执行订单确认核心逻辑 try { $orderStateId = Configuration::get('PS_OS_PAYMENT'); // 使用PrestaShop默认已付款状态 $this->module->validateOrder( $cart->id, $orderStateId, $cart->getOrderTotal(true, Cart::BOTH), $this->module->displayName, null, [], $this->context->currency->id, false, $customer->secure_key ); // 6. 返回网关要求的成功响应(格式按需调整) http_response_code(200); echo json_encode(['status' => 'success', 'order_id' => $this->module->currentOrder]); } catch (Exception $e) { http_response_code(500); echo json_encode(['status' => 'error', 'message' => $e->getMessage()]); } exit; } /** * 验证网关请求合法性,示例为HMAC签名验证(需根据网关文档调整) */ private function validateGatewayRequest() { $gatewaySignature = Tools::getValue('signature'); $rawPayload = file_get_contents('php://input'); // 从模块配置中获取网关密钥 $secretKey = Configuration::get('YOUR_MODULE_GATEWAY_SECRET'); $expectedSignature = hash_hmac('sha256', $rawPayload, $secretKey); return hash_equals($gatewaySignature, $expectedSignature); } }
2. 配置WebHook地址
修改external.php中的$ok_webhook,设置为新建的WebHook控制器地址,同时传递购物车ID给网关:
// 替换原有的$ok_webhook = "???????????"; $ok_webhook = $this->context->link->getModuleLink( $this->module->name, 'webhook', [], true // 强制使用HTTPS保障安全 ); // 调整传递给网关的参数,增加cart_id以便回调识别 $payload = [ 'cart_id' => $cart->id, 'redirect_ok' => $ok_redirect, 'webhook_ok' => $ok_webhook, 'amount' => $cart->getOrderTotal(true, Cart::BOTH), 'currency' => $this->context->currency->iso_code ];
3. 兼容原用户跳转流程(可选)
保留validation.php的逻辑,但增加订单重复处理判断,避免用户跳转时重复创建订单:
public function postProcess() { $cart = $this->context->cart; // 检查购物车是否已转为订单 $existingOrderId = Order::getOrderByCartId($cart->id); if ($existingOrderId) { // 直接跳转到订单确认页 Tools::redirect($this->context->link->getPageLink( 'order-confirmation', true, $this->context->language->id, [ 'id_cart' => $cart->id, 'id_module' => $this->module->id, 'id_order' => $existingOrderId, 'key' => $this->context->customer->secure_key, ] )); exit; } // 原订单验证逻辑(WebHook未触发时,用户跳转仍可处理) $customer = new Customer($cart->id_customer); $this->module->validateOrder( (int)$cart->id, (int)$this->getOrderState(), (float)$cart->getOrderTotal(true, Cart::BOTH), $this->module->displayName, null, [], (int)$this->context->currency->id, false, $customer->secure_key ); Tools::redirect($this->context->link->getPageLink( 'order-confirmation', true, (int)$this->context->language->id, [ 'id_cart' => (int)$cart->id, 'id_module' => (int)$this->module->id, 'id_order' => (int)$this->module->currentOrder, 'key' => $customer->secure_key, ] )); }
4. 关键注意事项
- 安全验证不可省略:必须实现
validateGatewayRequest逻辑,可结合网关签名、IP白名单双重验证,防止恶意伪造订单。 - 确保WebHook地址可访问:检查服务器防火墙、PrestaShop安全插件是否拦截网关请求,必要时添加网关IP到白名单。
- 添加日志记录:建议在WebHook控制器中增加日志,方便排查问题:
PrestaShopLogger::addLog( "WebHook received for cart {$cartId}: " . json_encode($_POST), 1, null, 'Order', $cartId, true );
内容的提问来源于stack exchange,提问作者DeLac
相关产品推荐
相关产品推荐

