feat(wechat): 支付成功后微信客服消息推回执(积分包/订阅购买)
用户扫码付款后(在 48h 互动窗口内),主动推一条客服消息回执: 到账积分/开通套餐 + 当前余额。只在 MarkOrderPaid changed=true 首次到账时推, 异步+超时隔离,失败只记日志、绝不影响入账。非微信用户自动跳过。 不做「周期刷新提醒」:客服消息受 48h 窗口限制,定时刷新那刻用户多半已超窗、 必然失败,那类隔天提醒须用模板消息(暂缓)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -179,6 +179,35 @@ func (c Config) CreateLoginQR(ctx context.Context, accessToken, scene string, ex
|
||||
return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + url.QueryEscape(r.Ticket), nil
|
||||
}
|
||||
|
||||
// SendCustomText 发一条「客服消息」文本给指定 openid(主动推送)。
|
||||
// 约束:微信只允许在用户 48 小时内与公众号有过互动时下发(否则 45015 errcode)。
|
||||
// 适合支付回执这类「用户刚操作完」的即时通知;隔天的提醒需改用模板消息。
|
||||
// 需 access_token(走中控/直连均可)。
|
||||
func SendCustomText(ctx context.Context, accessToken, openID, content string) error {
|
||||
reqBody := map[string]any{
|
||||
"touser": openID,
|
||||
"msgtype": "text",
|
||||
"text": map[string]any{"content": content},
|
||||
}
|
||||
raw, _ := json.Marshal(reqBody)
|
||||
endpoint := "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + url.QueryEscape(accessToken)
|
||||
body, err := httpPost(ctx, endpoint, raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var r struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &r); err != nil {
|
||||
return fmt.Errorf("解析客服消息响应失败: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
if r.ErrCode != 0 {
|
||||
return fmt.Errorf("发送客服消息失败: errcode=%d errmsg=%s", r.ErrCode, r.ErrMsg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Event 是微信推送的事件(明文 XML)。只取登录/回复需要的字段。
|
||||
type Event struct {
|
||||
XMLName xml.Name `xml:"xml"`
|
||||
|
||||
Reference in New Issue
Block a user