client_renewal_preview.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. package service
  2. import (
  3. "time"
  4. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  5. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  6. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  7. )
  8. type ClientRenewalPreviewRequest struct {
  9. ExpiryTime int64 `json:"expiryTime" example:"1893456000000"`
  10. Reset int `json:"reset" example:"0"`
  11. ResetDay int `json:"resetDay" example:"1"`
  12. ResetWeekday int `json:"resetWeekday" example:"0"`
  13. ResetMax int `json:"resetMax" example:"0"`
  14. ResetCount int `json:"resetCount" example:"0"`
  15. }
  16. type ClientRenewalPreview struct {
  17. TimeZone string `json:"timeZone" example:"UTC"`
  18. RenewAt string `json:"renewAt" example:"2030-01-01T00:00:00Z"`
  19. ValidThrough string `json:"validThrough" example:"2029-12-31T23:59:59Z"`
  20. NextExpiry string `json:"nextExpiry" example:"2030-02-01T00:00:00Z"`
  21. SuggestedExpiryTime int64 `json:"suggestedExpiryTime" example:"1893456000000"`
  22. SuggestedExpiry string `json:"suggestedExpiry" example:"2030-01-01T00:00:00Z"`
  23. Renewals int `json:"renewals" example:"1"`
  24. CanRenew bool `json:"canRenew" example:"true"`
  25. DelayedStart bool `json:"delayedStart" example:"false"`
  26. }
  27. func (s *ClientService) PreviewRenewal(request ClientRenewalPreviewRequest, settings *SettingService) (*ClientRenewalPreview, error) {
  28. client := model.Client{Reset: request.Reset, ResetDay: request.ResetDay, ResetWeekday: request.ResetWeekday}
  29. if err := validateClientRenewal(client); err != nil {
  30. return nil, err
  31. }
  32. if err := validateClientResetMax(request.ResetMax); err != nil {
  33. return nil, err
  34. }
  35. if request.ResetCount < 0 || request.Reset < 0 {
  36. return nil, common.NewError("renewal preview reset and resetCount must not be negative")
  37. }
  38. loc, err := settings.GetTimeLocation()
  39. if err != nil {
  40. return nil, err
  41. }
  42. return previewClientRenewal(request, time.Now().UnixMilli(), loc)
  43. }
  44. func previewClientRenewal(request ClientRenewalPreviewRequest, now int64, loc *time.Location) (*ClientRenewalPreview, error) {
  45. preview := &ClientRenewalPreview{TimeZone: loc.String(), DelayedStart: request.ExpiryTime < 0}
  46. if request.ResetDay > 0 || request.ResetWeekday > 0 {
  47. preview.SuggestedExpiryTime = nextClientRenewal(now, request.Reset, request.ResetDay, request.ResetWeekday, loc)
  48. if preview.SuggestedExpiryTime <= now {
  49. return nil, common.NewError("calendar renewal could not find a future expiry")
  50. }
  51. preview.SuggestedExpiry = time.UnixMilli(preview.SuggestedExpiryTime).In(loc).Format(time.RFC3339)
  52. }
  53. if request.ExpiryTime <= 0 || (request.Reset <= 0 && request.ResetDay <= 0 && request.ResetWeekday <= 0) {
  54. return preview, nil
  55. }
  56. at := canonicalRenewalExpiry(request.ExpiryTime, request.Reset, request.ResetDay, request.ResetWeekday, loc)
  57. preview.RenewAt = time.UnixMilli(at).In(loc).Format(time.RFC3339Nano)
  58. // Billing alignment does not rewrite the stored exclusive deadline; the
  59. // preview must not promise an extra second before the first renewal.
  60. preview.ValidThrough = time.UnixMilli(request.ExpiryTime - 1).In(loc).Format(time.RFC3339)
  61. traffic := &xray.ClientTraffic{
  62. ExpiryTime: request.ExpiryTime, Reset: request.Reset, ResetDay: request.ResetDay,
  63. ResetWeekday: request.ResetWeekday, ResetMax: request.ResetMax, ResetCount: request.ResetCount,
  64. }
  65. expiry, renewals := catchUpClientRenewal(traffic, max(now, at+1), loc)
  66. if renewals == 0 && request.ResetWeekday > 0 && (request.ResetMax == 0 || request.ResetCount < request.ResetMax) {
  67. return nil, common.NewError("calendar renewal could not find a future expiry")
  68. }
  69. preview.Renewals = renewals
  70. preview.CanRenew = renewals > 0 && expiry > max(now, at)
  71. if renewals > 0 {
  72. preview.NextExpiry = time.UnixMilli(expiry).In(loc).Format(time.RFC3339Nano)
  73. }
  74. return preview, nil
  75. }