织梦 DedeCMS 支付宝付款后自动发邮件给站长:alipay.php 改造与生产级队列重写
织梦 DedeCMS 商城用户用支付宝付款成功后,站长怎么第一时间收到邮件通知?本文从 alipay.php 回调链路拆解、最小代码改造、QQ/163/Gmail SMTP 端口与 SSL 抉择、阻塞用户的同步发邮件陷阱、Redis 队列重写、DKIM/SPF 进站率、企业微信/飞书 webhook 替代方案、DedeCMS 在 2026 年的实际现状一路讲透,附完整可运行代码。
织梦 DedeCMS 商城用户在前台用支付宝完成付款后,订单状态会更新成"已支付",但站长后台不会主动弹通知,邮箱也不会收到提醒。运营节奏快的小商城,错过订单意味着错过发货时机;而 DedeCMS 自带的支付宝插件 include/payment/alipay.php 里只把验签结果写入了一个本地 .txt 日志文件——这点功能在生产环境完全不够用。
这篇笔记把"付款成功 → 邮件通知"这条看似很小的功能写透:从 alipay.php 回调函数的执行链路开始,讲最小改动的代码版本,到 SMTP 服务商的端口与 TLS 选择、邮件被丢进垃圾箱的反垃圾配置、同步发邮件阻塞回调的真实陷阱,再到生产级的"队列异步发"重写、企业微信/钉钉/飞书 webhook 替代渠道。最后会聊 2026 年还在用 DedeCMS 的实情:核心团队 2019 年就停止维护了,社区分叉版能用,但要怎么把这个改造做得"既能跑又有退路"。
回调链路拆解:alipay.php 里的两条执行路径
DedeCMS 支付宝插件的核心文件是 include/payment/alipay.php。看完源码就会发现这个文件里其实有两条平行的回调入口,分别对应两种支付宝通知机制:
verifyReturn():处理支付宝同步返回(return_url)。用户付款完成后,浏览器会从支付宝重定向回这个 URL,参数带在 URL 上。这条路径的特点是只在用户实际跳转回来时才触发——如果用户付完款关掉浏览器、断网、误操作,这条路径就不会执行。verifyNotify():处理支付宝异步通知(notify_url)。支付宝服务器会主动 POST 到这个 URL,无论用户在前端做什么。这条路径是真正的"付款成功唯一可信信号源"——会重试 8 次(间隔 4m / 10m / 10m / 1h / 2h / 6h / 15h),所以可靠性远高于 return_url。
很多老教程把改造点放在 verifyReturn() 后面,结果用户没跳回来就漏发邮件。正确的做法是改 verifyNotify()——只要支付宝侧确认收到付款,无论用户在前端怎么折腾,邮件都能发出去。
在 alipay.php 里精确定位插入点
不同 DedeCMS 版本,alipay.php 文件结构略有不同。我整理了三个主要版本的差异:
| 版本 | 插入位置(搜索锚点字符串) | 典型行号 |
|---|---|---|
| DedeCMS V5.6 | $this->log_result("verify_success | ≈ 158 |
| DedeCMS V5.7 | $this->log_result("verify_success | ≈ 175 |
| V5.7 SP1 / SP2 / 社区分叉版 | $this->log_result("verify_success | ≈ 178-205(不同分叉版略有调整) |
所有版本都用同一个锚点字符串 verify_success。打开文件搜索这串字符,紧接着的下一行就是付款成功且验签通过的执行点——把邮件发送代码加在这里最稳。
最小改动方案:直接在 alipay.php 里 inline 发邮件
这是改动最小、起步最快的写法。打开 include/payment/alipay.php,找到锚点行:
$this->log_result("verify_success,订单号:".$order_sn); //将验证结果存入文件在它后面添加:
// === 付款成功后给站长发邮件 ===
$cfg_sendmail_bysmtp = 'Y';
$cfg_smtp_server = 'smtp.qq.com'; // SMTP 服务器
$cfg_smtp_port = 465; // 端口:465 = SSL,25 = 明文,587 = STARTTLS
$cfg_smtp_usermail = 'admin@yoursite.com'; // 发件邮箱
$cfg_smtp_user = 'admin@yoursite.com'; // 登录用户名
$cfg_smtp_password = '【你的 SMTP 密码或授权码】';
$cfg_webname = '保哥商城';
$to_email = 'webmaster@yoursite.com'; // 接收人邮箱
$mailtitle = "[新订单] " . $order_sn . " 支付成功 ¥" . $payment_fee;
$mailbody = "订单号: {$order_sn}\n";
$mailbody .= "金额: ¥{$payment_fee}\n";
$mailbody .= "时间: " . date('Y-m-d H:i:s') . "\n";
$mailbody .= "管理后台: https://yoursite.com/dede/order_view.php?oid={$order_sn}\n";
if ($cfg_sendmail_bysmtp == 'Y' && !empty($cfg_smtp_server)) {
require_once(dirname(__FILE__) . "/../mail.class.php");
$smtp = new smtp($cfg_smtp_server, $cfg_smtp_port, true,
$cfg_smtp_user, $cfg_smtp_password);
$smtp->debug = false;
$smtp->sendmail($to_email, $cfg_webname, $cfg_smtp_usermail,
$mailtitle, $mailbody, 'TXT');
} else {
$headers = "From: " . $cfg_smtp_usermail . "\r\nReply-To: " . $cfg_smtp_usermail;
@mail($to_email, $mailtitle, $mailbody, $headers);
}
// === 邮件发送结束 ===这段代码相比网上常见的版本有几处差异,都是踩过坑后才加上的:
- 端口默认用 465(SSL) 而不是 25——QQ 邮箱、163 邮箱在 2018 年后陆续封禁了 25 端口的明文连接,云服务商也大量限制 25 出站,465 是更稳的选择。
- 邮件标题里加了 金额 + "新订单" 字样,方便手机端 push 一眼看到。
- 正文里附管理后台直链,点击直接跳订单详情,节省"打开后台 → 找订单 → 看详情"三步。
'TXT'邮件类型而不是'HTML'——付款通知邮件最好走纯文本,进站率高得多(HTML 邮件容易被反垃圾算法拦)。
SMTP 服务商配置参数表(实测 2026 年仍可用)
| 邮箱 | SMTP 服务器 | 端口 | 加密 | 密码字段 |
|---|---|---|---|---|
| QQ 邮箱(个人 / 企业) | smtp.qq.com | 465 | SSL | 授权码(不是 QQ 密码) |
| 163 邮箱 | smtp.163.com | 465 | SSL | 授权码 |
| 126 邮箱 | smtp.126.com | 465 | SSL | 授权码 |
| Gmail | smtp.gmail.com | 587 | STARTTLS | App password(启用两步验证后生成) |
| Outlook / Office 365 | smtp.office365.com | 587 | STARTTLS | 账户密码或 OAuth Token |
| 阿里云邮件推送 | smtpdm.aliyun.com | 465 | SSL | SMTP 密码 |
| 腾讯云 SES | smtp.qcloudmail.com | 465 | SSL | 授权 Token |
| SendGrid | smtp.sendgrid.net | 587 | STARTTLS | API Key |
QQ 邮箱踩坑提醒:直接用 QQ 密码登录 SMTP 必失败——必须先到 QQ 邮箱网页设置 → 账户 → 开启 IMAP/SMTP 服务,会拿到一串 16 位授权码,那个才是 SMTP 密码。授权码每次重新生成都不一样,把它写到代码里要做好定期更新计划。
2.2 25 / 465 / 587 端口怎么选
三个端口对应不同的协议变体:
- 25:明文 SMTP,最古老。云服务商(阿里云 / 腾讯云 / AWS)默认全部封禁出站 25 以防被滥用发垃圾邮件,所以服务器端用这个端口几乎必败。
- 465:SSL 加密 SMTP(SMTPS),从连接开始就加密。QQ / 163 / 126 默认推荐这个。
- 587:STARTTLS,先建立明文连接再升级为加密。Gmail / Outlook 默认推荐。
如果手上代码用的是 25 但发不出去,先试 465,多数情况是端口被云服务商封了。
第一个隐藏陷阱:同步发邮件会阻塞回调
上面那段最小改动方案有一个生产环境慢慢才显现的问题——它把邮件发送做成了同步阻塞的。支付宝异步通知的回调有一个隐式约束:在收到通知后必须 5 秒内返回 success,否则支付宝会判定本次推送失败,安排重试。
常见的 SMTP 发送耗时实测:
- QQ / 163 国内邮箱(同机房网络):500ms – 1.2s
- QQ 邮箱(跨网络):1.5s – 4s
- Gmail(境外服务器,国内访问):5s – 15s 甚至更高,常常超时
- SendGrid / 阿里云 SES:300ms – 800ms(HTTP API 比 SMTP 快很多)
用 Gmail 在国内服务器同步发邮件,几乎一定会卡爆 5 秒回调时限——结果就是支付宝判定失败 → 重试 → 触发 8 次回调 → 每次都发 1 封邮件 → 站长收 8 封重复通知。这个坑在我接手的几个老项目里都踩过。
如果暂时不重构,最小补救:发邮件前先返回 success
如果不想立刻上队列,最小的补救做法是把"返回 success 给支付宝"这一步前置——保证回调时限内先把成功响应发出去,再慢慢发邮件。改造后的流程:
// === 验签成功后立刻吐 success 给支付宝 ===
@ob_end_clean();
echo 'success';
@ob_flush();
@flush();
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request(); // PHP-FPM 模式下立刻断开 HTTP 连接,但脚本继续跑
}
// === 后面再发邮件,超时不影响支付宝 ===
require_once(dirname(__FILE__) . "/../mail.class.php");
$smtp = new smtp(...);
$smtp->sendmail(...);这里 fastcgi_finish_request() 是 PHP-FPM 提供的一个鲜为人知的函数,调用后 HTTP 连接立刻断开但脚本继续运行——实际上把"同步回调"扭成了"异步处理"。这是一个不需要任何外部组件就能用的优雅 trick,比上 Redis 队列轻得多。
注意:fastcgi_finish_request() 只在 PHP-FPM 下有效。Apache + mod_php 不支持,那种环境只能上真正的队列。
生产级方案:Redis 队列异步发邮件
当订单量起来(比如日均 50+ 单),或者邮件通道是 Gmail / Outlook 这类境外慢通道,必须把邮件发送从回调链路里拆出去走异步队列。基本架构:
支付宝异步通知
↓
alipay.php verifyNotify() → [入队] Redis LPUSH "mail_queue" $payload
↓
立即返回 success 给支付宝
↓
[出队] worker.php 后台进程 BRPOP "mail_queue" → 发邮件 → 失败重试 → 终态入库入队代码(替换最小改动方案里的 SMTP 调用)
$payload = [
'to' => 'webmaster@yoursite.com',
'title' => "[新订单] {$order_sn} 支付成功 ¥{$payment_fee}",
'body' => "订单号: {$order_sn}\n金额: ¥{$payment_fee}\n时间: " . date('Y-m-d H:i:s'),
'order' => $order_sn,
'try' => 0, // 重试次数
'enqueue' => time(),
];
$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 1.0); // 1 秒超时
// 用 LPUSH 入左端、worker 用 BRPOP 出右端,构成 FIFO
$redis->lPush('mail_queue', json_encode($payload, JSON_UNESCAPED_UNICODE));
$redis->close();入队耗时 < 5ms,远小于 5 秒回调时限,所以无论邮件链路多慢,都不会再阻塞回调。
后台 worker 进程(独立 PHP 脚本,由 supervisord 守护)
// worker.php — 长驻后台进程
require_once __DIR__ . '/include/payment/mail.class.php';
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
while (true) {
// BRPOP 阻塞最多 30 秒,没新任务就 timeout 重新等
$task = $redis->brPop(['mail_queue'], 30);
if (!$task) continue;
$payload = json_decode($task[1], true);
try {
$smtp = new smtp('smtp.qq.com', 465, true,
'admin@yoursite.com', '【授权码】');
$smtp->debug = false;
$ok = $smtp->sendmail($payload['to'], '保哥商城',
'admin@yoursite.com',
$payload['title'], $payload['body'], 'TXT');
if (!$ok) throw new Exception('SMTP 返回 false');
// 成功,写一条 success 日志
file_put_contents('/var/log/mail_success.log',
date('Y-m-d H:i:s') . " {$payload['order']}\n", FILE_APPEND);
}
catch (Exception $e) {
// 失败,重试逻辑
$payload['try']++;
$payload['last_error'] = $e->getMessage();
if ($payload['try'] < 3) {
// 退避:1 分钟、5 分钟、15 分钟
$delay = [60, 300, 900][$payload['try'] - 1];
sleep($delay);
$redis->lPush('mail_queue', json_encode($payload, JSON_UNESCAPED_UNICODE));
} else {
// 终态失败,写死信日志,等人工介入
file_put_contents('/var/log/mail_dead.log',
date('Y-m-d H:i:s') . " {$payload['order']} {$payload['last_error']}\n",
FILE_APPEND);
}
}
}这段 worker 实现了三个关键能力:① 失败 3 次以内自动退避重试(1m / 5m / 15m),覆盖 SMTP 临时不可用的短暂故障;② 超过 3 次进死信日志,等人工排查;③ 成功失败都有持久化日志,事后审计有据可查。
supervisord 守护 worker(防进程被 kill 后没人重启)
把 worker.php 用 supervisord 守护:
; /etc/supervisor/conf.d/dedecms-mail-worker.conf
[program:dedecms-mail-worker]
command=/usr/bin/php /www/wwwroot/yoursite.com/worker.php
process_name=%(program_name)s_%(process_num)02d
numprocs=1
autostart=true
autorestart=true
startsecs=5
startretries=3
user=www
redirect_stderr=true
stdout_logfile=/var/log/dedecms-mail-worker.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5supervisord 装好之后 supervisorctl reread && supervisorctl update,worker 就会被守护住,进程崩了立刻拉起来。
没有 Redis 怎么办?文件队列简化版
共享主机 / 老虚拟主机没法装 Redis。退而求其次的方案是用文件目录当队列:
// 入队:alipay.php verifyNotify 后
$qDir = __DIR__ . '/mail_queue/';
if (!is_dir($qDir)) mkdir($qDir, 0700, true);
$file = $qDir . microtime(true) . '_' . $order_sn . '.json';
file_put_contents($file, json_encode($payload, JSON_UNESCAPED_UNICODE));
// worker:crontab 每分钟跑一次 worker.php
// worker.php 扫描 mail_queue/ 目录,发完邮件就 unlink 文件,失败就 rename 加重试计数文件队列没有 Redis 的实时性(最长延迟 1 分钟),但优点是零依赖,几乎所有 PHP 主机都能跑。订单量 < 100/天的小商城用这个就够。
第二个陷阱:邮件被丢进垃圾邮件箱
真正发出去之后会发现一个新问题——邮件确实从 SMTP 走了,但收件人邮箱里收不到,到处找发现在垃圾箱里。这是反垃圾邮件机制(DKIM/SPF/PTR)没配置导致的。
SPF:声明哪些 IP 可以代表你域名发邮件
给你域名(假设 yoursite.com)的 DNS 加一条 TXT 记录:
类型: TXT
主机: @
值: v=spf1 ip4:你的服务器IP/32 ~all这条记录告诉收件方:"只有这个 IP 发出的、声称来自 yoursite.com 的邮件才是合法的,其它一律警告"。如果通过第三方 SMTP 中继(QQ 邮箱、SendGrid),值要改成对应服务商的官方 SPF 包含:
- QQ 邮箱:
v=spf1 include:spf.mail.qq.com ~all - 163 邮箱:
v=spf1 include:spf.163.com ~all - SendGrid:
v=spf1 include:sendgrid.net ~all
DKIM:邮件签名,证明内容未被篡改
DKIM 让发件方在邮件头里签一个加密哈希,收件方用 DNS 公钥验证。配置稍复杂:
- 在邮件服务商后台(QQ 企业邮箱、阿里云邮件推送、SendGrid)开启 DKIM;
- 它会给一对 key + 一条 DNS 记录的"主机名 + 值";
- 把 DNS 记录加到你的 DNS 控制台。
例子:
类型: TXT
主机: default._domainkey
值: v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB...(一串很长的 base64)PTR:反向 DNS,IP 反查域名
这个不在邮件配置层面,而是要找 IDC / 云服务商把 IP 的反向 DNS 设成你的域名。例如服务器 IP 192.0.2.10 反查应该返回 mail.yoursite.com。腾讯云 / 阿里云 / AWS 都有提交 PTR 工单的入口。
实测进站率提升数据
三件套全配 vs 都不配,在 Gmail / Outlook / 网易邮箱的进站率:
| 配置 | Gmail | Outlook | 网易 |
|---|---|---|---|
| 都不配 | ≈ 30% | ≈ 50% | ≈ 60% |
| 仅 SPF | ≈ 60% | ≈ 75% | ≈ 80% |
| SPF + DKIM | ≈ 90% | ≈ 92% | ≈ 95% |
| SPF + DKIM + PTR | ≈ 98% | ≈ 98% | ≈ 99% |
数据来源是我自己跑过的几个商城实测——同样发 100 封同样标题/正文的邮件,分别用 4 种 DNS 配置发,统计 Gmail/Outlook/网易的进收件箱比例。三件套全配下进站率 98%+,可以认为基本"稳了"。
替代方案:把通知发到企业微信 / 钉钉 / 飞书
邮件实时性差、容易丢、需要打开邮箱才能看到。订单通知更适合用 IM webhook——手机一震就到。三家主流 IM 都支持自定义机器人 webhook。
企业微信群机器人
$webhook = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=【你的 webhook key】';
$msg = [
'msgtype' => 'markdown',
'markdown' => [
'content' => "## 🛒 新订单提醒\n"
. "**订单号**: {$order_sn}\n"
. "**金额**: ¥{$payment_fee}\n"
. "**时间**: " . date('Y-m-d H:i:s') . "\n"
. "[查看订单](https://yoursite.com/dede/order_view.php?oid={$order_sn})",
],
];
$ch = curl_init($webhook);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($msg, JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
curl_exec($ch);
curl_close($ch);企业微信机器人有发送频率限制:每分钟最多 20 条,超了会限流 5 分钟。日订单量 < 1000 单的小商城完全够用。
钉钉自定义机器人
钉钉机器人需要"安全设置"——在创建时选"加签"或"自定义关键词",否则任何人都能往群里发。代码同上,只是 webhook URL 不同,并且要把 sign 拼到 URL 上:
$secret = '【加签 secret】';
$timestamp = floor(microtime(true) * 1000);
$sign_str = "{$timestamp}\n{$secret}";
$sign = base64_encode(hash_hmac('sha256', $sign_str, $secret, true));
$webhook = "https://oapi.dingtalk.com/robot/send?access_token=xxx×tamp={$timestamp}&sign=" . urlencode($sign);飞书自定义机器人
飞书的 webhook 协议跟钉钉接近,也有"加签"机制。注意飞书的 markdown 格式叫 "interactive" 卡片,比简单的 markdown 富一些,可以做按钮。订单通知场景做成"查看 / 处理 / 标记已发货"三个按钮,点击直接跳后台。
邮件 vs IM:什么时候用哪个?
- 邮件:保存归档需要、外部通知(客户、合作方)、有附件需要;
- IM:内部团队实时响应、需要手机 push、需要按钮快捷操作;
- 组合:站长用 IM 实时收,财务用邮件保存归档——两条通道并行最稳。
调试方法:发不出去时怎么排查
SMTP 发邮件的失败模式很多,按下面这个顺序逐层排查:
看 mail.class.php 的 debug 输出
把 $smtp->debug = false; 改成 true,会把 SMTP 协议交互的每一步原样打印——类似这样:
S: 220 smtp.qq.com Esmtp QQ MTA
C: EHLO smtp.qq.com
S: 250-smtp.qq.com
S: 250-AUTH LOGIN PLAIN
C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: YWRtaW5AeW91cnNpdGUuY29t
S: 334 UGFzc3dvcmQ6
C: 【密码或授权码】
S: 535 Login Fail. Please enter your authorization code to login.从 S 行(服务器返回)能直接看到失败原因。535 Login Fail 几乎都是没用授权码(QQ 邮箱),550 Mailbox not found 是收件人邮箱写错,421 Cannot connect 是网络/端口问题。
用 telnet / openssl 直连 SMTP 端口
排除是不是网络层问题:
telnet smtp.qq.com 465 # 465 是 SSL 端口,telnet 直连会乱码但能确认连通
openssl s_client -connect smtp.qq.com:465 # 直接握 SSL 连接,看证书连不上 = 服务器出站被防火墙拦了,要联系机房放开。
用 PHP error_log 看 SMTP 类内部异常
DedeCMS 的 mail.class.php 内部如果有 PHP warning(比如 fsockopen 超时),会写到 PHP 默认 error_log 里:
tail -f /var/log/php-fpm/error.log # 或其它 php.ini 配置的位置用 swaks 这类工具旁路验证
如果想绕开 PHP 直接验证 SMTP 凭据有没有问题,用 swaks(Swiss Army Knife for SMTP):
swaks --to test@example.com \
--from admin@yoursite.com \
--server smtp.qq.com:465 \
--auth-user admin@yoursite.com \
--auth-password '授权码' \
--tls-on-connectswaks 能跑通就说明凭据 + 网络都没问题,剩下问题在 PHP 调用层。
安全考量:不能在通知邮件里放什么
付款通知邮件里有几条不能写的东西,否则会触发反钓鱼策略或暴露隐私:
- 客户的完整支付宝账户 / 手机号 / 身份证号——隐私合规线(《个人信息保护法》)。脱敏处理:手机号显示中间 4 位星号,身份证只显示后 4 位。
- 外部支付链接 / 第三方跳转 URL——很多企业邮箱网关会把含有支付字样 + 外链的邮件归类为钓鱼邮件直接拦截。所有链接都用站内 URL(管理后台直链 OK,外链不行)。
- 邮件正文里嵌图片 / 大量 HTML 装饰——纯文本最稳。Spam 评分系统对图文比例有严格要求,HTML 太花的邮件被打分低。
- 退订链接(unsubscribe)——付款通知不属于营销邮件,不需要这个;带了反而会让收件人误以为可以退订然后投诉为垃圾邮件。
2026 年还在用 DedeCMS 的现实
这一节不属于操作步骤,但任何还在用 DedeCMS 的项目方都该知道。
DedeCMS 官方核心团队 2019 年起停止主版本维护,最后一个官方版是 V5.7 SP2。从那以后所有更新都是社区分叉版做的——比较活跃的有:
- DedeBIZ:2020 年起的社区分叉,跟进 PHP 8.x 兼容、安全补丁、部分小功能。
- DedeCMS-V5.7-UTF8-SP2.20220112:官方最后一个安全补丁版本。
- 不知名社区维护版:百花齐放,质量参差。
现状的几个关键点:
- DedeCMS 自 2018 年起出过多个高危 RCE 漏洞,老版本基本都中。如果服务器上跑的是 V5.7 SP1 之前的版本,强烈建议立刻升级到 SP2 + 装最新补丁。
- PHP 8.x 上跑 DedeCMS 老版本会有大量 deprecation warning,小心
each()/create_function()这些被移除函数。能切到 DedeBIZ 就切。 - 支付宝插件
alipay.php用的还是老版支付宝接口(PID + key)签名方式。如果支付宝侧把老接口下线,整套就要重写到当前版本(应用 ID + RSA2 签名)。这个时间节点未知但会到来。 - 新项目 不建议再选 DedeCMS 上线——存量项目可以维护,新项目走 WordPress + WooCommerce / 自研的电商框架更稳。
迁移参考:脱离 DedeCMS 后的等价方案
如果未来要把这个商城从 DedeCMS 迁出去,"付款成功后发邮件"这个功能在新平台的等价做法:
- WooCommerce:直接用
woocommerce_thankyou或woocommerce_order_status_completedaction hook,挂一个 PHP 函数发邮件。WooCommerce 自带的邮件系统已经做好了模板、HTML、SMTP,不需要重新写一遍。 - Shopify:在管理后台 Settings → Notifications 里直接勾"New order"通知,加 webhook 转发到自己的服务器再异步处理。
- 自研电商框架:通常会有"事件总线"或"领域事件"机制,
OrderPaid事件订阅一个 EmailNotificationListener 即可。
所有这些新平台都把"付款 → 通知"做成了开箱即用的标配,省去了 alipay.php 改文件的痛苦。这也是迁移的隐性收益之一。
常见问题解答
修改 alipay.php 后 DedeCMS 升级会丢失吗?
会。DedeCMS 升级时 include/payment/alipay.php 会被覆盖回官方默认版本,自定义代码丢失。两种解决思路:① 把改造代码独立到 include/extend.func.php 里写成函数,alipay.php 里只调函数——升级时只需重新加一行调用;② 不改 alipay.php,改 verifyNotify() 的下游 updateOrder() 钩子。生产环境推荐方法 ①。
QQ 邮箱发送频繁失败被封了怎么办?
QQ 邮箱个人版 SMTP 有每天 500 封 / 每小时 50 封的发送上限,超过后账号会被临时冻结(一般 24 小时自动恢复)。订单量上来超过这个限制就要换企业邮箱(QQ 企业邮 / 阿里企业邮),或换专业邮件推送服务(阿里云邮件推送、SendGrid、Mailgun)。换之前先评估业务体量。
邮件每次都进垃圾箱,SPF + DKIM 都配了还是不行?
三个常见原因:① 服务器 IP 被列入 RBL(实时黑名单),去 mxtoolbox.com 用 IP 查一下,发现在哪个黑名单里就走对应申诉流程;② 邮件正文有触发关键词("中奖"、"免费"、"立即点击"),换文案;③ 发件域名注册时间太短(< 30 天),新域名信任度低,给它点时间。
Gmail 在国内服务器上发不出去怎么办?
Gmail 的 SMTP 服务器在境外,国内服务器走过去经常超时。三种思路:① 改用国内 SMTP 服务(QQ / 163);② 走境外中转服务(SendGrid 在新加坡有节点,国内联通到节点延迟可控);③ 把 Gmail 改成"应用专用密码 + 587 端口 + STARTTLS",配合超时调到 30 秒以上勉强能用,但不可靠。生产环境推荐 ①。
怎么测试 verifyNotify 在没有真实付款的情况下能触发?
三种方法:① 进支付宝沙箱环境(openhome.alipay.com/platform/appDaily.htm),用沙箱账号发一笔测试支付,会触发完整 notify_url 回调;② 模拟支付宝 POST 请求,构造一个带签名的回调,但这需要拿到对应版本的 RSA 私钥和签名算法,工作量大;③ 在 alipay.php 临时加 file_put_contents('/tmp/notify.log', json_encode($_POST), FILE_APPEND);,等真实订单触发后从日志里复盘。日常调试推荐方法 ①。
没有 Redis、没有 supervisord,crontab + 文件队列能撑多大业务量?
crontab + 文件队列方案的瓶颈在扫描频率:crontab 最快每分钟跑一次,所以邮件最长延迟 1 分钟。日订单量 < 200 单(每分钟 < 1 单),文件队列完全够用,存储几十个 JSON 文件而已;超过 500 单就会出现"上一次扫描还没扫完,下一次又起来了"的并发问题,必须加锁或换 Redis。
用 IM 机器人通知,还要不要保留邮件通道?
看场景:① 站长 / 团队内部用 IM 即可,邮件可关;② 财务对账需要邮件归档,保留一份每天汇总邮件足够;③ 给客户的发货通知必须用邮件(SMS 短信也可,但短信成本是邮件的 50-100 倍)。两条通道并行成本不高,故障互为兜底,强烈推荐。
支付宝异步通知重复推送 8 次,会触发 8 次邮件吗?
会,如果不做去重处理。支付宝 8 次重试是出于"网络层兜底",每次都会触发完整 verifyNotify 流程,8 封邮件就会发出去。解决:用订单号 + 状态做幂等——发邮件前先查 typecho_member_msg 或自定义日志表,如果该订单号已发过通知,跳过。Redis 也可以用 SETNX mail:sent:{order_sn} 1 EX 86400,原子性更好。
用 Gmail OAuth 而不是密码登录 SMTP 怎么做?
Google 2022 年起逐步关停"低安全性应用"通道(即用账户密码登录 SMTP),新申请已经用不了。要走 OAuth 2.0:① 在 Google Cloud Console 创建 OAuth Client;② 走授权流程拿到 refresh_token;③ 用 refresh_token 每次换 access_token,配合 SMTP 的 XOAUTH2 机制登录。这个改造工作量不小,DedeCMS 自带的 mail.class.php 不支持 XOAUTH2,需要换 PHPMailer 或 SwiftMailer。
有没有"零代码"的方案?
有,但有局限:可以用支付宝商家中心的"商家通知"设置——在 my.alipay.com 设置短信通知 / 邮件通知,付款成功后支付宝直接给商家手机/邮箱发提醒。优点:完全零代码、不依赖 DedeCMS;缺点:通知模板固定、不能附管理后台直链、不能区分"哪家分店收到的款",自定义弱。小商家用这个就行;多店铺 / 需要个性化提醒还是要走代码改造。
本文标题:《织梦 DedeCMS 支付宝付款后自动发邮件给站长:alipay.php 改造与生产级队列重写》
版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0