MultiMerch 8 使用第三方结账时不向卖家发送订单通知
明确结论
问题不在 country() 或 customfield() 这些 Ajax 接口,也不必先修改你贴出的 checkout.php 片段。
客户和管理员仍能收到订单邮件,说明 Ajax Best Checkout 至少完成了 OpenCart 的订单创建或状态更新。只有卖家邮件丢失,最可能有以下几个原因:
-
Ajax Best Checkout 使用了自己的“确认订单” controller,导致
multimerch_core.xml中用于插入以下代码的 vQmod 搜索规则没有命中实际执行路径:$this->MsLoader->MsMail->sendOrderMails($order_id); -
多个 OCMOD/vQmod 修改叠加后,原始目标代码发生了变化,导致 MultiMerch 的精确匹配失效。
-
MultiMerch 把邮件模板改成了读取
$seller_text,但普通客户或管理员邮件没有传入这个变量,因此出现:Undefined index: seller_text
multimerch_core.xml 负责发送卖家订单邮件,是这次排查的重点。multimerch_core_transactions.xml 主要处理卖家订单数据、余额和事件记录,并不是直接发送订单邮件的入口。
应先确认最终运行的代码中是否存在 sendOrderMails($order_id),再把调用补到真正完成订单确认的位置。不要根据扩展文件名猜测执行路径,也不要把卖家邮件发送逻辑加到国家、地区或自定义字段等 Ajax 请求中。
先确认实际执行的订单流程
OpenCart 2.3 的结账扩展通常仍会调用以下模型方法之一:
$this->model_checkout_order->addOrder($order_data);
$this->model_checkout_order->addOrderHistory(
$order_id,
$order_status_id,
$comment,
$notify
);
支付扩展也可能在支付成功回调中执行:
$this->model_checkout_order->addOrderHistory($order_id, $order_status_id);
Ajax Best Checkout 的入口 controller 不一定是最终确认订单的文件。你提供的代码只有国家和自定义字段接口,不包含订单创建、订单状态更新或邮件发送逻辑,因此与这次故障没有直接关系。
在扩展目录和最终修改缓存中搜索以下关键字:
addOrder(
addOrderHistory(
order_id
order_status_id
checkout/success
sendOrderMails
重点检查这些位置:
catalog/controller/extension/module/xtensions/checkout/
catalog/controller/extension/payment/
system/storage/modification/catalog/model/checkout/order.php
vqmod/vqcache/
不同主机环境中的 storage 目录位置可能不同,应以 config.php 里的 DIR_STORAGE 为准。
检查 vQmod 是否插入了邮件调用
打开 multimerch_core.xml,找到包含以下代码的完整 <operation>:
$this->MsLoader->MsMail->sendOrderMails($order_id);
除了插入的内容,还要查看该 operation 中的:
<file name="...">
<search ...>
<add position="...">
需要确认以下几点:
<file>指向哪个原始文件;<search>匹配的完整代码是什么;- 代码插在订单创建之后,还是订单状态更新之后。
随后检查最终生成的文件,例如:
system/storage/modification/catalog/model/checkout/order.php
以及可能存在的:
vqmod/vqcache/vq2-catalog_model_checkout_order.php
vqmod/vqcache/vq2-system_storage_modification_catalog_model_checkout_order.php
在最终执行文件中搜索:
sendOrderMails
根据搜索结果继续排查。
最终文件中没有 sendOrderMails
这基本可以确认 vQmod 没有匹配成功。常见原因是 Ajax Best Checkout 的 OCMOD 已经改写了目标代码,使 MultiMerch 的 <search> 无法精确匹配。
此时检查:
vqmod/logs/
system/storage/logs/
重点查找类似记录:
SEARCH NOT FOUND
ABORTING
multimerch_core.xml
catalog/model/checkout/order.php
修复时,应调整 multimerch_core.xml 中负责邮件调用的那个 operation,使其匹配当前服务器上的 OpenCart 最终代码。不要通过修改 multimerch_core_transactions.xml 来代替邮件调用。
例如,原规则可能依赖一整段已被其他扩展改写的代码:
<search><![CDATA[
$this->model_checkout_order->addOrderHistory($order_id, $order_status_id);
]]></search>
可以改为匹配目标方法中更稳定、只出现一次的语句。具体匹配内容必须以服务器上的目标文件为准,不能直接照搬示例。
匹配规则也不能过于宽泛。例如只搜索:
<search><![CDATA[
$order_id
]]></search>
可能会在多个位置插入邮件调用,导致卖家收到重复通知。
最终文件中已有 sendOrderMails
如果调用已经存在,可以在调用前后临时写入日志,确认代码是否执行,以及 $order_id 是否有效:
$this->log->write(
'MultiMerch seller mail before: order_id=' . (int)$order_id
);
$this->MsLoader->MsMail->sendOrderMails($order_id);
$this->log->write(
'MultiMerch seller mail after: order_id=' . (int)$order_id
);
如果日志中只有 before,没有 after,说明 sendOrderMails() 内部抛出了错误或提前终止。接着检查同一时间的 PHP、OpenCart 和邮件传输日志。
如果两条日志都有,但卖家仍未收到邮件,则继续确认:
ms_order_product_data是否已经为该订单生成;- 订单商品能否关联到正确的
seller_id; - 卖家账号是否填写了有效邮箱;
- MultiMerch 中的卖家订单通知是否启用;
- 邮件是否进入垃圾箱,或被 SMTP 服务拒绝。
单独修复 seller_text 错误
错误出现在:
catalog/view/theme/journal2/template/mail/order.tpl
经过修改后,该模板直接读取了:
$seller_text
但客户和管理员的标准订单邮件不一定会传入这个变量,因此模板需要允许该字段不存在。
如果第 93 行类似:
<?php echo $seller_text; ?>
可以改成:
<?php if (!empty($seller_text)) { ?>
<?php echo $seller_text; ?>
<?php } ?>
或者:
<?php echo isset($seller_text) ? $seller_text : ''; ?>
更合适的做法是在负责渲染邮件的 controller 或 MultiMerch 邮件类中提供默认值:
$data['seller_text'] = isset($data['seller_text'])
? $data['seller_text']
: '';
这样模板始终可以取得该键。
需要注意的是,消除 Undefined index 只能解决模板兼容问题,不能证明卖家邮件发送函数已经执行。如果 sendOrderMails() 根本没有被调用,只修改模板不会恢复卖家通知。
还要查明是哪条 vQmod/OCMOD 修改把 seller_text 加进了 order.tpl。最好在对应的 XML 修改中加入条件判断,不要直接编辑:
vqmod/vqcache/
system/storage/modification/
这些目录存放的是生成文件,刷新修改缓存后,直接编辑的内容会被覆盖。
补偿调用应该放在哪里
如果确认 Ajax Best Checkout 完全绕过了 MultiMerch 原来的挂载点,可以把卖家邮件调用加到该扩展真正完成订单确认的位置。这个位置通常需要满足以下条件:
- 已经取得有效的
$order_id; - 订单商品已经写入数据库;
createMsOrderDataEntries($order_id)已经完成;- 订单状态已更新为有效状态;
- 即将跳转到成功页;
- 每个订单只执行一次。
调用形式可以是:
if (class_exists('MsHelper') && MsHelper::isInstalled()) {
$this->MsLoader->MsMail->sendOrderMails($order_id);
}
如果当前 controller 尚未通过 MultiMerch 的核心修改获得 MsLoader,可以按照现有 MultiMerch 代码的初始化方式调用:
if (class_exists('MsHelper') && MsHelper::isInstalled()) {
MsLoader::getInstance()
->setRegistry($this->registry)
->MsMail
->sendOrderMails($order_id);
}
具体使用哪种形式,取决于 MultiMerch 8 在该站点中的 loader 初始化方式。应优先复制同版本 multimerch_core.xml 中已经使用并验证过的调用形式,不要自行创建新的 MsLoader 实例。
不要放进通用 Ajax controller
以下接口在结账过程中可能被多次调用:
country()
customfield()
shipping()
payment()
validate()
save()
如果把邮件调用放进这些方法,可能产生以下问题:
- 订单尚未创建,
$order_id不存在; - 页面每次刷新都会发送邮件;
- 更换地址或配送方式时重复发送;
- 支付失败时也会通知卖家。
因此,应修改 Ajax Best Checkout 的最终确认动作、支付成功回调或统一的订单状态更新模型,而不是你贴出的国家和自定义字段方法。
优先修复统一订单模型的挂载点
如果 Ajax Best Checkout 最终仍然调用:
catalog/model/checkout/order.php
最好修复 multimerch_core.xml 对该模型的匹配规则,而不是只修改 Ajax Best Checkout controller。这样,标准结账、Ajax 结账和其他支付方式都能使用同一个卖家通知入口。
调用顺序必须保证先创建 MultiMerch 订单商品数据,再发送卖家邮件。概念上的顺序如下:
$this->MsLoader->MsTransaction->createMsOrderDataEntries($order_id);
// OpenCart 保存订单状态、商品及其他订单信息
$this->MsLoader->MsMail->sendOrderMails($order_id);
如果先发送邮件,再创建 ms_order_product_data,MultiMerch 可能无法判断各件商品属于哪个卖家,最终找不到收件人或生成空白邮件内容。
multimerch_core_transactions.xml 虽然不直接发送邮件,但它能否正常执行会影响 sendOrderMails() 获取卖家订单数据。因此,还要检查对应的生成文件中是否存在:
$this->MsLoader->MsTransaction->createMsOrderDataEntries($order_id);
同时确认 ms_order_product_data 表中确实有新订单的记录。
防止重复发送
把调用补到 addOrderHistory() 一类方法时要格外谨慎,因为订单状态可能被多次更新。支付扩展、管理员操作和回调重试都可能再次执行该方法。
不能在每次状态变化时都无条件运行:
$this->MsLoader->MsMail->sendOrderMails($order_id);
至少应把条件限制为订单第一次从状态 0 进入有效状态。示意代码如下:
$order_info = $this->getOrder($order_id);
$old_order_status_id = $order_info
? (int)$order_info['order_status_id']
: 0;
// 执行原有的订单状态更新逻辑
if (
$old_order_status_id === 0 &&
(int)$order_status_id > 0 &&
class_exists('MsHelper') &&
MsHelper::isInstalled()
) {
$this->MsLoader->MsMail->sendOrderMails($order_id);
}
这只是调用条件的示例。变量的获取位置需要根据实际的 addOrderHistory() 代码调整,并确保读取的是更新前的状态。
如果 MultiMerch 本身已经实现发送记录或事件去重,应优先使用原有机制,避免另外建立一套不兼容的状态判断。
是否改用 OpenCart event
可以使用 event,但不建议把它作为首选方案。MultiMerch 8 已经通过 vQmod 集成订单流程,额外建立 event 后,如果原调用恢复,可能会导致重复发信。
只有同时满足以下条件,才适合改用 event:
- 已确认 MultiMerch 的 vQmod 无法稳定匹配第三方结账代码;
- 已确认当前安装的 OpenCart 2.3 支持对应的 model event route;
- event 能取得
$order_id和更新前后的订单状态; - 已移除或禁用原有的重复邮件挂载点;
- 已加入每个订单只发送一次的保护。
不同的 OpenCart 2.3 修改版和扩展可能使用不同的 event route。在没有查看实际代码和已注册事件之前,不能断定应使用某个固定 route。
建议的处理顺序
- 在最终修改文件和 vQmod 缓存中搜索
sendOrderMails。 - 查看
multimerch_core.xml中该调用对应的<file>和<search>。 - 检查 vQmod 日志中是否出现
SEARCH NOT FOUND。 - 找到 Ajax Best Checkout 真正执行
addOrder()或addOrderHistory()的确认代码。 - 确认
createMsOrderDataEntries($order_id)已执行,并检查ms_order_product_data中的新订单数据。 - 修正
multimerch_core.xml的匹配规则,优先恢复统一模型中的调用。 - 如果第三方结账完全绕过该模型,再到最终确认或支付成功的位置补充调用。
- 为模板中的
seller_text添加默认值或存在性判断。 - 清理 vQmod 缓存,并刷新 OpenCart 修改缓存。
- 先用只包含一个卖家商品的测试订单验证,再测试包含多个卖家商品的订单。
- 确认每个卖家只收到一封邮件,同时客户和管理员邮件仍能正常发送。
修改前应备份相关 XML 和模板。不要直接修改 vqmod/vqcache 或 system/storage/modification 下的生成文件,因为这些修改会在刷新缓存后消失。
备注:内容仅供参考。
