如何在 PrestaShop 进入付款流程时执行操作?
支付阿杰
结论
可以执行,但需要先确定触发时机:
- 如果想在用户选中付款方式后立即运行 PHP,需要用 JavaScript 监听付款选项的变化,再通过 AJAX 请求模块的前台控制器。选择单选框本身不会向服务器发送请求,所以不能直接触发 PHP。
- 如果操作应该在订单创建后,或付款模块确认订单后执行,使用
actionValidateOrderHook 更合适。
以下示例适用于 PrestaShop 1.7/8 的模块结构。部分主题会修改付款区域的 HTML,此时需要根据实际页面调整选择器。
选择付款方式后立即执行 PHP
1. 在模块中加载监听脚本
先在模块主类的 install() 方法中注册 displayHeader:
public function install()
{
return parent::install()
&& $this->registerHook('displayHeader');
}
接着只在结账页面加载 JavaScript,并将 AJAX 地址和令牌传给前端:
public function hookDisplayHeader()
{
if ($this->context->controller->php_self !== 'order') {
return;
}
$this->context->controller->registerJavascript(
'module-' . $this->name . '-payment-choice',
'modules/' . $this->name . '/views/js/payment-choice.js',
[
'position' => 'bottom',
'priority' => 150,
]
);
Media::addJsDef([
'paymentChoiceUrl' => $this->context->link->getModuleLink(
$this->name,
'choice',
[],
true
),
'paymentChoiceToken' => Tools::getToken(false),
]);
}
如果模块还要兼容不支持 registerJavascript() 的旧版本,可以改用该版本提供的脚本加载方法。
2. 监听付款选项变化
创建文件:
modules/yourmodule/views/js/payment-choice.js
示例代码:
document.addEventListener('change', function (event) {
const target = event.target;
if (!target.matches('input[name="payment-option"]')) {
return;
}
const body = new URLSearchParams({
ajax: '1',
token: paymentChoiceToken,
payment_option: target.value
});
fetch(paymentChoiceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
credentials: 'same-origin',
body: body.toString()
})
.then(function (response) {
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
return response.json();
})
.then(function (result) {
if (!result.success) {
console.error(result.message);
}
})
.catch(function (error) {
console.error('Payment choice request failed:', error);
});
});
这里采用事件委托,因为 PrestaShop 的结账页面可能通过 AJAX 重新渲染付款选项。如果直接给当前的单选框绑定事件,该区域刷新后,原来的事件监听可能失效。
3. 创建处理 PHP 请求的前台控制器
创建文件:
modules/yourmodule/controllers/front/choice.php
假设模块的技术名称是 yourmodule:
<?php
class YourModuleChoiceModuleFrontController extends ModuleFrontController
{
public function postProcess()
{
header('Content-Type: application/json; charset=utf-8');
if (!Tools::isSubmit('ajax')) {
$this->ajaxDie(json_encode([
'success' => false,
'message' => 'Invalid request.',
]));
}
$token = (string) Tools::getValue('token');
if (!hash_equals(Tools::getToken(false), $token)) {
$this->ajaxDie(json_encode([
'success' => false,
'message' => 'Invalid token.',
]));
}
$paymentOption = (string) Tools::getValue('payment_option');
if ($paymentOption === '' || strlen($paymentOption) > 255) {
$this->ajaxDie(json_encode([
'success' => false,
'message' => 'Invalid payment option.',
]));
}
/*
* 在这里执行需要的 PHP 操作。
*
* 不要仅凭 payment_option 的值执行敏感操作,因为该参数可以被用户伪造。
* 如需处理购物车或客户信息,应从当前 Context 获取并重新验证。
*/
PrestaShopLogger::addLog(
sprintf('Payment option selected: %s', $paymentOption),
1
);
$this->ajaxDie(json_encode([
'success' => true,
]));
}
}
类名前缀必须与模块的技术名称一致。例如,模块名为 custompaymentaction 时,类名应写成:
class CustomPaymentActionChoiceModuleFrontController extends ModuleFrontController
订单创建后再执行
如果 PHP 代码涉及库存、积分、发票、ERP 同步,或其他只能执行一次的业务逻辑,不适合在用户选择付款方式时运行。用户可能多次切换付款方式、刷新页面,也可能最终放弃下单。
这类操作可以注册 actionValidateOrder:
public function install()
{
return parent::install()
&& $this->registerHook('actionValidateOrder');
}
然后在模块主类中处理:
public function hookActionValidateOrder(array $params)
{
/** @var Order $order */
$order = $params['order'];
/** @var Cart $cart */
$cart = $params['cart'];
/** @var Customer $customer */
$customer = $params['customer'];
// 使用订单 ID 做幂等检查,避免重复处理。
$orderId = (int) $order->id;
// 在这里执行订单创建后的业务逻辑。
}
触发 actionValidateOrder 说明订单已经通过 validateOrder() 创建,但不代表资金已经成功到账。信用卡异步通知、银行转账等场景,还需要结合付款模块的回调和订单状态判断实际付款结果。
注意事项
- 前端提交的付款方式、购物车 ID、金额和客户 ID 都不可信,服务器端必须重新读取并验证。
- 选择付款方式的事件可能触发多次。相关 PHP 操作应当允许重复执行,或者通过幂等记录避免重复处理。
- 不要在”选择付款方式”阶段扣减库存、生成正式订单或确认收款。
- 自定义主题未必使用
input[name="payment-option"]。请检查页面的实际 HTML,再调整 JavaScript 选择器。 - 有些付款模块会在选中后显示附加表单,有些则会跳转到外部支付页面。因此,“选择付款方式”、“创建订单”和”支付成功”属于三个不同的生命周期节点。
- 修改模块文件后,如果代码没有生效,需要清理 PrestaShop 缓存,并确认模块已经重新安装,或已经补注册所需的 Hook。
备注:内容仅供参考。
