feat(server/pay): 首充钩子 — settle 内接邀请首充段发奖(first_paid_at 幂等)
This commit is contained in:
@@ -35,6 +35,7 @@ type WebhookHandler struct {
|
||||
tolerance time.Duration
|
||||
nonceTTL time.Duration
|
||||
now func() time.Time // 测试注入
|
||||
rewarder Rewarder // 可选:首充邀请奖励钩子(nil 则跳过)
|
||||
}
|
||||
|
||||
func NewWebhookHandler(store *Store, granter Granter, db *sql.DB, rdb *redis.Client,
|
||||
@@ -43,6 +44,14 @@ func NewWebhookHandler(store *Store, granter Granter, db *sql.DB, rdb *redis.Cli
|
||||
system: system, secret: secret, tolerance: tolerance, nonceTTL: nonceTTL, now: time.Now}
|
||||
}
|
||||
|
||||
// Rewarder(可选)在首充同事务内发放邀请首充奖励;nil 则跳过。
|
||||
type Rewarder interface {
|
||||
OnFirstPaidTx(ctx context.Context, tx *sql.Tx, inviteeID int64, now time.Time) error
|
||||
}
|
||||
|
||||
// SetRewarder 挂载首充邀请奖励钩子(reward.Service 满足此接口)。
|
||||
func (h *WebhookHandler) SetRewarder(r Rewarder) { h.rewarder = r }
|
||||
|
||||
// webhookEvent 对应 pay settle.go::enqueuePaymentSucceeded 的 payload
|
||||
// (注意:payment.succeeded 无 refund_id 字段)。
|
||||
type webhookEvent struct {
|
||||
@@ -172,5 +181,10 @@ func (h *WebhookHandler) settle(ctx context.Context, ev *webhookEvent) error {
|
||||
if err := h.store.MarkPaidTx(ctx, tx, purchaseID, ev.AmountMinor, ev.Currency, ev.Channel, subID, paidAt); err != nil {
|
||||
return err
|
||||
}
|
||||
if h.rewarder != nil {
|
||||
if err := h.rewarder.OnFirstPaidTx(ctx, tx, userID, h.now().UTC()); err != nil {
|
||||
return err // 同事务:发奖失败则整笔回滚,webhook 重试
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user