diff --git a/src/Api/Billing/Controllers/OrganizationBillingController.cs b/src/Api/Billing/Controllers/OrganizationBillingController.cs index 7da0a0f602..1c0cfd9388 100644 --- a/src/Api/Billing/Controllers/OrganizationBillingController.cs +++ b/src/Api/Billing/Controllers/OrganizationBillingController.cs @@ -1,7 +1,9 @@ #nullable enable +using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.Billing.Models.Requests; using Bit.Api.Billing.Models.Responses; using Bit.Core; +using Bit.Core.Billing.Models.Sales; using Bit.Core.Billing.Services; using Bit.Core.Context; using Bit.Core.Repositories; @@ -21,7 +23,8 @@ public class OrganizationBillingController( IOrganizationRepository organizationRepository, IPaymentService paymentService, ISubscriberService subscriberService, - IPaymentHistoryService paymentHistoryService) : BaseBillingController + IPaymentHistoryService paymentHistoryService, + IUserService userService) : BaseBillingController { [HttpGet("metadata")] public async Task GetMetadataAsync([FromRoute] Guid organizationId) @@ -278,4 +281,37 @@ public class OrganizationBillingController( return TypedResults.Ok(); } + + [HttpPost("restart-subscription")] + public async Task RestartSubscriptionAsync([FromRoute] Guid organizationId, + [FromBody] OrganizationCreateRequestModel model) + { + var user = await userService.GetUserByPrincipalAsync(User); + if (user == null) + { + throw new UnauthorizedAccessException(); + } + + if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI)) + { + return Error.NotFound(); + } + + if (!await currentContext.EditPaymentMethods(organizationId)) + { + return Error.Unauthorized(); + } + + var organization = await organizationRepository.GetByIdAsync(organizationId); + + if (organization == null) + { + return Error.NotFound(); + } + var organizationSignup = model.ToOrganizationSignup(user); + var sale = OrganizationSale.From(organization, organizationSignup); + await organizationBillingService.Finalize(sale); + + return TypedResults.Ok(); + } } diff --git a/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs b/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs index 28f156fa39..1dfc79be21 100644 --- a/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs +++ b/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs @@ -9,6 +9,7 @@ public record OrganizationMetadataResponse( bool IsSubscriptionUnpaid, bool HasSubscription, bool HasOpenInvoice, + bool IsSubscriptionCanceled, DateTime? InvoiceDueDate, DateTime? InvoiceCreatedDate, DateTime? SubPeriodEndDate) @@ -21,6 +22,7 @@ public record OrganizationMetadataResponse( metadata.IsSubscriptionUnpaid, metadata.HasSubscription, metadata.HasOpenInvoice, + metadata.IsSubscriptionCanceled, metadata.InvoiceDueDate, metadata.InvoiceCreatedDate, metadata.SubPeriodEndDate); diff --git a/src/Core/Billing/Models/OrganizationMetadata.cs b/src/Core/Billing/Models/OrganizationMetadata.cs index b6442e4c19..4bb9a85825 100644 --- a/src/Core/Billing/Models/OrganizationMetadata.cs +++ b/src/Core/Billing/Models/OrganizationMetadata.cs @@ -7,6 +7,7 @@ public record OrganizationMetadata( bool IsSubscriptionUnpaid, bool HasSubscription, bool HasOpenInvoice, + bool IsSubscriptionCanceled, DateTime? InvoiceDueDate, DateTime? InvoiceCreatedDate, DateTime? SubPeriodEndDate); diff --git a/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs b/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs index 8114d5ba65..ec9770c59e 100644 --- a/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs +++ b/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs @@ -69,7 +69,7 @@ public class OrganizationBillingService( if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) { return new OrganizationMetadata(isEligibleForSelfHost, isManaged, false, - false, false, false, null, null, null); + false, false, false, false, null, null, null); } var customer = await subscriberService.GetCustomer(organization, @@ -79,6 +79,7 @@ public class OrganizationBillingService( var isOnSecretsManagerStandalone = IsOnSecretsManagerStandalone(organization, customer, subscription); var isSubscriptionUnpaid = IsSubscriptionUnpaid(subscription); + var isSubscriptionCanceled = IsSubscriptionCanceled(subscription); var hasSubscription = true; var openInvoice = await HasOpenInvoiceAsync(subscription); var hasOpenInvoice = openInvoice.HasOpenInvoice; @@ -87,7 +88,7 @@ public class OrganizationBillingService( var subPeriodEndDate = subscription?.CurrentPeriodEnd; return new OrganizationMetadata(isEligibleForSelfHost, isManaged, isOnSecretsManagerStandalone, - isSubscriptionUnpaid, hasSubscription, hasOpenInvoice, invoiceDueDate, invoiceCreatedDate, subPeriodEndDate); + isSubscriptionUnpaid, hasSubscription, hasOpenInvoice, isSubscriptionCanceled, invoiceDueDate, invoiceCreatedDate, subPeriodEndDate); } public async Task UpdatePaymentMethod( @@ -437,5 +438,15 @@ public class OrganizationBillingService( ? (true, invoice.Created, invoice.DueDate) : (false, null, null); } + + private static bool IsSubscriptionCanceled(Subscription subscription) + { + if (subscription == null) + { + return false; + } + + return subscription.Status == "canceled"; + } #endregion } diff --git a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs index d500fb354a..a8c3cf15a9 100644 --- a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs @@ -52,7 +52,7 @@ public class OrganizationBillingControllerTests { sutProvider.GetDependency().OrganizationUser(organizationId).Returns(true); sutProvider.GetDependency().GetMetadata(organizationId) - .Returns(new OrganizationMetadata(true, true, true, true, true, true, null, null, null)); + .Returns(new OrganizationMetadata(true, true, true, true, true, true, true, null, null, null)); var result = await sutProvider.Sut.GetMetadataAsync(organizationId);