using System.Text.Json; using Tiku.Application.Commerce; using Tiku.Application.Security; using Tiku.Domain.Platform; namespace Tiku.Application.PlatformBilling; public sealed record SaasCatalogActor(Guid UserId); public sealed record TenantBillingActor(Guid UserId, Guid TenantId); public sealed record UpsertSaasFeatureCommand( Guid? Id, string Code, string Name, string Category, string? Description, int ReferencePriceCents, string Currency, SaasFeatureStatus Status, int SortOrder); public sealed record UpsertSaasOfferingCommand( Guid? Id, string Code, string Name, SaasOfferingType Type, SaasOfferingStatus Status, string? Description, int SortOrder); public sealed record UpsertSaasFeatureLimitCommand( Guid? Id, string MetricCode, string FeatureCode, string Name, string Unit, SaasFeatureLimitKind Kind, int WarningPercent, bool IsHardLimit); public sealed record UpsertSaasOfferingVersionCommand( Guid? Id, Guid OfferingId, PlatformBillingCycle BillingCycle, int OriginalAmountCents, int AmountCents, string Currency, DateTimeOffset? EffectiveAt, IReadOnlyCollection FeatureCodes, IReadOnlyDictionary Limits, JsonElement Metadata); public sealed record SaasOfferingVersionItem( Guid Id, Guid OfferingId, string OfferingCode, string OfferingName, SaasOfferingType OfferingType, int Version, SaasOfferingVersionStatus Status, PlatformBillingCycle BillingCycle, int OriginalAmountCents, int AmountCents, string Currency, DateTimeOffset? EffectiveAt, DateTimeOffset? PublishedAt, IReadOnlyCollection FeatureCodes, IReadOnlyDictionary Limits); public sealed record SaasCatalogSnapshot( IReadOnlyCollection Features, IReadOnlyCollection LimitDefinitions, IReadOnlyCollection Offerings, IReadOnlyCollection Versions); public interface ISaasCatalogAdminService { Task GetCatalogAsync(SaasCatalogActor actor, CancellationToken cancellationToken = default); Task UpsertFeatureAsync(SaasCatalogActor actor, UpsertSaasFeatureCommand command, CancellationToken cancellationToken = default); Task UpsertLimitDefinitionAsync(SaasCatalogActor actor, UpsertSaasFeatureLimitCommand command, CancellationToken cancellationToken = default); Task UpsertOfferingAsync(SaasCatalogActor actor, UpsertSaasOfferingCommand command, CancellationToken cancellationToken = default); Task UpsertDraftVersionAsync(SaasCatalogActor actor, UpsertSaasOfferingVersionCommand command, CancellationToken cancellationToken = default); Task PublishVersionAsync(SaasCatalogActor actor, Guid versionId, CancellationToken cancellationToken = default); Task CloneVersionAsync(SaasCatalogActor actor, Guid versionId, CancellationToken cancellationToken = default); Task RetireVersionAsync(SaasCatalogActor actor, Guid versionId, CancellationToken cancellationToken = default); } public sealed record TenantBillingCatalog( IReadOnlyCollection Features, IReadOnlyCollection BasePlans, IReadOnlyCollection AddOns); public sealed record CreatePlatformBillingQuoteCommand( Guid BaseOfferingVersionId, IReadOnlyCollection AddOnOfferingVersionIds, PlatformBillingOrderPurpose Purpose, string IdempotencyKey); public sealed record PlatformBillingQuoteItemView( Guid OfferingVersionId, string OfferingCode, string OfferingName, PlatformBillingItemType ItemType, int UnitAmountCents, int AmountCents); public sealed record PlatformBillingQuoteView( Guid Id, string QuoteNo, PlatformBillingQuoteStatus Status, PlatformBillingOrderPurpose Purpose, int OriginalAmountCents, int DiscountAmountCents, int TotalAmountCents, string Currency, DateTimeOffset ExpiresAt, IReadOnlyCollection Items, IReadOnlyCollection FeatureCodes, IReadOnlyDictionary Limits); public sealed record CreatePlatformBillingOrderCommand(Guid QuoteId, string IdempotencyKey); public sealed record CreatePlatformBillingPaymentCommand( string OrderNo, string Provider, string Method, string IdempotencyKey, string? OpenId, string? ReturnUrl, string? QuitUrl); public sealed record PlatformBillingOrderView( Guid Id, string OrderNo, PlatformBillingOrderPurpose Purpose, PlatformBillingOrderStatus Status, int TotalAmountCents, string Currency, DateTimeOffset ExpiresAt, DateTimeOffset? PaidAt, IReadOnlyCollection Items); public sealed record PlatformBillingPaymentView( Guid Id, string PaymentNo, string Provider, string Method, PlatformBillingPaymentStatus Status, int AmountCents, JsonElement ClientPayload); public sealed record TenantSubscriptionView( Guid Id, TenantSaasSubscriptionStatus Status, DateTimeOffset StartsAt, DateTimeOffset CurrentPeriodStart, DateTimeOffset CurrentPeriodEnd, bool CancelAtPeriodEnd, Guid BaseOfferingVersionId, Guid? ScheduledBaseOfferingVersionId, IReadOnlyCollection FeatureCodes); public sealed record ChangeTenantSubscriptionCommand(Guid BaseOfferingVersionId, IReadOnlyCollection AddOnOfferingVersionIds); public interface ITenantBillingService { Task GetCatalogAsync(TenantBillingActor actor, CancellationToken cancellationToken = default); Task CreateQuoteAsync(TenantBillingActor actor, CreatePlatformBillingQuoteCommand command, CancellationToken cancellationToken = default); Task CreateOrderAsync(TenantBillingActor actor, CreatePlatformBillingOrderCommand command, CancellationToken cancellationToken = default); Task CreatePaymentAsync(TenantBillingActor actor, CreatePlatformBillingPaymentCommand command, CancellationToken cancellationToken = default); Task> GetOrdersAsync(TenantBillingActor actor, int limit, CancellationToken cancellationToken = default); Task GetOrderAsync(TenantBillingActor actor, string orderNo, CancellationToken cancellationToken = default); Task GetSubscriptionAsync(TenantBillingActor actor, CancellationToken cancellationToken = default); Task ChangeSubscriptionAsync(TenantBillingActor actor, ChangeTenantSubscriptionCommand command, string idempotencyKey, CancellationToken cancellationToken = default); Task RenewSubscriptionAsync(TenantBillingActor actor, string idempotencyKey, CancellationToken cancellationToken = default); Task CancelSubscriptionAsync(TenantBillingActor actor, CancellationToken cancellationToken = default); Task> GetUsageAsync(TenantBillingActor actor, CancellationToken cancellationToken = default); Task> GetInvoicesAsync(TenantBillingActor actor, int limit, CancellationToken cancellationToken = default); } public sealed record PlatformBillingAdminQuery(Guid? TenantId, string? Status, int Limit = 100); public sealed record ConfirmManualPaymentCommand(Guid PaymentId, string? ProviderTradeNo, DateTimeOffset? PaidAt, string Reason); public sealed record UpsertTenantFeatureOverrideCommand(Guid TenantId, string FeatureCode, TenantFeatureOverrideMode Mode, DateTimeOffset? ExpiresAt, string Reason); public interface IPlatformBillingAdminService { Task> GetOrdersAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); Task> GetPaymentsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); Task> GetRefundsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); Task> GetInvoicesAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); Task> GetUsageAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); Task> GetInvoiceRemindersAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); Task> GetSubscriptionsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); Task ConfirmManualPaymentAsync(SaasCatalogActor actor, ConfirmManualPaymentCommand command, CancellationToken cancellationToken = default); Task UpsertFeatureOverrideAsync(SaasCatalogActor actor, UpsertTenantFeatureOverrideCommand command, CancellationToken cancellationToken = default); } public interface IPlatformBillingPaymentGateway { Task CreatePaymentAsync(string provider, CreatePaymentProviderRequest request, CancellationToken cancellationToken = default); Task ParseNotificationAsync(string provider, PaymentNotificationRequest request, CancellationToken cancellationToken = default); } public sealed record PlatformBillingNotification( string Provider, IReadOnlyDictionary Headers, string RawBody, JsonElement Body); public interface IPlatformBillingNotificationService { Task ProcessAsync(PlatformBillingNotification notification, CancellationToken cancellationToken = default); } public interface IPlatformBillingSettlementService { Task MarkPaidAsync( Guid paymentId, string providerEventId, string eventType, string? providerTradeNo, DateTimeOffset paidAt, JsonElement payload, Guid? actorUserId, CancellationToken cancellationToken = default); } public sealed class SaasSubscriptionLifecycleOptions { public bool Enabled { get; set; } = true; public int BatchSize { get; set; } = 100; public int PastDueGraceDays { get; set; } = 7; } public interface ISaasSubscriptionLifecycleService { Task ProcessDueAsync( DateTimeOffset? asOf = null, CancellationToken cancellationToken = default); } public sealed class PlatformBillingException(string message, string code) : Exception(message) { public string Code { get; } = code; }