funding_source.go 4.0 KB

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