Browse Source
* Added invoices and transaction history endpoints. Added cursor paging for each * Removed try/catch since it's handled by middleware. Updated condition to use pattern matching * Added unit tests for PaymentHistoryService * Removed organizationId from account billing controller endpointspull/4749/head
16 changed files with 379 additions and 28 deletions
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
using Bit.Core.Billing.Models; |
||||
using Bit.Core.Entities; |
||||
|
||||
namespace Bit.Core.Billing.Services; |
||||
|
||||
public interface IPaymentHistoryService |
||||
{ |
||||
Task<IEnumerable<BillingHistoryInfo.BillingInvoice>> GetInvoiceHistoryAsync( |
||||
ISubscriber subscriber, |
||||
int pageSize = 5, |
||||
string startAfter = null); |
||||
|
||||
Task<IEnumerable<BillingHistoryInfo.BillingTransaction>> GetTransactionHistoryAsync( |
||||
ISubscriber subscriber, |
||||
int pageSize = 5, |
||||
DateTime? startAfter = null); |
||||
} |
||||
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
using Bit.Core.AdminConsole.Entities; |
||||
using Bit.Core.Billing.Models; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Models.BitStripe; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Core.Services; |
||||
using Microsoft.Extensions.Logging; |
||||
|
||||
namespace Bit.Core.Billing.Services.Implementations; |
||||
|
||||
public class PaymentHistoryService( |
||||
IStripeAdapter stripeAdapter, |
||||
ITransactionRepository transactionRepository, |
||||
ILogger<PaymentHistoryService> logger) : IPaymentHistoryService |
||||
{ |
||||
public async Task<IEnumerable<BillingHistoryInfo.BillingInvoice>> GetInvoiceHistoryAsync( |
||||
ISubscriber subscriber, |
||||
int pageSize = 5, |
||||
string startAfter = null) |
||||
{ |
||||
if (subscriber is not { GatewayCustomerId: not null, GatewaySubscriptionId: not null }) |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
var invoices = await stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions |
||||
{ |
||||
Customer = subscriber.GatewayCustomerId, |
||||
Subscription = subscriber.GatewaySubscriptionId, |
||||
Limit = pageSize, |
||||
StartingAfter = startAfter |
||||
}); |
||||
|
||||
return invoices.Select(invoice => new BillingHistoryInfo.BillingInvoice(invoice)); |
||||
|
||||
} |
||||
|
||||
public async Task<IEnumerable<BillingHistoryInfo.BillingTransaction>> GetTransactionHistoryAsync( |
||||
ISubscriber subscriber, |
||||
int pageSize = 5, |
||||
DateTime? startAfter = null) |
||||
{ |
||||
var transactions = subscriber switch |
||||
{ |
||||
User => await transactionRepository.GetManyByUserIdAsync(subscriber.Id, pageSize, startAfter), |
||||
Organization => await transactionRepository.GetManyByOrganizationIdAsync(subscriber.Id, pageSize, startAfter), |
||||
_ => null |
||||
}; |
||||
|
||||
return transactions?.OrderByDescending(i => i.CreationDate) |
||||
.Select(t => new BillingHistoryInfo.BillingTransaction(t)); |
||||
} |
||||
} |
||||
@ -1,16 +1,16 @@
@@ -1,16 +1,16 @@
|
||||
CREATE PROCEDURE [dbo].[Transaction_ReadByOrganizationId] |
||||
@OrganizationId UNIQUEIDENTIFIER, |
||||
@Limit INT |
||||
@Limit INT, |
||||
@StartAfter DATETIME2 = NULL |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
SELECT |
||||
TOP (@Limit) * |
||||
FROM |
||||
[dbo].[TransactionView] |
||||
SELECT TOP (@Limit) * |
||||
FROM [dbo].[TransactionView] |
||||
WHERE |
||||
[OrganizationId] = @OrganizationId |
||||
AND (@StartAfter IS NULL OR [CreationDate] < @StartAfter) |
||||
ORDER BY |
||||
[CreationDate] DESC |
||||
END |
||||
|
||||
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
using Bit.Core.AdminConsole.Entities; |
||||
using Bit.Core.Billing.Services.Implementations; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Models.BitStripe; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Core.Services; |
||||
using Microsoft.Extensions.Logging; |
||||
using NSubstitute; |
||||
using Stripe; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Core.Test.Billing.Services; |
||||
|
||||
public class PaymentHistoryServiceTests |
||||
{ |
||||
[Fact] |
||||
public async Task GetInvoiceHistoryAsync_Succeeds() |
||||
{ |
||||
// Arrange |
||||
var subscriber = new Organization { GatewayCustomerId = "cus_id", GatewaySubscriptionId = "sub_id" }; |
||||
var invoices = new List<Invoice> { new() { Id = "in_id" } }; |
||||
var stripeAdapter = Substitute.For<IStripeAdapter>(); |
||||
stripeAdapter.InvoiceListAsync(Arg.Any<StripeInvoiceListOptions>()).Returns(invoices); |
||||
var transactionRepository = Substitute.For<ITransactionRepository>(); |
||||
var logger = Substitute.For<ILogger<PaymentHistoryService>>(); |
||||
var paymentHistoryService = new PaymentHistoryService(stripeAdapter, transactionRepository, logger); |
||||
|
||||
// Act |
||||
var result = await paymentHistoryService.GetInvoiceHistoryAsync(subscriber); |
||||
|
||||
// Assert |
||||
Assert.NotNull(result); |
||||
Assert.Single(result); |
||||
await stripeAdapter.Received(1).InvoiceListAsync(Arg.Any<StripeInvoiceListOptions>()); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task GetInvoiceHistoryAsync_SubscriberNull_ReturnsNull() |
||||
{ |
||||
// Arrange |
||||
var paymentHistoryService = new PaymentHistoryService( |
||||
Substitute.For<IStripeAdapter>(), |
||||
Substitute.For<ITransactionRepository>(), |
||||
Substitute.For<ILogger<PaymentHistoryService>>()); |
||||
|
||||
// Act |
||||
var result = await paymentHistoryService.GetInvoiceHistoryAsync(null); |
||||
|
||||
// Assert |
||||
Assert.Null(result); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task GetTransactionHistoryAsync_Succeeds() |
||||
{ |
||||
// Arrange |
||||
var subscriber = new Organization { Id = Guid.NewGuid() }; |
||||
var transactions = new List<Transaction> { new() { Id = Guid.NewGuid() } }; |
||||
var transactionRepository = Substitute.For<ITransactionRepository>(); |
||||
transactionRepository.GetManyByOrganizationIdAsync(subscriber.Id, Arg.Any<int>(), Arg.Any<DateTime?>()).Returns(transactions); |
||||
var stripeAdapter = Substitute.For<IStripeAdapter>(); |
||||
var logger = Substitute.For<ILogger<PaymentHistoryService>>(); |
||||
var paymentHistoryService = new PaymentHistoryService(stripeAdapter, transactionRepository, logger); |
||||
|
||||
// Act |
||||
var result = await paymentHistoryService.GetTransactionHistoryAsync(subscriber); |
||||
|
||||
// Assert |
||||
Assert.NotNull(result); |
||||
Assert.Single(result); |
||||
await transactionRepository.Received(1).GetManyByOrganizationIdAsync(subscriber.Id, Arg.Any<int>(), Arg.Any<DateTime?>()); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task GetTransactionHistoryAsync_SubscriberNull_ReturnsNull() |
||||
{ |
||||
// Arrange |
||||
var paymentHistoryService = new PaymentHistoryService( |
||||
Substitute.For<IStripeAdapter>(), |
||||
Substitute.For<ITransactionRepository>(), |
||||
Substitute.For<ILogger<PaymentHistoryService>>()); |
||||
|
||||
// Act |
||||
var result = await paymentHistoryService.GetTransactionHistoryAsync(null); |
||||
|
||||
// Assert |
||||
Assert.Null(result); |
||||
} |
||||
} |
||||
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Transaction_ReadByOrganizationId] |
||||
@OrganizationId UNIQUEIDENTIFIER, |
||||
@Limit INT, |
||||
@StartAfter DATETIME2 = NULL |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
SELECT |
||||
TOP (@Limit) * |
||||
FROM |
||||
[dbo].[TransactionView] |
||||
WHERE |
||||
[OrganizationId] = @OrganizationId |
||||
AND (@StartAfter IS NULL OR [CreationDate] < @StartAfter) |
||||
ORDER BY |
||||
[CreationDate] DESC |
||||
END |
||||
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Transaction_ReadByProviderId] |
||||
@ProviderId UNIQUEIDENTIFIER, |
||||
@Limit INT, |
||||
@StartAfter DATETIME2 = NULL |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
SELECT |
||||
TOP (@Limit) * |
||||
FROM |
||||
[dbo].[TransactionView] |
||||
WHERE |
||||
[ProviderId] = @ProviderId |
||||
AND (@StartAfter IS NULL OR [CreationDate] < @StartAfter) |
||||
ORDER BY |
||||
[CreationDate] DESC |
||||
END |
||||
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Transaction_ReadByUserId] |
||||
@UserId UNIQUEIDENTIFIER, |
||||
@Limit INT, |
||||
@StartAfter DATETIME2 = NULL |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
SELECT |
||||
TOP (@Limit) * |
||||
FROM |
||||
[dbo].[TransactionView] |
||||
WHERE |
||||
[UserId] = @UserId |
||||
AND (@StartAfter IS NULL OR [CreationDate] < @StartAfter) |
||||
ORDER BY |
||||
[CreationDate] DESC |
||||
END |
||||
Loading…
Reference in new issue