using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Tiku.Api.Contracts; using Tiku.Api.OpenApi; using Tiku.Application.PlatformAdmin; using Tiku.Application.PlatformBilling; using Tiku.Application.Security; using Tiku.Domain.Platform; namespace Tiku.Api.Controllers; [ApiController] [Tags("平台端-SaaS 套餐")] [Route("api/platform/saas")] [Authorize(Policy = TikuPolicies.PlatformBackofficeBootstrap)] public sealed class PlatformSaasController( ISaasCatalogAdminService catalogService, IPlatformBillingAdminService billingService, IPlatformApprovalService approvalService, ICurrentUser currentUser) : ControllerBase { [HttpGet("catalog")] [EndpointSummary("查询平台 SaaS 商品目录")] [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] public Task Catalog(CancellationToken cancellationToken) { return catalogService.GetCatalogAsync(Actor(), cancellationToken); } [HttpPut("features")] [EndpointSummary("新增或更新 SaaS 功能")] [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] public Task UpsertFeature(UpsertSaasFeatureDto request, CancellationToken cancellationToken) { return catalogService.UpsertFeatureAsync(Actor(), request.ToCommand(), cancellationToken); } [HttpPut("feature-limits")] [EndpointSummary("新增或更新 SaaS 功能限额")] [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] public Task UpsertFeatureLimit( UpsertSaasFeatureLimitDto request, CancellationToken cancellationToken) { return catalogService.UpsertLimitDefinitionAsync(Actor(), request.ToCommand(), cancellationToken); } [HttpPut("offerings")] [EndpointSummary("新增或更新 SaaS 套餐")] [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] public Task UpsertOffering(UpsertSaasOfferingDto request, CancellationToken cancellationToken) { return catalogService.UpsertOfferingAsync(Actor(), request.ToCommand(), cancellationToken); } [HttpPut("offering-versions")] [EndpointSummary("新增或更新 SaaS 套餐版本草稿")] [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] public Task UpsertVersion(UpsertSaasOfferingVersionDto request, CancellationToken cancellationToken) { return catalogService.UpsertDraftVersionAsync(Actor(), request.ToCommand(), cancellationToken); } [HttpPost("offering-versions/{versionId:guid}/publish")] [EndpointSummary("发布 SaaS 套餐版本")] [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] public Task PublishVersion(Guid versionId, CancellationToken cancellationToken) { return catalogService.PublishVersionAsync(Actor(), versionId, cancellationToken); } [HttpPost("offering-versions/{versionId:guid}/clone")] [EndpointSummary("克隆 SaaS 套餐版本")] [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] public Task CloneVersion(Guid versionId, CancellationToken cancellationToken) { return catalogService.CloneVersionAsync(Actor(), versionId, cancellationToken); } [HttpPost("offering-versions/{versionId:guid}/retire")] [EndpointSummary("下架 SaaS 套餐版本")] [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] public Task RetireVersion(Guid versionId, CancellationToken cancellationToken) { return catalogService.RetireVersionAsync(Actor(), versionId, cancellationToken); } [HttpGet("orders")] [EndpointSummary("查询平台 SaaS 订单")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task> Orders(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) { return billingService.GetOrdersAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); } [HttpGet("payments")] [EndpointSummary("查询平台 SaaS 支付记录")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task> Payments(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) { return billingService.GetPaymentsAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); } [HttpGet("refunds")] [EndpointSummary("查询平台 SaaS 退款记录")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task> Refunds(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) { return billingService.GetRefundsAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); } [HttpGet("invoices")] [EndpointSummary("查询平台 SaaS 发票")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task> Invoices(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) { return billingService.GetInvoicesAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); } [HttpGet("usage")] [EndpointSummary("查询租户 SaaS 用量")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task> Usage(Guid? tenantId, int limit = 100, CancellationToken cancellationToken = default) { return billingService.GetUsageAsync(Actor(), new PlatformBillingAdminQuery(tenantId, null, limit), cancellationToken); } [HttpGet("invoices/reminders")] [EndpointSummary("查询平台 SaaS 账单提醒")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task> InvoiceReminders(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) { return billingService.GetInvoiceRemindersAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); } [HttpGet("subscriptions")] [EndpointSummary("查询租户 SaaS 订阅")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task> Subscriptions(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) { return billingService.GetSubscriptionsAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); } [HttpPost("payments/manual/confirm")] [EndpointSummary("确认平台手工支付")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] [PlatformOperationRisk("high", PlatformApprovalPolicyCodes.FinancialAdjustment)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status202Accepted)] public async Task> ConfirmManualPayment( ConfirmManualPlatformPaymentDto request, [FromHeader(Name = "Idempotency-Key")] [Required] string idempotencyKey, CancellationToken cancellationToken) { return CommandResult(await approvalService.ConfirmManualPaymentAsync(Actor(), request.ToCommand(), idempotencyKey, cancellationToken)); } [HttpPut("tenant-feature-overrides")] [EndpointSummary("新增或更新租户功能覆盖规则")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task UpsertFeatureOverride(UpsertTenantFeatureOverrideDto request, CancellationToken cancellationToken) { return billingService.UpsertFeatureOverrideAsync(Actor(), request.ToCommand(), cancellationToken); } [HttpGet("metrics")] [EndpointSummary("查询 SaaS 商业经营指标")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task Metrics(CancellationToken cancellationToken) { return billingService.GetCommercialMetricsAsync(Actor(), cancellationToken); } [HttpPost("subscriptions/trial")] [EndpointSummary("为已有租户补录试用订阅")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task GrantTrial(GrantTenantTrialDto request, CancellationToken cancellationToken) { return billingService.GrantTrialAsync(Actor(), request.ToCommand(), cancellationToken); } [HttpPost("subscriptions/{subscriptionId:guid}/suspend")] [EndpointSummary("暂停租户 SaaS 订阅")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task SuspendSubscription(Guid subscriptionId, ChangePlatformSubscriptionDto request, CancellationToken cancellationToken) { return billingService.SuspendSubscriptionAsync(Actor(), request.ToCommand(subscriptionId), cancellationToken); } [HttpPost("subscriptions/{subscriptionId:guid}/resume")] [EndpointSummary("恢复租户 SaaS 订阅")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task ResumeSubscription(Guid subscriptionId, ChangePlatformSubscriptionDto request, CancellationToken cancellationToken) { return billingService.ResumeSubscriptionAsync(Actor(), request.ToCommand(subscriptionId), cancellationToken); } [HttpPost("subscriptions/{subscriptionId:guid}/cancel")] [EndpointSummary("立即取消租户 SaaS 订阅")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task CancelSubscription(Guid subscriptionId, ChangePlatformSubscriptionDto request, CancellationToken cancellationToken) { return billingService.CancelSubscriptionAsync(Actor(), request.ToCommand(subscriptionId), cancellationToken); } [HttpPost("subscriptions/{subscriptionId:guid}/extend")] [EndpointSummary("延长租户 SaaS 订阅账期")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task ExtendSubscription(Guid subscriptionId, ChangePlatformSubscriptionDto request, CancellationToken cancellationToken) { return billingService.ExtendSubscriptionAsync(Actor(), request.ToCommand(subscriptionId), cancellationToken); } [HttpPost("refunds")] [EndpointSummary("申请平台 SaaS 退款")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] [PlatformOperationRisk("high", PlatformApprovalPolicyCodes.FinancialAdjustment)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status202Accepted)] public async Task> RequestRefund(RequestPlatformRefundDto request, CancellationToken cancellationToken) { return CommandResult(await approvalService.SubmitRefundAsync(Actor(), request.ToCommand(), cancellationToken)); } [HttpPost("refunds/{refundId:guid}/approve")] [EndpointSummary("批准平台 SaaS 退款")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task ApproveRefund(Guid refundId, ReviewPlatformRefundDto request, CancellationToken cancellationToken) { return billingService.ApproveRefundAsync(Actor(), request.ToCommand(refundId), cancellationToken); } [HttpPost("refunds/{refundId:guid}/reject")] [EndpointSummary("拒绝平台 SaaS 退款")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task RejectRefund(Guid refundId, ReviewPlatformRefundDto request, CancellationToken cancellationToken) { return billingService.RejectRefundAsync(Actor(), request.ToCommand(refundId), cancellationToken); } [HttpPost("refunds/{refundId:guid}/retry")] [EndpointSummary("重试失败的平台 SaaS 退款")] [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] public Task RetryRefund(Guid refundId, ReviewPlatformRefundDto request, CancellationToken cancellationToken) { return billingService.RetryRefundAsync(Actor(), request.ToCommand(refundId), cancellationToken); } private SaasCatalogActor Actor() { return currentUser.UserId is { } userId ? new SaasCatalogActor(userId) : throw new PlatformBillingException("Platform actor was not resolved.", "platform_access_denied"); } private ActionResult CommandResult(PlatformCommandSubmission result) { return result.ExecutionStatus == "pending_approval" ? Accepted(result) : Ok(result); } }