维护说明:本文档已并入本站直接维护(单仓重构后原 backend/docs/*.md、前端 docs/*.md 不再随仓库发布)。
实现细节以 pay-unify 源码 为准,页面与源码的对应关系见相关资源。
PowerWeChat 接入企业微信通知指南
1. 目标
本文档说明如何在本项目中使用 github.com/ArtisanCloud/PowerWeChat/v3 接入企业微信通知,并在支付成功后自动发送一条企业微信应用消息。
文档覆盖三部分:
- PowerWeChat 的基础用法
- 本项目当前的实现方式
- 如何自行扩展和排查问题
2. 本项目当前实现概览
本项目已经完成以下接入:
- 引入
PowerWeChat v3
- 新增
WeComNotificationService 作为企业微信通知服务
- 在支付成功统一收口
PaymentHandler.notify(...) 中触发通知
- 扩展
WorkWechatConfig,用于控制企业微信通知开关和接收人
- 增加配置校验,避免开启通知后仍缺少关键配置
当前代码位置:
- 企业微信通知服务:
internal/pkg/service/wecom_notification_service.go
- 支付成功通知接入点:
internal/handler/payment_handler.go
- 企业微信配置结构:
internal/core/types/config.go
- 企业微信配置校验:
internal/pkg/validation/config_validator.go
- 配置示例:
config.toml.example
3. 为什么使用 PowerWeChat
PowerWeChat 是一个对微信生态接口做过封装的 Go SDK,适合直接调用企业微信的应用消息接口。
在这个场景下,它的价值是:
- 已经封装好 access token 获取与刷新
- 已经封装好企业微信应用消息发送接口
- 不需要手写 HTTP 请求和签名逻辑
- 适合放在服务层单独封装,和支付逻辑解耦
4. 版本选择
本项目当前使用:
1github.com/ArtisanCloud/PowerWeChat/v3 v3.4.30
原因:
- 项目当前 Go 版本是
go1.24.4
- 直接执行
go get -u github.com/ArtisanCloud/PowerWeChat/v3 会把依赖升级到需要 Go 1.25 的版本链
v3.4.30 可以在当前仓库和当前 Go 版本下正常编译
因此,本项目建议使用:
1go get github.com/ArtisanCloud/PowerWeChat/v3@v3.4.30
而不是直接无约束执行:
1go get -u github.com/ArtisanCloud/PowerWeChat/v3
5. 企业微信通知的前提条件
要发送企业微信通知,你需要先在企业微信后台准备好一个“企业应用”,并拿到以下信息:
CorpID
AgentID
Secret
- 接收消息的成员、部门或标签
在企业微信后台中,应用消息发送本质上依赖这几个参数:
- 企业 ID
- 应用 AgentID
- 应用 Secret
touser / toparty / totag
如果这几个参数不完整,就算 SDK 初始化成功,也无法把消息发到正确对象。
6. PowerWeChat 的最小发送示例
下面是一个最小可运行的企业微信文本消息发送示例,逻辑和本项目采用的方法一致。
1package main
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 powercache "github.com/ArtisanCloud/PowerLibs/v3/cache"
9 "github.com/ArtisanCloud/PowerWeChat/v3/src/work"
10 wecomrequest "github.com/ArtisanCloud/PowerWeChat/v3/src/work/message/request"
11)
12
13func main() {
14 app, err := work.NewWork(&work.UserConfig{
15 CorpID: "wwxxxxxxxxxxxxxxxx",
16 AgentID: 1000002,
17 Secret: "your_agent_secret",
18 ResponseType: "json",
19 Cache: powercache.NewMemCache("demo-wecom", 2*time.Hour, ""),
20 Http: work.Http{
21 Timeout: 5,
22 },
23 })
24 if err != nil {
25 panic(err)
26 }
27
28 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
29 defer cancel()
30
31 resp, err := app.Message.SendText(ctx, &wecomrequest.RequestMessageSendText{
32 RequestMessageSend: wecomrequest.RequestMessageSend{
33 ToUser: "@all",
34 MsgType: "text",
35 AgentID: 1000002,
36 },
37 Text: &wecomrequest.RequestText{
38 Content: "测试通知:支付成功",
39 },
40 })
41 if err != nil {
42 panic(err)
43 }
44
45 fmt.Printf("errcode=%d errmsg=%s\n", resp.ErrCode, resp.ErrMsg)
46}
这个示例里最关键的调用链是:
work.NewWork(...) 初始化企业微信应用客户端
app.Message.SendText(...) 发送文本消息
7. 本项目里是怎么封装的
7.1 服务初始化
本项目把企业微信通知封装成了一个独立服务:
1type WeComNotificationService struct {
2 appConfig *types.AppConfig
3 logger *zap.SugaredLogger
4 client *work.Work
5}
初始化逻辑在 NewWeComNotificationService(...) 中完成。
核心代码逻辑:
1client, err := work.NewWork(&work.UserConfig{
2 CorpID: strings.TrimSpace(appConfig.WorkWechatConfig.CorpID),
3 AgentID: appConfig.WorkWechatConfig.AgentID,
4 Secret: strings.TrimSpace(appConfig.WorkWechatConfig.DefaultAgentSecret),
5 Token: strings.TrimSpace(appConfig.WorkWechatConfig.Token),
6 AESKey: strings.TrimSpace(appConfig.WorkWechatConfig.EncodingAESKey),
7 ResponseType: "json",
8 Cache: powercache.NewMemCache("pay-unify-wecom", 2*time.Hour, ""),
9 Http: work.Http{
10 Timeout: 5,
11 },
12 Debug: appConfig.Debug,
13})
这里各字段作用如下:
CorpID:企业微信企业 ID
AgentID:发送消息使用的企业应用 ID
Secret:企业应用 Secret
ResponseType:要求 SDK 使用 JSON 结构处理响应
Cache:本地内存缓存,用于 access token 缓存
Http.Timeout:接口调用超时控制
7.2 消息发送
发送逻辑在 SendPaymentSuccessNotification(...) 中:
1request := &wecomrequest.RequestMessageSendText{
2 RequestMessageSend: wecomrequest.RequestMessageSend{
3 ToUser: strings.TrimSpace(s.appConfig.WorkWechatConfig.NotifyToUser),
4 ToParty: strings.TrimSpace(s.appConfig.WorkWechatConfig.NotifyToParty),
5 ToTag: strings.TrimSpace(s.appConfig.WorkWechatConfig.NotifyToTag),
6 MsgType: "text",
7 AgentID: s.appConfig.WorkWechatConfig.AgentID,
8 EnableDuplicateCheck: 1,
9 DuplicateCheckInterval: 1800,
10 },
11 Text: &wecomrequest.RequestText{
12 Content: s.buildPaymentSuccessMessage(order),
13 },
14}
15
16result, err := s.client.Message.SendText(ctx, request)
这段代码说明:
- 使用的是企业微信“应用消息”的文本类型
- 接收对象支持用户、部门、标签三种范围
- 开启了重复消息检查,降低重复通知概率
- 消息内容来自支付订单对象,按文本拼接输出
8. 为什么要单独封装成服务
不建议把 PowerWeChat 的发送逻辑直接写进支付 handler。
单独封装成 WeComNotificationService 的好处:
- 支付逻辑和通知逻辑解耦
- 以后可以扩展为短信、邮件、钉钉等多通知渠道
- 便于单独测试和替换实现
- 便于在
fx 中做统一依赖注入
本项目通过下面这行把服务注入容器:
1fx.Provide(service.NewWeComNotificationService)
9. 为什么把通知挂在支付成功统一收口
本项目把支付成功统一收口放在:
1func (h *PaymentHandler) notify(orderNo string, tradeNo string) error
这个函数负责:
- 查订单
- 做并发锁保护
- 判断是否已支付,避免重复处理
- 更新订单状态为支付成功
- 为订阅类订单增加 VIP 天数
- 发送企业微信通知
通知放在这个位置的原因是:
- 支付宝、微信支付、PayPal 都会经过这个函数
- 只需要接一次,就覆盖所有支付渠道
- 只在真正完成订单成功落库后才发通知
- 不会在支付预下单阶段误发通知
当前接入逻辑:
1if h.WeComNotifier != nil {
2 if err := h.WeComNotifier.SendPaymentSuccessNotification(&order); err != nil {
3 h.App.SugarLogger.Errorf("发送企业微信支付通知失败: orderNo=%s, err=%v", orderNo, err)
4 } else {
5 h.App.SugarLogger.Infof("企业微信支付通知发送成功: orderNo=%s", orderNo)
6 }
7}
这里特意做成“通知失败不影响支付主流程”,因为支付成功是核心事务,企业微信通知只是附加能力。
10. 配置说明
10.1 配置结构
当前项目中的企业微信配置结构如下:
1type WorkWechatConfig struct {
2 Enabled bool
3 CorpID string
4 Token string
5 EncodingAESKey string
6 DefaultAgentSecret string
7 AgentID int
8 PaymentNotifyEnabled bool
9 NotifyToUser string
10 NotifyToParty string
11 NotifyToTag string
12 CustomerServiceSecret string
13}
10.2 示例配置
你可以在 config.toml 中这样配置:
1[WorkWechatConfig]
2 Enabled = true
3 PaymentNotifyEnabled = true
4 CorpID = "wwxxxxxxxxxxxxxxxx"
5 AgentID = 1000002
6 DefaultAgentSecret = "your_work_wechat_agent_secret"
7 Token = ""
8 EncodingAESKey = ""
9 NotifyToUser = "@all"
10 NotifyToParty = ""
11 NotifyToTag = ""
12 CustomerServiceSecret = ""
字段说明:
Enabled:企业微信能力总开关
PaymentNotifyEnabled:支付成功通知开关
CorpID:企业微信企业 ID
AgentID:企业应用 AgentID
DefaultAgentSecret:企业应用 Secret
NotifyToUser:通知成员,多个成员用 | 分隔,例如 zhangsan|lisi
NotifyToParty:通知部门,多个部门用 | 分隔
NotifyToTag:通知标签,多个标签用 | 分隔
10.3 接收对象规则
企业微信应用消息允许三种投递方式:
touser
toparty
totag
本项目要求至少配置其中一项。
校验逻辑已经加在配置验证器中,如果你开启了 PaymentNotifyEnabled=true,但没有配置接收人,服务启动时会报错。
11. 当前通知消息内容
当前发送的是文本消息,内容格式如下:
1支付成功通知
2应用ID:xxx
3订单号:xxx
4交易号:xxx
5支付方式:wechat
6订单主题:VIP会员30天
7支付金额:99.00
8订单类型:vip
9用户ID:user_123
10支付时间:2026-03-19 12:00:00
消息生成逻辑在:
1func (s *WeComNotificationService) buildPaymentSuccessMessage(order *model.TbPaymentOrder) string
如果你后续想改成更适合运营看的格式,可以直接修改这个函数。
12. 如何自己扩展
12.1 扩展为模板卡片或富文本
当前是最稳妥的文本消息。如果后续需要更强展示能力,可以考虑:
- 文本卡片
- 模板卡片
- 图文消息
这时只需要替换 app.Message.SendText(...) 的请求结构,不需要动支付成功收口逻辑。
12.2 扩展为异步发送
如果你担心支付回调里调用企业微信接口增加时延,可以进一步改成异步发送:
- 回调里只投递一个 goroutine
- 或者把通知写入队列
- 由后台 worker 再发送企业微信通知
当前版本没有这么做,是因为:
- 逻辑更简单
- 当前只是一条文本消息
- 已经设置了 10 秒超时
- 失败不会影响支付主流程
12.3 扩展为多业务通知
目前方法名是:
1SendPaymentSuccessNotification(order *model.TbPaymentOrder)
如果后续要支持退款通知、订单关闭通知、告警通知,可以继续加:
SendRefundNotification(...)
SendOrderClosedNotification(...)
SendSystemAlert(...)
13. 启用步骤
如果你要在当前仓库里真正启用企业微信通知,按下面顺序即可:
步骤 1:配置企业微信应用
在企业微信后台获取:
CorpID
AgentID
Secret
并确认该应用可见范围包含你要接收消息的成员或部门。
步骤 2:填写配置文件
在 config.toml 中填写:
1[WorkWechatConfig]
2 Enabled = true
3 PaymentNotifyEnabled = true
4 CorpID = "你的 CorpID"
5 AgentID = 你的 AgentID
6 DefaultAgentSecret = "你的 Secret"
7 NotifyToUser = "@all"
步骤 3:启动服务
1go build . ./internal/...
2./pay-unify
或者使用你当前仓库的启动方式。
步骤 4:触发一次真实支付成功
让订单真正走到:
1PaymentHandler.notify(orderNo, tradeNo)
成功后会自动发送企业微信通知。
步骤 5:检查日志
成功时日志类似:
1企业微信支付通知发送成功: orderNo=...
失败时日志类似:
1发送企业微信支付通知失败: orderNo=..., err=...
14. 常见问题
Q1:为什么通知没有发出去,但支付成功了?
这是当前设计使然。
企业微信通知是附加能力,支付成功是主流程。为了避免因为企业微信接口波动导致支付回调失败,当前实现是:
- 先更新订单成功状态
- 再尝试发送企业微信通知
- 企业微信发送失败只记日志,不回滚支付成功
Q2:为什么一定要配置 AgentID?
因为企业微信应用消息是按应用发送的,AgentID 用来指定“哪一个企业应用”发出消息。
只有 CorpID + Secret 不够,还必须知道发送消息的应用 ID。
Q3:Token 和 EncodingAESKey 是不是必须?
对于“单向发送应用消息”这个场景,不是必须。
当前保留它们是为了和现有 WorkWechatConfig 结构保持一致,也为以后接企业微信回调预留字段。
Q4:为什么要加本地缓存?
PowerWeChat 在获取 access token 时依赖缓存接口。
当前实现使用:
1powercache.NewMemCache("pay-unify-wecom", 2*time.Hour, "")
这样可以:
- 避免每次发消息都重新拉 token
- 减少接口请求次数
- 降低 SDK 初始化后的额外复杂度
Q5:为什么不直接自己发 HTTP 请求?
可以自己写,但没必要。
PowerWeChat 已经把下面这些内容封装好了:
- token 获取和刷新
- 企业微信 API 地址
- 请求结构与响应结构
- 错误字段映射
在这个项目里,直接复用 SDK 的成本更低,也更稳。
15. 最小接入结论
如果只看最核心的接入步骤,可以压缩成下面四步:
- 引入依赖:
1go get github.com/ArtisanCloud/PowerWeChat/v3@v3.4.30
- 初始化企业微信应用:
1app, err := work.NewWork(&work.UserConfig{ ... })
- 发送文本消息:
1resp, err := app.Message.SendText(ctx, request)
- 把发送调用挂到你的业务成功节点,比如支付成功、退款成功、告警触发等。
16. 建议
如果你后续还要继续完善这块,建议按这个顺序推进:
- 先用
NotifyToUser = "@all" 跑通链路
- 再收敛到具体成员或部门
- 再决定是否改成模板卡片
- 最后再考虑异步化和失败重试
这条路线最稳,排障成本也最低。