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; }
}