1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 08:02:49 -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,6 +1,7 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Models;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@ -1555,20 +1556,6 @@ public class StripePaymentService : IPaymentService
}
public async Task<BillingInfo> GetBillingAsync(ISubscriber subscriber)
{
var customer = await GetCustomerAsync(subscriber.GatewayCustomerId, GetCustomerPaymentOptions());
var billingInfo = new BillingInfo
{
Balance = GetBillingBalance(customer),
PaymentSource = await GetBillingPaymentSourceAsync(customer),
Invoices = await GetBillingInvoicesAsync(customer),
Transactions = await GetBillingTransactionsAsync(subscriber)
};
return billingInfo;
}
public async Task<BillingInfo> GetBillingBalanceAndSourceAsync(ISubscriber subscriber)
{
var customer = await GetCustomerAsync(subscriber.GatewayCustomerId, GetCustomerPaymentOptions());
var billingInfo = new BillingInfo
@ -1580,13 +1567,13 @@ public class StripePaymentService : IPaymentService
return billingInfo;
}
public async Task<BillingInfo> GetBillingHistoryAsync(ISubscriber subscriber)
public async Task<BillingHistoryInfo> GetBillingHistoryAsync(ISubscriber subscriber)
{
var customer = await GetCustomerAsync(subscriber.GatewayCustomerId);
var billingInfo = new BillingInfo
var billingInfo = new BillingHistoryInfo
{
Transactions = await GetBillingTransactionsAsync(subscriber),
Invoices = await GetBillingInvoicesAsync(customer)
Transactions = await GetBillingTransactionsAsync(subscriber, 20),
Invoices = await GetBillingInvoicesAsync(customer, 20)
};
return billingInfo;
@ -1936,44 +1923,66 @@ public class StripePaymentService : IPaymentService
return customer;
}
private async Task<IEnumerable<BillingInfo.BillingTransaction>> GetBillingTransactionsAsync(ISubscriber subscriber)
private async Task<IEnumerable<BillingHistoryInfo.BillingTransaction>> GetBillingTransactionsAsync(ISubscriber subscriber, int? limit = null)
{
ICollection<Transaction> transactions = null;
if (subscriber is User)
var transactions = subscriber switch
{
transactions = await _transactionRepository.GetManyByUserIdAsync(subscriber.Id);
}
else if (subscriber is Organization)
{
transactions = await _transactionRepository.GetManyByOrganizationIdAsync(subscriber.Id);
}
User => await _transactionRepository.GetManyByUserIdAsync(subscriber.Id, limit),
Organization => await _transactionRepository.GetManyByOrganizationIdAsync(subscriber.Id, limit),
_ => null
};
return transactions?.OrderByDescending(i => i.CreationDate)
.Select(t => new BillingInfo.BillingTransaction(t));
.Select(t => new BillingHistoryInfo.BillingTransaction(t));
}
private async Task<IEnumerable<BillingInfo.BillingInvoice>> GetBillingInvoicesAsync(Customer customer)
private async Task<IEnumerable<BillingHistoryInfo.BillingInvoice>> GetBillingInvoicesAsync(Customer customer,
int? limit = null)
{
if (customer == null)
{
return null;
}
var options = new StripeInvoiceListOptions
{
Customer = customer.Id,
SelectAll = true
};
try
{
var invoices = await _stripeAdapter.InvoiceListAsync(options);
var paidInvoicesTask = _stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions
{
Customer = customer.Id,
SelectAll = !limit.HasValue,
Limit = limit,
Status = "paid"
});
var openInvoicesTask = _stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions
{
Customer = customer.Id,
SelectAll = !limit.HasValue,
Limit = limit,
Status = "open"
});
var uncollectibleInvoicesTask = _stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions
{
Customer = customer.Id,
SelectAll = !limit.HasValue,
Limit = limit,
Status = "uncollectible"
});
return invoices
.Where(invoice => invoice.Status != "void" && invoice.Status != "draft")
var paidInvoices = await paidInvoicesTask;
var openInvoices = await openInvoicesTask;
var uncollectibleInvoices = await uncollectibleInvoicesTask;
var invoices = paidInvoices
.Concat(openInvoices)
.Concat(uncollectibleInvoices);
var result = invoices
.OrderByDescending(invoice => invoice.Created)
.Select(invoice => new BillingInfo.BillingInvoice(invoice));
.Select(invoice => new BillingHistoryInfo.BillingInvoice(invoice));
return limit.HasValue
? result.Take(limit.Value)
: result;
}
catch (StripeException exception)
{