Files
tiku-backend.net/Tiku.Application/PlatformBilling/PlatformBillingContracts.cs

245 lines
11 KiB
C#

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<string> FeatureCodes,
IReadOnlyDictionary<string, long> 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<string> FeatureCodes,
IReadOnlyDictionary<string, long> Limits);
public sealed record SaasCatalogSnapshot(
IReadOnlyCollection<SaasFeature> Features,
IReadOnlyCollection<SaasFeatureLimitDefinition> LimitDefinitions,
IReadOnlyCollection<SaasOffering> Offerings,
IReadOnlyCollection<SaasOfferingVersionItem> Versions);
public interface ISaasCatalogAdminService
{
Task<SaasCatalogSnapshot> GetCatalogAsync(SaasCatalogActor actor, CancellationToken cancellationToken = default);
Task<SaasFeature> UpsertFeatureAsync(SaasCatalogActor actor, UpsertSaasFeatureCommand command, CancellationToken cancellationToken = default);
Task<SaasFeatureLimitDefinition> UpsertLimitDefinitionAsync(SaasCatalogActor actor, UpsertSaasFeatureLimitCommand command, CancellationToken cancellationToken = default);
Task<SaasOffering> UpsertOfferingAsync(SaasCatalogActor actor, UpsertSaasOfferingCommand command, CancellationToken cancellationToken = default);
Task<SaasOfferingVersionItem> UpsertDraftVersionAsync(SaasCatalogActor actor, UpsertSaasOfferingVersionCommand command, CancellationToken cancellationToken = default);
Task<SaasOfferingVersionItem> PublishVersionAsync(SaasCatalogActor actor, Guid versionId, CancellationToken cancellationToken = default);
Task<SaasOfferingVersionItem> CloneVersionAsync(SaasCatalogActor actor, Guid versionId, CancellationToken cancellationToken = default);
Task<SaasOfferingVersionItem> RetireVersionAsync(SaasCatalogActor actor, Guid versionId, CancellationToken cancellationToken = default);
}
public sealed record TenantBillingCatalog(
IReadOnlyCollection<SaasFeature> Features,
IReadOnlyCollection<SaasOfferingVersionItem> BasePlans,
IReadOnlyCollection<SaasOfferingVersionItem> AddOns);
public sealed record CreatePlatformBillingQuoteCommand(
Guid BaseOfferingVersionId,
IReadOnlyCollection<Guid> 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<PlatformBillingQuoteItemView> Items,
IReadOnlyCollection<string> FeatureCodes,
IReadOnlyDictionary<string, long> 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<PlatformBillingQuoteItemView> 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<string> FeatureCodes);
public sealed record ChangeTenantSubscriptionCommand(Guid BaseOfferingVersionId, IReadOnlyCollection<Guid> AddOnOfferingVersionIds);
public interface ITenantBillingService
{
Task<TenantBillingCatalog> GetCatalogAsync(TenantBillingActor actor, CancellationToken cancellationToken = default);
Task<PlatformBillingQuoteView> CreateQuoteAsync(TenantBillingActor actor, CreatePlatformBillingQuoteCommand command, CancellationToken cancellationToken = default);
Task<PlatformBillingOrderView> CreateOrderAsync(TenantBillingActor actor, CreatePlatformBillingOrderCommand command, CancellationToken cancellationToken = default);
Task<PlatformBillingPaymentView> CreatePaymentAsync(TenantBillingActor actor, CreatePlatformBillingPaymentCommand command, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<PlatformBillingOrderView>> GetOrdersAsync(TenantBillingActor actor, int limit, CancellationToken cancellationToken = default);
Task<PlatformBillingOrderView> GetOrderAsync(TenantBillingActor actor, string orderNo, CancellationToken cancellationToken = default);
Task<TenantSubscriptionView?> GetSubscriptionAsync(TenantBillingActor actor, CancellationToken cancellationToken = default);
Task<PlatformBillingOrderView> ChangeSubscriptionAsync(TenantBillingActor actor, ChangeTenantSubscriptionCommand command, string idempotencyKey, CancellationToken cancellationToken = default);
Task<PlatformBillingOrderView> RenewSubscriptionAsync(TenantBillingActor actor, string idempotencyKey, CancellationToken cancellationToken = default);
Task<TenantSubscriptionView> CancelSubscriptionAsync(TenantBillingActor actor, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<FeatureQuotaSnapshot>> GetUsageAsync(TenantBillingActor actor, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<PlatformBillingInvoice>> 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<IReadOnlyCollection<PlatformBillingOrder>> GetOrdersAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<PlatformBillingPayment>> GetPaymentsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<PlatformBillingRefund>> GetRefundsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<PlatformBillingInvoice>> GetInvoicesAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<TenantFeatureUsage>> GetUsageAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<PlatformBillingInvoiceReminder>> GetInvoiceRemindersAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<TenantSaasSubscription>> GetSubscriptionsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default);
Task<PlatformBillingPayment> ConfirmManualPaymentAsync(SaasCatalogActor actor, ConfirmManualPaymentCommand command, CancellationToken cancellationToken = default);
Task<TenantFeatureOverride> UpsertFeatureOverrideAsync(SaasCatalogActor actor, UpsertTenantFeatureOverrideCommand command, CancellationToken cancellationToken = default);
}
public interface IPlatformBillingPaymentGateway
{
Task<CreatePaymentProviderResult> CreatePaymentAsync(string provider, CreatePaymentProviderRequest request, CancellationToken cancellationToken = default);
Task<PaymentNotificationResult> ParseNotificationAsync(string provider, PaymentNotificationRequest request, CancellationToken cancellationToken = default);
}
public sealed record PlatformBillingNotification(
string Provider,
IReadOnlyDictionary<string, string> Headers,
string RawBody,
JsonElement Body);
public interface IPlatformBillingNotificationService
{
Task ProcessAsync(PlatformBillingNotification notification, CancellationToken cancellationToken = default);
}
public interface IPlatformBillingSettlementService
{
Task<PlatformBillingPayment> 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<int> ProcessDueAsync(
DateTimeOffset? asOf = null,
CancellationToken cancellationToken = default);
}
public sealed class PlatformBillingException(string message, string code) : Exception(message)
{
public string Code { get; } = code;
}