如何在 prestashop 中添加自定义支付方式?
结论
在 PrestaShop 中,如果支付方式不需要在下单时在线扣款,而是在订单创建后线下收款,应该将它开发成独立的支付模块。模块继承 PaymentModule,通过 paymentOptions 钩子在结账页显示支付选项,再由前台控制器调用 validateOrder() 创建订单。
如果业务流程和货到付款完全相同,安装并配置官方的 Cash on Delivery 模块即可。只有支付方式的名称、订单状态、手续费、适用地区或处理流程不同,才需要开发自定义模块。
以下示例采用 PrestaShop 1.7/8 中常见的模块结构。不同版本在目录、翻译系统和钩子方面可能存在差异,上线前应根据实际版本核对。
模块结构
在 modules 目录下创建 customoffline:
modules/
└── customoffline/
├── customoffline.php
├── controllers/
│ └── front/
│ └── validation.php
└── views/
└── templates/
└── hook/
├── payment_info.tpl
└── payment_return.tpl
模块目录名、主 PHP 文件名和模块类名必须相互对应,否则 PrestaShop 无法正确加载模块。
创建支付模块
modules/customoffline/customoffline.php:
<?php
if (!defined('_PS_VERSION_')) {
exit;
}
use PrestaShop\PrestaShop\Core\Payment\PaymentOption;
class CustomOffline extends PaymentModule
{
public function __construct()
{
$this->name = 'customoffline';
$this->tab = 'payments_gateways';
$this->version = '1.0.0';
$this->author = 'Your Company';
$this->bootstrap = true;
$this->need_instance = 0;
parent::__construct();
$this->displayName = $this->l('Custom offline payment');
$this->description = $this->l(
'Allows customers to place an order and pay through an offline method.'
);
}
public function install()
{
return parent::install()
&& $this->installOrderState()
&& $this->registerHook('paymentOptions')
&& $this->registerHook('paymentReturn');
}
private function installOrderState()
{
$configuredStateId = (int) Configuration::get('CUSTOMOFFLINE_ORDER_STATE');
if ($configuredStateId > 0 && Validate::isLoadedObject(
new OrderState($configuredStateId)
)) {
return true;
}
$orderState = new OrderState();
$orderState->module_name = $this->name;
$orderState->color = '#4169E1';
$orderState->send_email = false;
$orderState->invoice = false;
$orderState->logable = false;
$orderState->paid = false;
$orderState->shipped = false;
$orderState->delivery = false;
$orderState->unremovable = false;
$orderState->hidden = false;
foreach (Language::getLanguages(false) as $language) {
$orderState->name[(int) $language['id_lang']] =
'Awaiting offline payment';
}
if (!$orderState->add()) {
return false;
}
return Configuration::updateValue(
'CUSTOMOFFLINE_ORDER_STATE',
(int) $orderState->id
);
}
public function hookPaymentOptions($params)
{
if (!$this->active || empty($params['cart'])) {
return [];
}
/** @var Cart $cart */
$cart = $params['cart'];
if (!$this->checkCurrency($cart)) {
return [];
}
$paymentOption = new PaymentOption();
$paymentOption
->setModuleName($this->name)
->setCallToActionText($this->l('Pay with custom offline payment'))
->setAction(
$this->context->link->getModuleLink(
$this->name,
'validation',
[],
true
)
)
->setAdditionalInformation(
$this->fetch(
'module:' . $this->name .
'/views/templates/hook/payment_info.tpl'
)
);
return [$paymentOption];
}
public function hookPaymentReturn($params)
{
if (!$this->active || empty($params['order'])) {
return '';
}
/** @var Order $order */
$order = $params['order'];
$this->context->smarty->assign([
'order_reference' => $order->reference,
'total_to_pay' => Tools::displayPrice(
$order->total_paid,
new Currency((int) $order->id_currency)
),
]);
return $this->fetch(
'module:' . $this->name .
'/views/templates/hook/payment_return.tpl'
);
}
}
这段代码会单独创建一个“等待线下付款”订单状态。不要直接把这类订单标记为已支付,否则库存、发票、统计数据或第三方 ERP 可能会误判订单已经收款。
创建订单验证控制器
modules/customoffline/controllers/front/validation.php:
<?php
class CustomOfflineValidationModuleFrontController extends ModuleFrontController
{
public $ssl = true;
public function postProcess()
{
if (!$this->module->active) {
Tools::redirect('index.php?controller=order&step=1');
}
$cart = $this->context->cart;
$customer = $this->context->customer;
if (
!Validate::isLoadedObject($cart)
|| (int) $cart->id_customer <= 0
|| !Validate::isLoadedObject($customer)
|| (int) $cart->id_customer !== (int) $customer->id
|| !$this->module->checkCurrency($cart)
) {
Tools::redirect('index.php?controller=order&step=1');
}
// 防止刷新页面或重复提交时为同一个购物车重复创建订单。
if ((int) Order::getIdByCartId((int) $cart->id) > 0) {
Tools::redirect('index.php?controller=history');
}
$orderStateId = (int) Configuration::get(
'CUSTOMOFFLINE_ORDER_STATE'
);
$orderState = new OrderState($orderStateId);
if (!Validate::isLoadedObject($orderState)) {
Tools::redirect('index.php?controller=order&step=1');
}
$total = (float) $cart->getOrderTotal(true, Cart::BOTH);
$this->module->validateOrder(
(int) $cart->id,
$orderStateId,
$total,
$this->module->displayName,
null,
[],
(int) $cart->id_currency,
false,
$customer->secure_key
);
$orderId = (int) $this->module->currentOrder;
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' => $orderId,
'key' => $customer->secure_key,
]
)
);
}
}
validateOrder() 会根据购物车创建订单,并触发 PrestaShop 对库存、订单历史和邮件等相关流程的处理。不要绕过这个方法,直接向 orders 表写入记录。
添加支付说明模板
views/templates/hook/payment_info.tpl:
<div class="customoffline-payment-info">
<p>
{l s='Place your order now and pay using the agreed offline method.' mod='customoffline'}
</p>
<p>
{l s='Payment instructions will be shown after the order is confirmed.' mod='customoffline'}
</p>
</div>
views/templates/hook/payment_return.tpl:
<div class="customoffline-payment-return">
<p>
{l s='Your order has been created successfully.' mod='customoffline'}
</p>
<p>
{l s='Order reference:' mod='customoffline'}
<strong>{$order_reference|escape:'htmlall':'UTF-8'}</strong>
</p>
<p>
{l s='Amount to pay:' mod='customoffline'}
<strong>{$total_to_pay|escape:'htmlall':'UTF-8'}</strong>
</p>
<p>
{l s='Please follow the offline payment instructions provided by the store.' mod='customoffline'}
</p>
</div>
模块安装后,可以通过后台翻译功能,将模板中的英文文案翻译成中文或其他语言。
安装与测试
将模块目录放入 PrestaShop 的 modules 目录,然后在后台模块管理页面安装并启用。完成后,至少检查以下流程:
- 支付方式能否正常出现在结账页面。
- 禁用模块后,验证地址是否无法继续创建订单。
- 同一个购物车重复提交时,是否只生成一个订单。
- 订单金额、币种、商品数量和运费是否正确。
- 新订单是否处于“等待线下付款”状态,而不是“已付款”。
- 在后台手动确认收款后,订单状态能否正常变更。
- 游客结账、多店铺、多币种和不同配送方式是否符合业务规则。
开发期间新增或修改类、控制器或模板后,通常还需要清除 PrestaShop 缓存。
上线时需要注意
- 不要修改 PrestaShop 核心文件,也不要直接改动现有的货到付款模块。核心或官方模块升级后,这些修改很容易丢失。
- 如果支付方式只对特定国家、币种、客户组或承运商开放,应在后台配置支付限制,并在验证控制器中再次检查必要条件,不能只在前端隐藏支付选项。
- 如果需要收取手续费,不能只改页面显示的金额。手续费必须进入购物车或订单的计算过程,并正确处理税费、退款和发票。
- 如果需要用户填写交易编号或付款账户等信息,应提供表单并在服务端校验,同时做好 HTML 转义和长度限制。
- 传给
validateOrder()的金额必须由服务端根据购物车重新计算,不能使用浏览器提交的金额。 - 卸载模块时,不建议直接删除已经被历史订单引用的订单状态。配置项可以删除,但应保证历史订单仍然可读。
- 如果业务包含在线扣款、异步回调或签名验签,就不能采用简单的货到付款模式。此时还需要单独设计支付请求、回调幂等、金额校验和交易状态同步。
备注:内容仅供参考。
