batch_image_billing_hold.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. package service
  2. import (
  3. "context"
  4. "errors"
  5. "strings"
  6. "github.com/Wei-Shaw/sub2api/internal/pkg/logger"
  7. "go.uber.org/zap"
  8. )
  9. const (
  10. batchImageHoldRequestPrefix = "batch_image_hold:"
  11. batchImageCaptureRequestPrefix = "batch_image_capture:"
  12. batchImageReleaseRequestPrefix = "batch_image_release:"
  13. )
  14. func BatchImageHoldRequestID(batchID string) string {
  15. return batchImageHoldRequestPrefix + strings.TrimSpace(batchID)
  16. }
  17. func BatchImageCaptureRequestID(batchID string) string {
  18. return batchImageCaptureRequestPrefix + strings.TrimSpace(batchID)
  19. }
  20. func BatchImageReleaseRequestID(batchID string) string {
  21. return batchImageReleaseRequestPrefix + strings.TrimSpace(batchID)
  22. }
  23. func buildBatchImageHoldCommand(job *BatchImageJob, requestID string, actualAmount float64, payloadHash string) (*BatchImageBalanceHoldCommand, error) {
  24. if job == nil {
  25. return nil, ErrBatchImageBillingHoldFailed
  26. }
  27. if job.APIKeyID == nil || *job.APIKeyID <= 0 {
  28. return nil, ErrBatchImageSettlementMissingAPIKeyID
  29. }
  30. holdAmount := job.EstimatedCost
  31. if job.HoldAmount != nil {
  32. holdAmount = *job.HoldAmount
  33. }
  34. if holdAmount < 0 {
  35. holdAmount = 0
  36. }
  37. if actualAmount < 0 {
  38. actualAmount = 0
  39. }
  40. return &BatchImageBalanceHoldCommand{
  41. RequestID: requestID,
  42. APIKeyID: *job.APIKeyID,
  43. UserID: job.UserID,
  44. BatchID: job.BatchID,
  45. HoldAmount: holdAmount,
  46. ActualAmount: actualAmount,
  47. RequestPayloadHash: strings.TrimSpace(payloadHash),
  48. }, nil
  49. }
  50. func reserveBatchImageBalanceHold(ctx context.Context, repo UsageBillingRepository, job *BatchImageJob, payloadHash string) error {
  51. if repo == nil {
  52. return ErrBatchImageBillingHoldFailed.WithCause(errors.New("batch image billing repository is not configured"))
  53. }
  54. cmd, err := buildBatchImageHoldCommand(job, BatchImageHoldRequestID(job.BatchID), 0, payloadHash)
  55. if err != nil {
  56. return err
  57. }
  58. if cmd.HoldAmount <= 0 {
  59. return nil
  60. }
  61. if _, err := repo.ReserveBatchImageBalance(ctx, cmd); err != nil {
  62. if errors.Is(err, ErrBatchImageInsufficientBalance) {
  63. return ErrBatchImageInsufficientBalance
  64. }
  65. return ErrBatchImageBillingHoldFailed.WithCause(err)
  66. }
  67. return nil
  68. }
  69. func captureBatchImageBalanceHold(ctx context.Context, repo UsageBillingRepository, job *BatchImageJob, actualAmount float64, payloadHash string) error {
  70. if repo == nil {
  71. return ErrBatchImageSettlementBillingFailed.WithCause(errors.New("batch image billing repository is not configured"))
  72. }
  73. cmd, err := buildBatchImageHoldCommand(job, BatchImageCaptureRequestID(job.BatchID), actualAmount, payloadHash)
  74. if err != nil {
  75. return err
  76. }
  77. if _, err := repo.CaptureBatchImageBalance(ctx, cmd); err != nil {
  78. return ErrBatchImageSettlementBillingFailed.WithCause(err)
  79. }
  80. return nil
  81. }
  82. func releaseBatchImageBalanceHold(ctx context.Context, repo UsageBillingRepository, job *BatchImageJob, payloadHash string) error {
  83. if repo == nil || job == nil {
  84. return nil
  85. }
  86. cmd, err := buildBatchImageHoldCommand(job, BatchImageReleaseRequestID(job.BatchID), 0, payloadHash)
  87. if err != nil {
  88. return err
  89. }
  90. if cmd.HoldAmount <= 0 {
  91. return nil
  92. }
  93. if _, err := repo.ReleaseBatchImageBalance(ctx, cmd); err != nil {
  94. // 同一 release request id 出现指纹冲突,说明此前已有一次携带不同
  95. // payloadHash 的释放成功提交(资金已归还)。视为幂等成功,
  96. // 避免历史指纹不一致的 job 永远卡在释放失败的毒消息循环里。
  97. if errors.Is(err, ErrUsageBillingRequestConflict) {
  98. logger.L().Warn("batch_image.release_fingerprint_conflict_treated_as_released",
  99. zap.String("batch_id", job.BatchID),
  100. )
  101. return nil
  102. }
  103. return ErrBatchImageBillingHoldFailed.WithCause(err)
  104. }
  105. return nil
  106. }