PAYATHON 2026

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 并不会创建订单。

标准支付流程

支付模块通常按照以下顺序运行:

  1. 用户在结账页选择支付方式。
  2. PrestaShop 将请求提交到支付模块的前台控制器。
  3. 模块直接创建订单,或者将用户带到第三方支付平台。
  4. 确认支付结果后,模块调用 validateOrder()。
  5. 模块把用户重定向到 order-confirmation。
  6. 订单确认页根据 id_order、id_module 和 key 验证订单。
  7. 页面执行 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。排查对应关系时,可以查看以下位置:

  1. 官方开发文档中的 Hook 列表。
  2. 核心代码中的调用,例如:
Hook::exec('displayPaymentReturn', $params);
  1. 模板中的 Hook 调用,例如:
{hook h='displaySomething'}
  1. 目标页面对应的控制器和主题模板。
  2. 后台的模块位置或 Hook 管理页面,用来检查模块的注册情况。

对于这个问题,应直接检查订单确认控制器、订单确认模板,以及核心代码中调用 displayPaymentReturn 的位置。具体文件路径和传入参数会随 PrestaShop 大版本变化,但排查思路相同:先找到页面控制器,再搜索该 Hook 的实际执行位置。

常见错误

  • 尚未调用 validateOrder() 就跳转到确认页。
  • 把购物车 ID 当作 id_order 传入。
  • id_module 不是当前支付模块的 ID。
  • key 没有使用下单客户的 secure_key。
  • 安装模块时没有注册 displayPaymentReturn。
  • 支付失败或取消后,仍然使用已付款订单状态。
  • 只依赖浏览器返回确认第三方支付结果。
  • 混用不同 PrestaShop 大版本的支付 Hook 和控制器示例。

模块负责验证支付结果并创建订单,order-confirmation 负责展示订单结果,displayPaymentReturn 则是订单确认页留给支付模块的展示入口。

备注:内容仅供参考。