Prestashop displayPaymentReturn hook 应跳转到哪个 URL?
明确结论
完成支付并调用 validateOrder() 创建订单后,应把用户重定向到 PrestaShop 的 order-confirmation 页面。displayPaymentReturn 会由订单确认页执行,不需要直接调用,也不应通过跳转到模块模板来触发。
典型的跳转代码如下:
Tools::redirect(
$this->context->link->getPageLink(
'order-confirmation',
true,
null,
[
'id_cart' => (int) $this->context->cart->id,
'id_module' => (int) $this->module->id,
'id_order' => (int) $this->module->currentOrder,
'key' => $this->context->customer->secure_key,
]
)
);
前提是先成功创建订单,再跳转到订单确认页。仅访问 order-confirmation URL 并不会创建订单。
标准支付流程
支付模块通常按照以下顺序运行:
- 用户在结账页选择支付方式。
- PrestaShop 将请求提交到支付模块的前台控制器。
- 模块直接创建订单,或者将用户带到第三方支付平台。
- 确认支付结果后,模块调用
validateOrder()。 - 模块把用户重定向到
order-confirmation。 - 订单确认页根据
id_order、id_module和key验证订单。 - 页面执行
displayPaymentReturn,显示模块提供的支付结果信息。
使用站外支付平台时,通常会有异步通知和浏览器返回两个入口:
结账页面
→ 模块支付控制器
→ 第三方支付平台
→ 支付平台异步通知模块
→ 模块验证并创建/更新订单
第三方支付平台
→ 浏览器返回模块
→ order-confirmation
→ displayPaymentReturn
这两个入口需要分开处理。用户可能关闭浏览器或中断返回流程,因此不能只靠返回页面确认付款。
支付控制器示例
下面是一个简化的模块前台控制器,适合可以立即确认支付结果的方式,例如线下付款或测试支付模块。
<?php
class MyPaymentValidationModuleFrontController extends ModuleFrontController
{
public function postProcess()
{
$cart = $this->context->cart;
$customer = new Customer((int) $cart->id_customer);
if (!Validate::isLoadedObject($cart)
|| !Validate::isLoadedObject($customer)
|| !$this->module->active
) {
Tools::redirect('index.php?controller=order');
}
$amount = (float) $cart->getOrderTotal(true, Cart::BOTH);
$this->module->validateOrder(
(int) $cart->id,
(int) Configuration::get('PS_OS_PAYMENT'),
$amount,
$this->module->displayName,
null,
[],
(int) $cart->id_currency,
false,
$customer->secure_key
);
Tools::redirect(
$this->context->link->getPageLink(
'order-confirmation',
true,
null,
[
'id_cart' => (int) $cart->id,
'id_module' => (int) $this->module->id,
'id_order' => (int) $this->module->currentOrder,
'key' => $customer->secure_key,
]
)
);
}
}
不要手动拼接确认页 URL。使用 Link::getPageLink() 可以兼容 HTTPS、友好 URL、语言设置和店铺上下文。
实现 displayPaymentReturn
可以在模块中这样实现:
public function hookDisplayPaymentReturn($params)
{
if (!$this->active) {
return '';
}
$order = $params['order'] ?? null;
if (!$order || !Validate::isLoadedObject($order)) {
return '';
}
$this->context->smarty->assign([
'order_reference' => $order->reference,
'total_paid' => Tools::displayPrice(
$order->total_paid,
new Currency((int) $order->id_currency)
),
]);
return $this->fetch(
'module:' . $this->name . '/views/templates/hook/payment_return.tpl'
);
}
对应的模板示例:
<div class="alert alert-success">
<p>订单已创建,订单编号:{$order_reference|escape:'htmlall':'UTF-8'}</p>
<p>订单金额:{$total_paid|escape:'htmlall':'UTF-8'}</p>
</div>
安装模块时还需要注册 Hook:
public function install()
{
return parent::install()
&& $this->registerHook('paymentOptions')
&& $this->registerHook('displayPaymentReturn');
}
为旧版 PrestaShop 编写的模块中,可能会出现 paymentReturn、hookPaymentReturn() 或其他旧式支付 Hook。不同大版本的支付 API 差异很大,应以目标版本的核心代码和官方开发文档为准,避免混用新旧版本的示例。
支付选项应指向哪里
在较新的 PrestaShop 版本中,hookPaymentOptions() 返回的支付选项通常会把 action 指向模块自己的验证控制器:
public function hookPaymentOptions($params)
{
if (!$this->active) {
return [];
}
$option = new PrestaShop\PrestaShop\Core\Payment\PaymentOption();
$option
->setCallToActionText($this->l('Pay with My Payment'))
->setAction(
$this->context->link->getModuleLink(
$this->name,
'validation',
[],
true
)
);
return [$option];
}
对应的控制器一般位于:
modules/mypayment/controllers/front/validation.php
支付选项应先进入模块控制器,而不是直接进入 order-confirmation。模块控制器创建订单或验证支付结果后,再跳转到确认页。
外部支付平台的处理方式
如果付款结果由第三方平台返回,不要仅根据浏览器中的 success=1、金额或订单状态参数调用 validateOrder(),因为这些参数可能被伪造。
处理流程需要包括:
- 校验支付平台签名。
- 通过服务端接口核实交易状态。
- 核对购物车编号、币种和金额。
- 检查购物车是否已经生成订单,避免重复调用
validateOrder()。 - 将支付平台交易号保存到订单付款记录或模块自己的交易表。
- 由异步通知完成最终的支付确认,浏览器返回页只展示结果。
创建订单前可以先做幂等检查:
if (!$this->module->currentOrder) {
$orderId = (int) Order::getIdByCartId((int) $cart->id);
if (!$orderId) {
$this->module->validateOrder(
(int) $cart->id,
(int) Configuration::get('PS_OS_PAYMENT'),
(float) $cart->getOrderTotal(true, Cart::BOTH),
$this->module->displayName,
null,
[],
(int) $cart->id_currency,
false,
$customer->secure_key
);
$orderId = (int) $this->module->currentOrder;
}
}
实际模块还要处理并发通知。只做一次查询,不一定能完全避免竞争条件。
Hooks 与页面并非固定的一对一关系
PrestaShop 没有一张始终准确的“页面对应 Hook”表。Hook 可能由以下位置触发:
- 前台或后台控制器;
- Smarty/Twig 模板;
- 核心服务;
- 其他模块;
- 被不同主题覆盖后的模板。
同一个 Hook 可能出现在多个页面,一个页面通常也会包含多个 Hook。排查对应关系时,可以查看以下位置:
- 官方开发文档中的 Hook 列表。
- 核心代码中的调用,例如:
Hook::exec('displayPaymentReturn', $params);
- 模板中的 Hook 调用,例如:
{hook h='displaySomething'}
- 目标页面对应的控制器和主题模板。
- 后台的模块位置或 Hook 管理页面,用来检查模块的注册情况。
对于这个问题,应直接检查订单确认控制器、订单确认模板,以及核心代码中调用 displayPaymentReturn 的位置。具体文件路径和传入参数会随 PrestaShop 大版本变化,但排查思路相同:先找到页面控制器,再搜索该 Hook 的实际执行位置。
常见错误
- 尚未调用
validateOrder()就跳转到确认页。 - 把购物车 ID 当作
id_order传入。 id_module不是当前支付模块的 ID。key没有使用下单客户的secure_key。- 安装模块时没有注册
displayPaymentReturn。 - 支付失败或取消后,仍然使用已付款订单状态。
- 只依赖浏览器返回确认第三方支付结果。
- 混用不同 PrestaShop 大版本的支付 Hook 和控制器示例。
模块负责验证支付结果并创建订单,order-confirmation 负责展示订单结果,displayPaymentReturn 则是订单确认页留给支付模块的展示入口。
备注:内容仅供参考。
