1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05:00

[AC-2567] Billing Performance Improvements (#4143)

* Moved AccountsBilling controller to be owned by Billing

* Added org billing history endpoint

* Updated GetBillingInvoicesAsync to only retrieve paid, open, and uncollectible invoices, and added option to limit results

* Removed invoices and transactions from GetBillingAsync

* Limiting the number of invoices and transactions returned

* Moved Billing models to Billing namespace

* Split billing info and billing history objects

* Removed billing method GetBillingBalanceAndSourceAsync

* Removed unused using

* Cleaned up BillingInfo a bit

* Update migration scripts to use `CREATE OR ALTER` instead of checking for the `OBJECT_ID`

* Applying limit to aggregated invoices after they return from Stripe
This commit is contained in:
Conner Turnbull
2024-06-11 13:55:23 -04:00
committed by GitHub
parent f615858724
commit fc1c488a78
30 changed files with 474 additions and 341 deletions

View File

@ -1,51 +1,42 @@
using Bit.Api.Models.Response;
using Bit.Api.Billing.Models.Responses;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
namespace Bit.Api.Billing.Controllers;
[Route("accounts/billing")]
[Authorize("Application")]
public class AccountsBillingController : Controller
public class AccountsBillingController(
IPaymentService paymentService,
IUserService userService) : Controller
{
private readonly IPaymentService _paymentService;
private readonly IUserService _userService;
public AccountsBillingController(
IPaymentService paymentService,
IUserService userService)
{
_paymentService = paymentService;
_userService = userService;
}
[HttpGet("history")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<BillingHistoryResponseModel> GetBillingHistory()
public async Task<BillingHistoryResponseModel> GetBillingHistoryAsync()
{
var user = await _userService.GetUserByPrincipalAsync(User);
var user = await userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var billingInfo = await _paymentService.GetBillingHistoryAsync(user);
var billingInfo = await paymentService.GetBillingHistoryAsync(user);
return new BillingHistoryResponseModel(billingInfo);
}
[HttpGet("payment-method")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<BillingPaymentResponseModel> GetPaymentMethod()
public async Task<BillingPaymentResponseModel> GetPaymentMethodAsync()
{
var user = await _userService.GetUserByPrincipalAsync(User);
var user = await userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var billingInfo = await _paymentService.GetBillingBalanceAndSourceAsync(user);
var billingInfo = await paymentService.GetBillingAsync(user);
return new BillingPaymentResponseModel(billingInfo);
}
}

View File

@ -1,5 +1,4 @@
using Bit.Api.Billing.Models.Responses;
using Bit.Api.Models.Response;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
using Bit.Core.Repositories;
@ -33,6 +32,21 @@ public class OrganizationBillingController(
return TypedResults.Ok(response);
}
[HttpGet("history")]
public async Task<IResult> GetHistoryAsync([FromRoute] Guid organizationId)
{
var organization = await organizationRepository.GetByIdAsync(organizationId);
if (organization == null)
{
return TypedResults.NotFound();
}
var billingInfo = await paymentService.GetBillingHistoryAsync(organization);
return TypedResults.Ok(billingInfo);
}
[HttpGet]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<IResult> GetBillingAsync(Guid organizationId)

View File

@ -45,7 +45,7 @@ public class OrganizationsController(
ISubscriberService subscriberService)
: Controller
{
[HttpGet("{id}/billing-status")]
[HttpGet("{id:guid}/billing-status")]
public async Task<OrganizationBillingStatusResponseModel> GetBillingStatus(Guid id)
{
if (!await currentContext.EditPaymentMethods(id))

View File

@ -1,45 +1,24 @@
using Bit.Core.Enums;
using Bit.Core.Billing.Models;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Models.Business;
namespace Bit.Api.Models.Response;
namespace Bit.Api.Billing.Models.Responses;
public class BillingResponseModel : ResponseModel
public class BillingHistoryResponseModel : ResponseModel
{
public BillingResponseModel(BillingInfo billing)
: base("billing")
public BillingHistoryResponseModel(BillingHistoryInfo billing)
: base("billingHistory")
{
Balance = billing.Balance;
PaymentSource = billing.PaymentSource != null ? new BillingSource(billing.PaymentSource) : null;
Transactions = billing.Transactions?.Select(t => new BillingTransaction(t));
Invoices = billing.Invoices?.Select(i => new BillingInvoice(i));
}
public decimal Balance { get; set; }
public BillingSource PaymentSource { get; set; }
public IEnumerable<BillingInvoice> Invoices { get; set; }
public IEnumerable<BillingTransaction> Transactions { get; set; }
}
public class BillingSource
{
public BillingSource(BillingInfo.BillingSource source)
{
Type = source.Type;
CardBrand = source.CardBrand;
Description = source.Description;
NeedsVerification = source.NeedsVerification;
}
public PaymentMethodType Type { get; set; }
public string CardBrand { get; set; }
public string Description { get; set; }
public bool NeedsVerification { get; set; }
}
public class BillingInvoice
{
public BillingInvoice(BillingInfo.BillingInvoice inv)
public BillingInvoice(BillingHistoryInfo.BillingInvoice inv)
{
Amount = inv.Amount;
Date = inv.Date;
@ -59,7 +38,7 @@ public class BillingInvoice
public class BillingTransaction
{
public BillingTransaction(BillingInfo.BillingTransaction transaction)
public BillingTransaction(BillingHistoryInfo.BillingTransaction transaction)
{
CreatedDate = transaction.CreatedDate;
Amount = transaction.Amount;

View File

@ -1,7 +1,7 @@
using Bit.Core.Models.Api;
using Bit.Core.Models.Business;
using Bit.Core.Billing.Models;
using Bit.Core.Models.Api;
namespace Bit.Api.Models.Response;
namespace Bit.Api.Billing.Models.Responses;
public class BillingPaymentResponseModel : ResponseModel
{

View File

@ -0,0 +1,34 @@
using Bit.Core.Billing.Models;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
namespace Bit.Api.Billing.Models.Responses;
public class BillingResponseModel : ResponseModel
{
public BillingResponseModel(BillingInfo billing)
: base("billing")
{
Balance = billing.Balance;
PaymentSource = billing.PaymentSource != null ? new BillingSource(billing.PaymentSource) : null;
}
public decimal Balance { get; set; }
public BillingSource PaymentSource { get; set; }
}
public class BillingSource
{
public BillingSource(BillingInfo.BillingSource source)
{
Type = source.Type;
CardBrand = source.CardBrand;
Description = source.Description;
NeedsVerification = source.NeedsVerification;
}
public PaymentMethodType Type { get; set; }
public string CardBrand { get; set; }
public string Description { get; set; }
public bool NeedsVerification { get; set; }
}

View File

@ -1,16 +0,0 @@
using Bit.Core.Models.Api;
using Bit.Core.Models.Business;
namespace Bit.Api.Models.Response;
public class BillingHistoryResponseModel : ResponseModel
{
public BillingHistoryResponseModel(BillingInfo billing)
: base("billingHistory")
{
Transactions = billing.Transactions?.Select(t => new BillingTransaction(t));
Invoices = billing.Invoices?.Select(i => new BillingInvoice(i));
}
public IEnumerable<BillingInvoice> Invoices { get; set; }
public IEnumerable<BillingTransaction> Transactions { get; set; }
}