feat(server/pay): 首充钩子 — settle 内接邀请首充段发奖(first_paid_at 幂等)

This commit is contained in:
wangjia
2026-07-13 07:41:15 +08:00
parent 54d106ca8b
commit de1117b0e6
3 changed files with 82 additions and 0 deletions
+20
View File
@@ -114,3 +114,23 @@ func (s *Service) OnRegister(ctx context.Context, inviteeID int64, inviteCode, d
slog.Warn("reward: commit failed", "err", err)
}
}
// OnFirstPaidTx 在 pay webhook 的同一事务内调用:标记 first_paid_at,若确为首充且该用户
// 是被邀请人(referral 命中且未发过首充奖)→ 发首充段双方各 PaidDays 天。幂等。
func (s *Service) OnFirstPaidTx(ctx context.Context, tx *sql.Tx, inviteeID int64, now time.Time) error {
first, err := s.st.MarkFirstPaidTx(ctx, tx, inviteeID, now)
if err != nil || !first {
return err // 非首充 → 不发
}
inviterID, status, ok, err := s.st.ReferralByInvitee(ctx, tx, inviteeID)
if err != nil || !ok || status == "paid_rewarded" || status == "rejected" {
return err // 无邀请关系 / 已发过 / 被拒 → 不发
}
if _, _, err := s.g.GrantRewardTx(ctx, tx, inviterID, s.cfg.PaidDays, "invite", "invite_paid_inviter", "ref-paid-inviter"); err != nil {
return err
}
if _, _, err := s.g.GrantRewardTx(ctx, tx, inviteeID, s.cfg.PaidDays, "invite", "invite_paid_invitee", "ref-paid-invitee"); err != nil {
return err
}
return s.st.MarkPaidRewardedTx(ctx, tx, inviteeID, now)
}