using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; using NodaTime; namespace DysonNetwork.Sphere.Wallet; public abstract class SubscriptionType { /// /// DO NOT USE THIS TYPE DIRECTLY, /// this is the prefix of all the stellar program subscriptions. /// public const string StellarProgram = "solian.stellar"; /// /// No actual usage, just tells there is a free level named twinkle. /// Applies to every registered user by default, so there is no need to create a record in db for that. /// public const string Twinkle = "solian.stellar.twinkle"; public const string Stellar = "solian.stellar.primary"; public const string Nova = "solian.stellar.nova"; public const string Supernova = "solian.stellar.supernova"; } public abstract class SubscriptionPaymentMethod { /// /// The solar points / solar dollars. /// public const string InAppWallet = "solian.wallet"; /// /// afdian.com /// aka. China patreon /// public const string Afdian = "afdian"; } public enum SubscriptionStatus { Unpaid, Paid, Expired, Cancelled } /// /// The subscription is for the Stellar Program in most cases. /// The paid subscription in another word. /// [Index(nameof(Identifier))] public class Subscription : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); public Instant BegunAt { get; set; } public Instant? EndedAt { get; set; } /// /// The type of the subscriptions /// [MaxLength(4096)] public string Identifier { get; set; } = null!; /// /// The field is used to override the activation status of the membership. /// Might be used for refund handling and other special cases. /// /// Go see the IsAvailable field if you want to get real the status of the membership. /// public bool IsActive { get; set; } = true; /// /// Indicates is the current user got the membership for free, /// to prevent giving the same discount for the same user again. /// public bool IsFreeTrial { get; set; } public SubscriptionStatus Status { get; set; } = SubscriptionStatus.Unpaid; [MaxLength(4096)] public string PaymentMethod { get; set; } = null!; [Column(TypeName = "jsonb")] public PaymentDetails PaymentDetails { get; set; } = null!; public decimal BasePrice { get; set; } public Guid? CouponId { get; set; } public Coupon? Coupon { get; set; } public Instant? RenewalAt { get; set; } public Guid AccountId { get; set; } public Account.Account Account { get; set; } = null!; [NotMapped] public bool IsAvailable { get { if (!IsActive) return false; var now = SystemClock.Instance.GetCurrentInstant(); if (BegunAt > now) return false; if (EndedAt.HasValue && now > EndedAt.Value) return false; if (RenewalAt.HasValue && now > RenewalAt.Value) return false; if (Status != SubscriptionStatus.Paid) return false; return true; } } [NotMapped] public decimal FinalPrice { get { if (Coupon == null) return BasePrice; var now = SystemClock.Instance.GetCurrentInstant(); if (Coupon.AffectedAt.HasValue && now < Coupon.AffectedAt.Value || Coupon.ExpiredAt.HasValue && now > Coupon.ExpiredAt.Value) return BasePrice; if (Coupon.DiscountAmount.HasValue) return BasePrice - Coupon.DiscountAmount.Value; if (Coupon.DiscountRate.HasValue) return BasePrice * (decimal)(1 - Coupon.DiscountRate.Value); return BasePrice; } } public SubscriptionReferenceObject ToReference() { return new SubscriptionReferenceObject { Id = Id, BegunAt = BegunAt, EndedAt = EndedAt, Identifier = Identifier, IsActive = IsActive, AccountId = AccountId, CreatedAt = CreatedAt, UpdatedAt = UpdatedAt, DeletedAt = DeletedAt, }; } } public class PaymentDetails { public string Currency { get; set; } = null!; public string? OrderId { get; set; } } public class SubscriptionReferenceObject : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); public Instant BegunAt { get; set; } public Instant? EndedAt { get; set; } [MaxLength(4096)] public string Identifier { get; set; } = null!; public bool IsActive { get; set; } = true; public Guid AccountId { get; set; } } /// /// A discount that can applies in purchases among the Solar Network. /// For now, it can be used in the subscription purchase. /// public class Coupon : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); /// /// The items that can apply this coupon. /// Leave it to null to apply to all items. /// [MaxLength(4096)] public string? Identifier { get; set; } /// /// The code that human-readable and memorizable. /// Leave it blank to use it only with the ID. /// [MaxLength(1024)] public string? Code { get; set; } public Instant? AffectedAt { get; set; } public Instant? ExpiredAt { get; set; } /// /// The amount of the discount. /// If this field and the rate field are both not null, /// the amount discount will be applied and the discount rate will be ignored. /// Formula: final price = base price - discount amount /// public decimal? DiscountAmount { get; set; } /// /// The percentage of the discount. /// If this field and the amount field are both not null, /// this field will be ignored. /// Formula: final price = base price * (1 - discount rate) /// public double? DiscountRate { get; set; } /// /// The max usage of the current coupon. /// Leave it to null to use it unlimited. /// public int? MaxUsage { get; set; } }