using Bit.Api.Billing.Models.Requests; using Bit.Api.Billing.Models.Responses; using Bit.Core; using Bit.Core.Billing.Services; using Bit.Core.Context; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Bit.Api.Billing.Controllers; [Route("organizations/{organizationId:guid}/billing")] [Authorize("Application")] public class OrganizationBillingController( ICurrentContext currentContext, IFeatureService featureService, IOrganizationBillingService organizationBillingService, IOrganizationRepository organizationRepository, IPaymentService paymentService, ISubscriberService subscriberService) : BaseBillingController { [HttpGet("metadata")] public async Task GetMetadataAsync([FromRoute] Guid organizationId) { if (!await currentContext.AccessMembersTab(organizationId)) { return Error.Unauthorized(); } var metadata = await organizationBillingService.GetMetadata(organizationId); if (metadata == null) { return Error.NotFound(); } var response = OrganizationMetadataResponse.From(metadata); return TypedResults.Ok(response); } [HttpGet("history")] public async Task GetHistoryAsync([FromRoute] Guid organizationId) { if (!await currentContext.ViewBillingHistory(organizationId)) { return Error.Unauthorized(); } var organization = await organizationRepository.GetByIdAsync(organizationId); if (organization == null) { return Error.NotFound(); } var billingInfo = await paymentService.GetBillingHistoryAsync(organization); return TypedResults.Ok(billingInfo); } [HttpGet] [SelfHosted(NotSelfHostedOnly = true)] public async Task GetBillingAsync(Guid organizationId) { if (!await currentContext.ViewBillingHistory(organizationId)) { return Error.Unauthorized(); } var organization = await organizationRepository.GetByIdAsync(organizationId); if (organization == null) { return Error.NotFound(); } var billingInfo = await paymentService.GetBillingAsync(organization); var response = new BillingResponseModel(billingInfo); return TypedResults.Ok(response); } [HttpGet("payment-method")] public async Task GetPaymentMethodAsync([FromRoute] Guid organizationId) { 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 paymentMethod = await subscriberService.GetPaymentMethod(organization); var response = PaymentMethodResponse.From(paymentMethod); return TypedResults.Ok(response); } [HttpPut("payment-method")] public async Task UpdatePaymentMethodAsync( [FromRoute] Guid organizationId, [FromBody] UpdatePaymentMethodRequestBody requestBody) { 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 tokenizedPaymentSource = requestBody.PaymentSource.ToDomain(); await subscriberService.UpdatePaymentSource(organization, tokenizedPaymentSource); var taxInformation = requestBody.TaxInformation.ToDomain(); await subscriberService.UpdateTaxInformation(organization, taxInformation); return TypedResults.Ok(); } [HttpPost("payment-method/verify-bank-account")] public async Task VerifyBankAccountAsync( [FromRoute] Guid organizationId, [FromBody] VerifyBankAccountRequestBody requestBody) { 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(); } await subscriberService.VerifyBankAccount(organization, (requestBody.Amount1, requestBody.Amount2)); return TypedResults.Ok(); } [HttpGet("tax-information")] public async Task GetTaxInformationAsync([FromRoute] Guid organizationId) { 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 taxInformation = await subscriberService.GetTaxInformation(organization); var response = TaxInformationResponse.From(taxInformation); return TypedResults.Ok(response); } [HttpPut("tax-information")] public async Task UpdateTaxInformationAsync( [FromRoute] Guid organizationId, [FromBody] TaxInformationRequestBody requestBody) { 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 taxInformation = requestBody.ToDomain(); await subscriberService.UpdateTaxInformation(organization, taxInformation); return TypedResults.Ok(); } }