mirror of
https://github.com/bitwarden/server.git
synced 2025-05-21 03:24:31 -05:00
[AC-1942] Add endpoint to get provider invoices (#4158)
* Added endpoint to get provider invoices * Added missing properties of invoice * Run dotnet format'
This commit is contained in:
parent
4a6113dc86
commit
a0a7654077
@ -25,6 +25,23 @@ public class ProviderBillingController(
|
|||||||
IStripeAdapter stripeAdapter,
|
IStripeAdapter stripeAdapter,
|
||||||
ISubscriberService subscriberService) : Controller
|
ISubscriberService subscriberService) : Controller
|
||||||
{
|
{
|
||||||
|
[HttpGet("invoices")]
|
||||||
|
public async Task<IResult> GetInvoicesAsync([FromRoute] Guid providerId)
|
||||||
|
{
|
||||||
|
var (provider, result) = await GetAuthorizedBillableProviderOrResultAsync(providerId);
|
||||||
|
|
||||||
|
if (provider == null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var invoices = await subscriberService.GetInvoices(provider);
|
||||||
|
|
||||||
|
var response = InvoicesResponse.From(invoices);
|
||||||
|
|
||||||
|
return TypedResults.Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("payment-information")]
|
[HttpGet("payment-information")]
|
||||||
public async Task<IResult> GetPaymentInformationAsync([FromRoute] Guid providerId)
|
public async Task<IResult> GetPaymentInformationAsync([FromRoute] Guid providerId)
|
||||||
{
|
{
|
||||||
|
30
src/Api/Billing/Models/Responses/InvoicesResponse.cs
Normal file
30
src/Api/Billing/Models/Responses/InvoicesResponse.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using Stripe;
|
||||||
|
|
||||||
|
namespace Bit.Api.Billing.Models.Responses;
|
||||||
|
|
||||||
|
public record InvoicesResponse(
|
||||||
|
List<InvoiceDTO> Invoices)
|
||||||
|
{
|
||||||
|
public static InvoicesResponse From(IEnumerable<Invoice> invoices) => new(
|
||||||
|
invoices
|
||||||
|
.Where(i => i.Status is "open" or "paid" or "uncollectible")
|
||||||
|
.OrderByDescending(i => i.Created)
|
||||||
|
.Select(InvoiceDTO.From).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public record InvoiceDTO(
|
||||||
|
DateTime Date,
|
||||||
|
string Number,
|
||||||
|
decimal Total,
|
||||||
|
string Status,
|
||||||
|
string Url,
|
||||||
|
string PdfUrl)
|
||||||
|
{
|
||||||
|
public static InvoiceDTO From(Invoice invoice) => new(
|
||||||
|
invoice.Created,
|
||||||
|
invoice.Number,
|
||||||
|
invoice.Total / 100M,
|
||||||
|
invoice.Status,
|
||||||
|
invoice.HostedInvoiceUrl,
|
||||||
|
invoice.InvoicePdf);
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.BitStripe;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
|
|
||||||
namespace Bit.Core.Billing.Services;
|
namespace Bit.Core.Billing.Services;
|
||||||
@ -46,6 +47,18 @@ public interface ISubscriberService
|
|||||||
ISubscriber subscriber,
|
ISubscriber subscriber,
|
||||||
CustomerGetOptions customerGetOptions = null);
|
CustomerGetOptions customerGetOptions = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a list of Stripe <see cref="Invoice"/> objects using the <paramref name="subscriber"/>'s <see cref="ISubscriber.GatewayCustomerId"/> property.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subscriber">The subscriber to retrieve the Stripe invoices for.</param>
|
||||||
|
/// <param name="invoiceListOptions">Optional parameters that can be passed to Stripe to expand, modify or filter the invoices. The <see cref="subscriber"/>'s
|
||||||
|
/// <see cref="ISubscriber.GatewayCustomerId"/> will be automatically attached to the provided options as the <see cref="InvoiceListOptions.Customer"/> parameter.</param>
|
||||||
|
/// <returns>A list of Stripe <see cref="Invoice"/> objects.</returns>
|
||||||
|
/// <remarks>This method opts for returning an empty list rather than throwing exceptions, making it ideal for surfacing data from API endpoints.</remarks>
|
||||||
|
Task<List<Invoice>> GetInvoices(
|
||||||
|
ISubscriber subscriber,
|
||||||
|
StripeInvoiceListOptions invoiceListOptions = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the account credit, a masked representation of the default payment method and the tax information for the
|
/// Retrieves the account credit, a masked representation of the default payment method and the tax information for the
|
||||||
/// provided <paramref name="subscriber"/>. This is essentially a consolidated invocation of the <see cref="GetPaymentMethod"/>
|
/// provided <paramref name="subscriber"/>. This is essentially a consolidated invocation of the <see cref="GetPaymentMethod"/>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.BitStripe;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
@ -137,6 +138,76 @@ public class SubscriberService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Customer> GetCustomerOrThrow(
|
||||||
|
ISubscriber subscriber,
|
||||||
|
CustomerGetOptions customerGetOptions = null)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(subscriber);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(subscriber.GatewayCustomerId))
|
||||||
|
{
|
||||||
|
logger.LogError("Cannot retrieve customer for subscriber ({SubscriberID}) with no {FieldName}", subscriber.Id, nameof(subscriber.GatewayCustomerId));
|
||||||
|
|
||||||
|
throw ContactSupport();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var customer = await stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions);
|
||||||
|
|
||||||
|
if (customer != null)
|
||||||
|
{
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogError("Could not find Stripe customer ({CustomerID}) for subscriber ({SubscriberID})",
|
||||||
|
subscriber.GatewayCustomerId, subscriber.Id);
|
||||||
|
|
||||||
|
throw ContactSupport();
|
||||||
|
}
|
||||||
|
catch (StripeException exception)
|
||||||
|
{
|
||||||
|
logger.LogError("An error occurred while trying to retrieve Stripe customer ({CustomerID}) for subscriber ({SubscriberID}): {Error}",
|
||||||
|
subscriber.GatewayCustomerId, subscriber.Id, exception.Message);
|
||||||
|
|
||||||
|
throw ContactSupport("An error occurred while trying to retrieve a Stripe Customer", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Invoice>> GetInvoices(
|
||||||
|
ISubscriber subscriber,
|
||||||
|
StripeInvoiceListOptions invoiceListOptions = null)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(subscriber);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(subscriber.GatewayCustomerId))
|
||||||
|
{
|
||||||
|
logger.LogError("Cannot retrieve invoices for subscriber ({SubscriberID}) with no {FieldName}", subscriber.Id, nameof(subscriber.GatewayCustomerId));
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (invoiceListOptions == null)
|
||||||
|
{
|
||||||
|
invoiceListOptions = new StripeInvoiceListOptions { Customer = subscriber.GatewayCustomerId };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
invoiceListOptions.Customer = subscriber.GatewayCustomerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await stripeAdapter.InvoiceListAsync(invoiceListOptions);
|
||||||
|
}
|
||||||
|
catch (StripeException exception)
|
||||||
|
{
|
||||||
|
logger.LogError("An error occurred while trying to retrieve Stripe invoices for subscriber ({SubscriberID}): {Error}", subscriber.Id, exception.Message);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<PaymentInformationDTO> GetPaymentInformation(
|
public async Task<PaymentInformationDTO> GetPaymentInformation(
|
||||||
ISubscriber subscriber)
|
ISubscriber subscriber)
|
||||||
{
|
{
|
||||||
@ -177,42 +248,6 @@ public class SubscriberService(
|
|||||||
return await GetMaskedPaymentMethodDTOAsync(subscriber.Id, customer);
|
return await GetMaskedPaymentMethodDTOAsync(subscriber.Id, customer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Customer> GetCustomerOrThrow(
|
|
||||||
ISubscriber subscriber,
|
|
||||||
CustomerGetOptions customerGetOptions = null)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(subscriber);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(subscriber.GatewayCustomerId))
|
|
||||||
{
|
|
||||||
logger.LogError("Cannot retrieve customer for subscriber ({SubscriberID}) with no {FieldName}", subscriber.Id, nameof(subscriber.GatewayCustomerId));
|
|
||||||
|
|
||||||
throw ContactSupport();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var customer = await stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions);
|
|
||||||
|
|
||||||
if (customer != null)
|
|
||||||
{
|
|
||||||
return customer;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogError("Could not find Stripe customer ({CustomerID}) for subscriber ({SubscriberID})",
|
|
||||||
subscriber.GatewayCustomerId, subscriber.Id);
|
|
||||||
|
|
||||||
throw ContactSupport();
|
|
||||||
}
|
|
||||||
catch (StripeException exception)
|
|
||||||
{
|
|
||||||
logger.LogError("An error occurred while trying to retrieve Stripe customer ({CustomerID}) for subscriber ({SubscriberID}): {Error}",
|
|
||||||
subscriber.GatewayCustomerId, subscriber.Id, exception.Message);
|
|
||||||
|
|
||||||
throw ContactSupport("An error occurred while trying to retrieve a Stripe Customer", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Subscription> GetSubscription(
|
public async Task<Subscription> GetSubscription(
|
||||||
ISubscriber subscriber,
|
ISubscriber subscriber,
|
||||||
SubscriptionGetOptions subscriptionGetOptions = null)
|
SubscriptionGetOptions subscriptionGetOptions = null)
|
||||||
|
@ -26,6 +26,160 @@ namespace Bit.Api.Test.Billing.Controllers;
|
|||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class ProviderBillingControllerTests
|
public class ProviderBillingControllerTests
|
||||||
{
|
{
|
||||||
|
#region GetInvoices
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task GetInvoices_Ok(
|
||||||
|
Provider provider,
|
||||||
|
SutProvider<ProviderBillingController> sutProvider)
|
||||||
|
{
|
||||||
|
ConfigureStableInputs(provider, sutProvider);
|
||||||
|
|
||||||
|
var invoices = new List<Invoice>
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Created = new DateTime(2024, 7, 1),
|
||||||
|
Status = "draft",
|
||||||
|
Total = 100000,
|
||||||
|
HostedInvoiceUrl = "https://example.com/invoice/3",
|
||||||
|
InvoicePdf = "https://example.com/invoice/3/pdf"
|
||||||
|
},
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Created = new DateTime(2024, 6, 1),
|
||||||
|
Number = "2",
|
||||||
|
Status = "open",
|
||||||
|
Total = 100000,
|
||||||
|
HostedInvoiceUrl = "https://example.com/invoice/2",
|
||||||
|
InvoicePdf = "https://example.com/invoice/2/pdf"
|
||||||
|
},
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Created = new DateTime(2024, 5, 1),
|
||||||
|
Number = "1",
|
||||||
|
Status = "paid",
|
||||||
|
Total = 100000,
|
||||||
|
HostedInvoiceUrl = "https://example.com/invoice/1",
|
||||||
|
InvoicePdf = "https://example.com/invoice/1/pdf"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISubscriberService>().GetInvoices(provider).Returns(invoices);
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.GetInvoicesAsync(provider.Id);
|
||||||
|
|
||||||
|
Assert.IsType<Ok<InvoicesResponse>>(result);
|
||||||
|
|
||||||
|
var response = ((Ok<InvoicesResponse>)result).Value;
|
||||||
|
|
||||||
|
Assert.Equal(2, response.Invoices.Count);
|
||||||
|
|
||||||
|
var openInvoice = response.Invoices.FirstOrDefault(i => i.Status == "open");
|
||||||
|
|
||||||
|
Assert.NotNull(openInvoice);
|
||||||
|
Assert.Equal(new DateTime(2024, 6, 1), openInvoice.Date);
|
||||||
|
Assert.Equal("2", openInvoice.Number);
|
||||||
|
Assert.Equal(1000, openInvoice.Total);
|
||||||
|
Assert.Equal("https://example.com/invoice/2", openInvoice.Url);
|
||||||
|
Assert.Equal("https://example.com/invoice/2/pdf", openInvoice.PdfUrl);
|
||||||
|
|
||||||
|
var paidInvoice = response.Invoices.FirstOrDefault(i => i.Status == "paid");
|
||||||
|
Assert.NotNull(paidInvoice);
|
||||||
|
Assert.Equal(new DateTime(2024, 5, 1), paidInvoice.Date);
|
||||||
|
Assert.Equal("1", paidInvoice.Number);
|
||||||
|
Assert.Equal(1000, paidInvoice.Total);
|
||||||
|
Assert.Equal("https://example.com/invoice/1", paidInvoice.Url);
|
||||||
|
Assert.Equal("https://example.com/invoice/1/pdf", paidInvoice.PdfUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region GetPaymentInformationAsync
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task GetPaymentInformation_PaymentInformationNull_NotFound(
|
||||||
|
Provider provider,
|
||||||
|
SutProvider<ProviderBillingController> sutProvider)
|
||||||
|
{
|
||||||
|
ConfigureStableInputs(provider, sutProvider);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISubscriberService>().GetPaymentInformation(provider).ReturnsNull();
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.GetSubscriptionAsync(provider.Id);
|
||||||
|
|
||||||
|
Assert.IsType<NotFound>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task GetPaymentInformation_Ok(
|
||||||
|
Provider provider,
|
||||||
|
SutProvider<ProviderBillingController> sutProvider)
|
||||||
|
{
|
||||||
|
ConfigureStableInputs(provider, sutProvider);
|
||||||
|
|
||||||
|
var maskedPaymentMethod = new MaskedPaymentMethodDTO(PaymentMethodType.Card, "VISA *1234", false);
|
||||||
|
|
||||||
|
var taxInformation =
|
||||||
|
new TaxInformationDTO("US", "12345", "123456789", "123 Example St.", null, "Example Town", "NY");
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISubscriberService>().GetPaymentInformation(provider).Returns(new PaymentInformationDTO(
|
||||||
|
100,
|
||||||
|
maskedPaymentMethod,
|
||||||
|
taxInformation));
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.GetPaymentInformationAsync(provider.Id);
|
||||||
|
|
||||||
|
Assert.IsType<Ok<PaymentInformationResponse>>(result);
|
||||||
|
|
||||||
|
var response = ((Ok<PaymentInformationResponse>)result).Value;
|
||||||
|
|
||||||
|
Assert.Equal(100, response.AccountCredit);
|
||||||
|
Assert.Equal(maskedPaymentMethod.Description, response.PaymentMethod.Description);
|
||||||
|
Assert.Equal(taxInformation.TaxId, response.TaxInformation.TaxId);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region GetPaymentMethodAsync
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task GetPaymentMethod_PaymentMethodNull_NotFound(
|
||||||
|
Provider provider,
|
||||||
|
SutProvider<ProviderBillingController> sutProvider)
|
||||||
|
{
|
||||||
|
ConfigureStableInputs(provider, sutProvider);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISubscriberService>().GetPaymentMethod(provider).ReturnsNull();
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.GetSubscriptionAsync(provider.Id);
|
||||||
|
|
||||||
|
Assert.IsType<NotFound>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task GetPaymentMethod_Ok(
|
||||||
|
Provider provider,
|
||||||
|
SutProvider<ProviderBillingController> sutProvider)
|
||||||
|
{
|
||||||
|
ConfigureStableInputs(provider, sutProvider);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISubscriberService>().GetPaymentMethod(provider).Returns(new MaskedPaymentMethodDTO(
|
||||||
|
PaymentMethodType.Card, "Description", false));
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.GetPaymentMethodAsync(provider.Id);
|
||||||
|
|
||||||
|
Assert.IsType<Ok<MaskedPaymentMethodResponse>>(result);
|
||||||
|
|
||||||
|
var response = ((Ok<MaskedPaymentMethodResponse>)result).Value;
|
||||||
|
|
||||||
|
Assert.Equal(PaymentMethodType.Card, response.Type);
|
||||||
|
Assert.Equal("Description", response.Description);
|
||||||
|
Assert.False(response.NeedsVerification);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region GetSubscriptionAsync
|
#region GetSubscriptionAsync
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetSubscriptionAsync_FFDisabled_NotFound(
|
public async Task GetSubscriptionAsync_FFDisabled_NotFound(
|
||||||
@ -165,91 +319,6 @@ public class ProviderBillingControllerTests
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region GetPaymentInformationAsync
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task GetPaymentInformation_PaymentInformationNull_NotFound(
|
|
||||||
Provider provider,
|
|
||||||
SutProvider<ProviderBillingController> sutProvider)
|
|
||||||
{
|
|
||||||
ConfigureStableInputs(provider, sutProvider);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>().GetPaymentInformation(provider).ReturnsNull();
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.GetSubscriptionAsync(provider.Id);
|
|
||||||
|
|
||||||
Assert.IsType<NotFound>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task GetPaymentInformation_Ok(
|
|
||||||
Provider provider,
|
|
||||||
SutProvider<ProviderBillingController> sutProvider)
|
|
||||||
{
|
|
||||||
ConfigureStableInputs(provider, sutProvider);
|
|
||||||
|
|
||||||
var maskedPaymentMethod = new MaskedPaymentMethodDTO(PaymentMethodType.Card, "VISA *1234", false);
|
|
||||||
|
|
||||||
var taxInformation =
|
|
||||||
new TaxInformationDTO("US", "12345", "123456789", "123 Example St.", null, "Example Town", "NY");
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>().GetPaymentInformation(provider).Returns(new PaymentInformationDTO(
|
|
||||||
100,
|
|
||||||
maskedPaymentMethod,
|
|
||||||
taxInformation));
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.GetPaymentInformationAsync(provider.Id);
|
|
||||||
|
|
||||||
Assert.IsType<Ok<PaymentInformationResponse>>(result);
|
|
||||||
|
|
||||||
var response = ((Ok<PaymentInformationResponse>)result).Value;
|
|
||||||
|
|
||||||
Assert.Equal(100, response.AccountCredit);
|
|
||||||
Assert.Equal(maskedPaymentMethod.Description, response.PaymentMethod.Description);
|
|
||||||
Assert.Equal(taxInformation.TaxId, response.TaxInformation.TaxId);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region GetPaymentMethodAsync
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task GetPaymentMethod_PaymentMethodNull_NotFound(
|
|
||||||
Provider provider,
|
|
||||||
SutProvider<ProviderBillingController> sutProvider)
|
|
||||||
{
|
|
||||||
ConfigureStableInputs(provider, sutProvider);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>().GetPaymentMethod(provider).ReturnsNull();
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.GetSubscriptionAsync(provider.Id);
|
|
||||||
|
|
||||||
Assert.IsType<NotFound>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task GetPaymentMethod_Ok(
|
|
||||||
Provider provider,
|
|
||||||
SutProvider<ProviderBillingController> sutProvider)
|
|
||||||
{
|
|
||||||
ConfigureStableInputs(provider, sutProvider);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>().GetPaymentMethod(provider).Returns(new MaskedPaymentMethodDTO(
|
|
||||||
PaymentMethodType.Card, "Description", false));
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.GetPaymentMethodAsync(provider.Id);
|
|
||||||
|
|
||||||
Assert.IsType<Ok<MaskedPaymentMethodResponse>>(result);
|
|
||||||
|
|
||||||
var response = ((Ok<MaskedPaymentMethodResponse>)result).Value;
|
|
||||||
|
|
||||||
Assert.Equal(PaymentMethodType.Card, response.Type);
|
|
||||||
Assert.Equal("Description", response.Description);
|
|
||||||
Assert.False(response.NeedsVerification);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region GetTaxInformationAsync
|
#region GetTaxInformationAsync
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
|
@ -5,6 +5,7 @@ using Bit.Core.Billing.Constants;
|
|||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Services.Implementations;
|
using Bit.Core.Billing.Services.Implementations;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.BitStripe;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
@ -320,6 +321,109 @@ public class SubscriberServiceTests
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region GetInvoices
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task GetInvoices_NullSubscriber_ThrowsArgumentNullException(
|
||||||
|
SutProvider<SubscriberService> sutProvider)
|
||||||
|
=> await Assert.ThrowsAsync<ArgumentNullException>(
|
||||||
|
async () => await sutProvider.Sut.GetInvoices(null));
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task GetCustomer_NoGatewayCustomerId_ReturnsEmptyList(
|
||||||
|
Organization organization,
|
||||||
|
SutProvider<SubscriberService> sutProvider)
|
||||||
|
{
|
||||||
|
organization.GatewayCustomerId = null;
|
||||||
|
|
||||||
|
var invoices = await sutProvider.Sut.GetInvoices(organization);
|
||||||
|
|
||||||
|
Assert.Empty(invoices);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task GetInvoices_StripeException_ReturnsEmptyList(
|
||||||
|
Organization organization,
|
||||||
|
SutProvider<SubscriberService> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IStripeAdapter>()
|
||||||
|
.InvoiceListAsync(Arg.Any<StripeInvoiceListOptions>())
|
||||||
|
.ThrowsAsync<StripeException>();
|
||||||
|
|
||||||
|
var invoices = await sutProvider.Sut.GetInvoices(organization);
|
||||||
|
|
||||||
|
Assert.Empty(invoices);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task GetInvoices_NullOptions_Succeeds(
|
||||||
|
Organization organization,
|
||||||
|
SutProvider<SubscriberService> sutProvider)
|
||||||
|
{
|
||||||
|
var invoices = new List<Invoice>
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Created = new DateTime(2024, 6, 1),
|
||||||
|
Number = "2",
|
||||||
|
Status = "open",
|
||||||
|
Total = 100000,
|
||||||
|
HostedInvoiceUrl = "https://example.com/invoice/2",
|
||||||
|
InvoicePdf = "https://example.com/invoice/2/pdf"
|
||||||
|
},
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Created = new DateTime(2024, 5, 1),
|
||||||
|
Number = "1",
|
||||||
|
Status = "paid",
|
||||||
|
Total = 100000,
|
||||||
|
HostedInvoiceUrl = "https://example.com/invoice/1",
|
||||||
|
InvoicePdf = "https://example.com/invoice/1/pdf"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IStripeAdapter>()
|
||||||
|
.InvoiceListAsync(Arg.Is<StripeInvoiceListOptions>(options => options.Customer == organization.GatewayCustomerId))
|
||||||
|
.Returns(invoices);
|
||||||
|
|
||||||
|
var gotInvoices = await sutProvider.Sut.GetInvoices(organization);
|
||||||
|
|
||||||
|
Assert.Equivalent(invoices, gotInvoices);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task GetInvoices_ProvidedOptions_Succeeds(
|
||||||
|
Organization organization,
|
||||||
|
SutProvider<SubscriberService> sutProvider)
|
||||||
|
{
|
||||||
|
var invoices = new List<Invoice>
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Created = new DateTime(2024, 5, 1),
|
||||||
|
Number = "1",
|
||||||
|
Status = "paid",
|
||||||
|
Total = 100000,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IStripeAdapter>()
|
||||||
|
.InvoiceListAsync(Arg.Is<StripeInvoiceListOptions>(
|
||||||
|
options =>
|
||||||
|
options.Customer == organization.GatewayCustomerId &&
|
||||||
|
options.Status == "paid"))
|
||||||
|
.Returns(invoices);
|
||||||
|
|
||||||
|
var gotInvoices = await sutProvider.Sut.GetInvoices(organization, new StripeInvoiceListOptions
|
||||||
|
{
|
||||||
|
Status = "paid"
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equivalent(invoices, gotInvoices);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region GetPaymentMethod
|
#region GetPaymentMethod
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetPaymentMethod_NullSubscriber_ThrowsArgumentNullException(
|
public async Task GetPaymentMethod_NullSubscriber_ThrowsArgumentNullException(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user