mirror of
https://github.com/bitwarden/server.git
synced 2025-04-10 23:58:13 -05:00

* Refactor: Rename some methods and models for consistency This commit contains no logic changes at all. It's entirely comprised of renames of existing models and methods to bring our codebase more in line with our app's functionality and terminology. * Add feature flag: AC-2476-deprecate-stripe-sources-api * Standardize error responses from applicable billing controllers During my work on CB, I found that just using the built-in TypedResults errors results in the client choking on the response because it's looking for the ErrroResponseModel. The new BaseBillingController provides Error utilities to return TypedResults wrapping that model so the client can process it. * Add feature flagged payment method endoints to OrganizationBillingController * Run dotnet format
230 lines
6.6 KiB
C#
230 lines
6.6 KiB
C#
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<IResult> 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<IResult> 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<IResult> 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<IResult> 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<IResult> 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<IResult> 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<IResult> 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<IResult> 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();
|
|
}
|
|
}
|