feat(wechat): 支付成功后微信客服消息推回执(积分包/订阅购买)

用户扫码付款后(在 48h 互动窗口内),主动推一条客服消息回执:
到账积分/开通套餐 + 当前余额。只在 MarkOrderPaid changed=true 首次到账时推,
异步+超时隔离,失败只记日志、绝不影响入账。非微信用户自动跳过。

不做「周期刷新提醒」:客服消息受 48h 窗口限制,定时刷新那刻用户多半已超窗、
必然失败,那类隔天提醒须用模板消息(暂缓)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-21 11:23:41 +08:00
parent 83269e067a
commit 68608c1592
4 changed files with 129 additions and 2 deletions
@@ -147,7 +147,10 @@ func (h *Handler) reconcileOrder(ctx context.Context, o *store.PaymentOrder) (*s
if r, err := ch.QueryOrder(ctx, o.ID); err == nil {
switch {
case r.Paid && r.AmountFen == o.AmountFen:
if _, err := h.db.MarkOrderPaid(ctx, o.ID, r.ChannelTxn); err == nil {
if changed, err := h.db.MarkOrderPaid(ctx, o.ID, r.ChannelTxn); err == nil {
if changed {
h.notifyOrderPaid(o.ID) // 首次到账才推回执,避免重复查单重复推送
}
o, _ = h.db.GetOrder(ctx, o.ID)
}
case r.Paid: // 金额对不上:不入账,人工对账(比错账便宜)
@@ -198,10 +201,14 @@ func (h *Handler) PaymentCallback(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": "SUCCESS"})
return
}
if _, err := h.db.MarkOrderPaid(ctx, o.ID, r.ChannelTxn); err != nil {
changed, err := h.db.MarkOrderPaid(ctx, o.ID, r.ChannelTxn)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": "FAIL", "message": "入账失败"})
return
}
if changed {
h.notifyOrderPaid(o.ID) // 首次到账才推回执(回调可能重复推送,靠 changed 去重)
}
c.JSON(http.StatusOK, gin.H{"code": "SUCCESS"})
}