File size: 4,081 Bytes
daa8246 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | package service
import (
"time"
"github.com/QuantumNous/new-api/model"
)
// ---------------------------------------------------------------------------
// FundingSource — 资金来源接口(钱包 or 订阅)
// ---------------------------------------------------------------------------
// FundingSource 抽象了预扣费的资金来源。
type FundingSource interface {
// Source 返回资金来源标识:"wallet" 或 "subscription"
Source() string
// PreConsume 从该资金来源预扣 amount 额度
PreConsume(amount int) error
// Settle 根据差额调整资金来源(正数补扣,负数退还)
Settle(delta int) error
// Refund 退还所有预扣费
Refund() error
}
// ---------------------------------------------------------------------------
// WalletFunding — 钱包资金来源实现
// ---------------------------------------------------------------------------
type WalletFunding struct {
userId int
consumed int // 实际预扣的用户额度
}
func (w *WalletFunding) Source() string { return BillingSourceWallet }
func (w *WalletFunding) PreConsume(amount int) error {
if amount <= 0 {
return nil
}
if err := model.DecreaseUserQuota(w.userId, amount); err != nil {
return err
}
w.consumed = amount
return nil
}
func (w *WalletFunding) Settle(delta int) error {
if delta == 0 {
return nil
}
if delta > 0 {
return model.DecreaseUserQuota(w.userId, delta)
}
return model.IncreaseUserQuota(w.userId, -delta, false)
}
func (w *WalletFunding) Refund() error {
if w.consumed <= 0 {
return nil
}
// IncreaseUserQuota 是 quota += N 的非幂等操作,不能重试,否则会多退额度。
// 订阅的 RefundSubscriptionPreConsume 有 requestId 幂等保护所以可以重试。
return model.IncreaseUserQuota(w.userId, w.consumed, false)
}
// ---------------------------------------------------------------------------
// SubscriptionFunding — 订阅资金来源实现
// ---------------------------------------------------------------------------
type SubscriptionFunding struct {
requestId string
userId int
modelName string
amount int64 // 预扣的订阅额度(subConsume)
subscriptionId int
preConsumed int64
// 以下字段在 PreConsume 成功后填充,供 RelayInfo 同步使用
AmountTotal int64
AmountUsedAfter int64
PlanId int
PlanTitle string
}
func (s *SubscriptionFunding) Source() string { return BillingSourceSubscription }
func (s *SubscriptionFunding) PreConsume(_ int) error {
// amount 参数被忽略,使用内部 s.amount(已在构造时根据 preConsumedQuota 计算)
res, err := model.PreConsumeUserSubscription(s.requestId, s.userId, s.modelName, 0, s.amount)
if err != nil {
return err
}
s.subscriptionId = res.UserSubscriptionId
s.preConsumed = res.PreConsumed
s.AmountTotal = res.AmountTotal
s.AmountUsedAfter = res.AmountUsedAfter
// 获取订阅计划信息
if planInfo, err := model.GetSubscriptionPlanInfoByUserSubscriptionId(res.UserSubscriptionId); err == nil && planInfo != nil {
s.PlanId = planInfo.PlanId
s.PlanTitle = planInfo.PlanTitle
}
return nil
}
func (s *SubscriptionFunding) Settle(delta int) error {
if delta == 0 {
return nil
}
return model.PostConsumeUserSubscriptionDelta(s.subscriptionId, int64(delta))
}
func (s *SubscriptionFunding) Refund() error {
if s.preConsumed <= 0 {
return nil
}
return refundWithRetry(func() error {
return model.RefundSubscriptionPreConsume(s.requestId)
})
}
// refundWithRetry 尝试多次执行退款操作以提高成功率,只能用于基于事务的退款函数!!!!!!
// try to refund with retries, only for refund functions based on transactions!!!
func refundWithRetry(fn func() error) error {
if fn == nil {
return nil
}
const maxAttempts = 3
var lastErr error
for i := 0; i < maxAttempts; i++ {
if err := fn(); err == nil {
return nil
} else {
lastErr = err
}
if i < maxAttempts-1 {
time.Sleep(time.Duration(200*(i+1)) * time.Millisecond)
}
}
return lastErr
}
|