mirror of
https://github.com/bitwarden/server
synced 2026-01-15 15:03:34 +00:00
* feat(get-subscription): Add EnumMemberJsonConverter * feat(get-subscription): Add BitwardenDiscount model * feat(get-subscription): Add Cart model * feat(get-subscription): Add Storage model * feat(get-subscription): Add BitwardenSubscription model * feat(get-subscription): Add DiscountExtensions * feat(get-subscription): Add error code to StripeConstants * feat(get-subscription): Add GetBitwardenSubscriptionQuery * feat(get-subscription): Expose GET /account/billing/vnext/subscription * feat(reinstate-subscription): Add ReinstateSubscriptionCommand * feat(reinstate-subscription): Expose POST /account/billing/vnext/subscription/reinstate * feat(pay-with-paypal-immediately): Add SubscriberId union * feat(pay-with-paypal-immediately): Add BraintreeService with PayInvoice method * feat(pay-with-paypal-immediately): Pay PayPal invoice immediately when starting premium subscription * feat(pay-with-paypal-immediately): Pay invoice with Braintree on invoice.created for subscription cycles only * fix(update-storage): Always invoice for premium storage update * fix(update-storage): Move endpoint to subscription path * docs: Note FF removal POIs * (format): Run dotnet format
608 lines
22 KiB
C#
608 lines
22 KiB
C#
using Bit.Core.Billing.Constants;
|
|
using Bit.Core.Billing.Enums;
|
|
using Bit.Core.Billing.Pricing;
|
|
using Bit.Core.Billing.Services;
|
|
using Bit.Core.Billing.Subscriptions.Models;
|
|
using Bit.Core.Billing.Subscriptions.Queries;
|
|
using Bit.Core.Entities;
|
|
using Bit.Core.Exceptions;
|
|
using Microsoft.Extensions.Logging;
|
|
using NSubstitute;
|
|
using NSubstitute.ExceptionExtensions;
|
|
using Stripe;
|
|
using Xunit;
|
|
|
|
namespace Bit.Core.Test.Billing.Subscriptions.Queries;
|
|
|
|
using static StripeConstants;
|
|
|
|
public class GetBitwardenSubscriptionQueryTests
|
|
{
|
|
private readonly ILogger<GetBitwardenSubscriptionQuery> _logger = Substitute.For<ILogger<GetBitwardenSubscriptionQuery>>();
|
|
private readonly IPricingClient _pricingClient = Substitute.For<IPricingClient>();
|
|
private readonly IStripeAdapter _stripeAdapter = Substitute.For<IStripeAdapter>();
|
|
private readonly GetBitwardenSubscriptionQuery _query;
|
|
|
|
public GetBitwardenSubscriptionQueryTests()
|
|
{
|
|
_query = new GetBitwardenSubscriptionQuery(
|
|
_logger,
|
|
_pricingClient,
|
|
_stripeAdapter);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_IncompleteStatus_ReturnsBitwardenSubscriptionWithSuspension()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Incomplete);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(SubscriptionStatus.Incomplete, result.Status);
|
|
Assert.NotNull(result.Suspension);
|
|
Assert.Equal(subscription.Created.AddHours(23), result.Suspension);
|
|
Assert.Equal(1, result.GracePeriod);
|
|
Assert.Null(result.NextCharge);
|
|
Assert.Null(result.CancelAt);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_IncompleteExpiredStatus_ReturnsBitwardenSubscriptionWithSuspension()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.IncompleteExpired);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(SubscriptionStatus.IncompleteExpired, result.Status);
|
|
Assert.NotNull(result.Suspension);
|
|
Assert.Equal(subscription.Created.AddHours(23), result.Suspension);
|
|
Assert.Equal(1, result.GracePeriod);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_TrialingStatus_ReturnsBitwardenSubscriptionWithNextCharge()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Trialing);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(SubscriptionStatus.Trialing, result.Status);
|
|
Assert.NotNull(result.NextCharge);
|
|
Assert.Equal(subscription.Items.First().CurrentPeriodEnd, result.NextCharge);
|
|
Assert.Null(result.Suspension);
|
|
Assert.Null(result.GracePeriod);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_ActiveStatus_ReturnsBitwardenSubscriptionWithNextCharge()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(SubscriptionStatus.Active, result.Status);
|
|
Assert.NotNull(result.NextCharge);
|
|
Assert.Equal(subscription.Items.First().CurrentPeriodEnd, result.NextCharge);
|
|
Assert.Null(result.Suspension);
|
|
Assert.Null(result.GracePeriod);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_ActiveStatusWithCancelAt_ReturnsCancelAt()
|
|
{
|
|
var user = CreateUser();
|
|
var cancelAt = DateTime.UtcNow.AddMonths(1);
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active, cancelAt: cancelAt);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(SubscriptionStatus.Active, result.Status);
|
|
Assert.Equal(cancelAt, result.CancelAt);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_PastDueStatus_WithOpenInvoices_ReturnsSuspension()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.PastDue, collectionMethod: "charge_automatically");
|
|
var premiumPlans = CreatePremiumPlans();
|
|
var openInvoice = CreateInvoice();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
_stripeAdapter.SearchInvoiceAsync(Arg.Any<InvoiceSearchOptions>())
|
|
.Returns([openInvoice]);
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(SubscriptionStatus.PastDue, result.Status);
|
|
Assert.NotNull(result.Suspension);
|
|
Assert.Equal(openInvoice.Created.AddDays(14), result.Suspension);
|
|
Assert.Equal(14, result.GracePeriod);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_PastDueStatus_WithoutOpenInvoices_ReturnsNoSuspension()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.PastDue);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
_stripeAdapter.SearchInvoiceAsync(Arg.Any<InvoiceSearchOptions>())
|
|
.Returns([]);
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(SubscriptionStatus.PastDue, result.Status);
|
|
Assert.Null(result.Suspension);
|
|
Assert.Null(result.GracePeriod);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_UnpaidStatus_WithOpenInvoices_ReturnsSuspension()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Unpaid, collectionMethod: "charge_automatically");
|
|
var premiumPlans = CreatePremiumPlans();
|
|
var openInvoice = CreateInvoice();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
_stripeAdapter.SearchInvoiceAsync(Arg.Any<InvoiceSearchOptions>())
|
|
.Returns([openInvoice]);
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(SubscriptionStatus.Unpaid, result.Status);
|
|
Assert.NotNull(result.Suspension);
|
|
Assert.Equal(14, result.GracePeriod);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_CanceledStatus_ReturnsCanceledDate()
|
|
{
|
|
var user = CreateUser();
|
|
var canceledAt = DateTime.UtcNow.AddDays(-5);
|
|
var subscription = CreateSubscription(SubscriptionStatus.Canceled, canceledAt: canceledAt);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(SubscriptionStatus.Canceled, result.Status);
|
|
Assert.Equal(canceledAt, result.Canceled);
|
|
Assert.Null(result.Suspension);
|
|
Assert.Null(result.NextCharge);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_UnmanagedStatus_ThrowsConflictException()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription("unmanaged_status");
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
await Assert.ThrowsAsync<ConflictException>(() => _query.Run(user));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_WithAdditionalStorage_IncludesStorageInCart()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active, includeStorage: true);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Cart.PasswordManager.AdditionalStorage);
|
|
Assert.Equal("additionalStorageGB", result.Cart.PasswordManager.AdditionalStorage.TranslationKey);
|
|
Assert.Equal(2, result.Cart.PasswordManager.AdditionalStorage.Quantity);
|
|
Assert.NotNull(result.Storage);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_WithoutAdditionalStorage_ExcludesStorageFromCart()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active, includeStorage: false);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Null(result.Cart.PasswordManager.AdditionalStorage);
|
|
Assert.NotNull(result.Storage);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_WithCartLevelDiscount_IncludesDiscountInCart()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active);
|
|
subscription.Customer.Discount = CreateDiscount(discountType: "cart");
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Cart.Discount);
|
|
Assert.Equal(BitwardenDiscountType.PercentOff, result.Cart.Discount.Type);
|
|
Assert.Equal(20, result.Cart.Discount.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_WithProductLevelDiscount_IncludesDiscountInCartItem()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active);
|
|
var productDiscount = CreateDiscount(discountType: "product", productId: "prod_premium_seat");
|
|
subscription.Discounts = [productDiscount];
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Cart.PasswordManager.Seats.Discount);
|
|
Assert.Equal(BitwardenDiscountType.PercentOff, result.Cart.PasswordManager.Seats.Discount.Type);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_WithoutMaxStorageGb_ReturnsNullStorage()
|
|
{
|
|
var user = CreateUser();
|
|
user.MaxStorageGb = null;
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Null(result.Storage);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_CalculatesStorageCorrectly()
|
|
{
|
|
var user = CreateUser();
|
|
user.Storage = 5368709120; // 5 GB in bytes
|
|
user.MaxStorageGb = 10;
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active, includeStorage: true);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Storage);
|
|
Assert.Equal(10, result.Storage.Available);
|
|
Assert.Equal(5.0, result.Storage.Used);
|
|
Assert.NotEmpty(result.Storage.ReadableUsed);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_TaxEstimation_WithInvoiceUpcomingNoneError_ReturnsZeroTax()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.ThrowsAsync(new StripeException { StripeError = new StripeError { Code = ErrorCodes.InvoiceUpcomingNone } });
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(0, result.Cart.EstimatedTax);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_MissingPasswordManagerSeatsItem_ThrowsConflictException()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active);
|
|
subscription.Items = new StripeList<SubscriptionItem>
|
|
{
|
|
Data = []
|
|
};
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
|
|
await Assert.ThrowsAsync<ConflictException>(() => _query.Run(user));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_IncludesEstimatedTax()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
var invoice = CreateInvoicePreview(totalTax: 500); // $5.00 tax
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(invoice);
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(5.0m, result.Cart.EstimatedTax);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_SetsCadenceToAnnually()
|
|
{
|
|
var user = CreateUser();
|
|
var subscription = CreateSubscription(SubscriptionStatus.Active);
|
|
var premiumPlans = CreatePremiumPlans();
|
|
|
|
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
|
|
.Returns(subscription);
|
|
_pricingClient.ListPremiumPlans().Returns(premiumPlans);
|
|
_stripeAdapter.CreateInvoicePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
|
.Returns(CreateInvoicePreview());
|
|
|
|
var result = await _query.Run(user);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(PlanCadenceType.Annually, result.Cart.Cadence);
|
|
}
|
|
|
|
#region Helper Methods
|
|
|
|
private static User CreateUser()
|
|
{
|
|
return new User
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
GatewaySubscriptionId = "sub_test123",
|
|
MaxStorageGb = 1,
|
|
Storage = 1073741824 // 1 GB in bytes
|
|
};
|
|
}
|
|
|
|
private static Subscription CreateSubscription(
|
|
string status,
|
|
bool includeStorage = false,
|
|
DateTime? cancelAt = null,
|
|
DateTime? canceledAt = null,
|
|
string collectionMethod = "charge_automatically")
|
|
{
|
|
var currentPeriodEnd = DateTime.UtcNow.AddMonths(1);
|
|
var items = new List<SubscriptionItem>
|
|
{
|
|
new()
|
|
{
|
|
Id = "si_premium_seat",
|
|
Price = new Price
|
|
{
|
|
Id = "price_premium_seat",
|
|
UnitAmountDecimal = 1000,
|
|
Product = new Product { Id = "prod_premium_seat" }
|
|
},
|
|
Quantity = 1,
|
|
CurrentPeriodStart = DateTime.UtcNow,
|
|
CurrentPeriodEnd = currentPeriodEnd
|
|
}
|
|
};
|
|
|
|
if (includeStorage)
|
|
{
|
|
items.Add(new SubscriptionItem
|
|
{
|
|
Id = "si_storage",
|
|
Price = new Price
|
|
{
|
|
Id = "price_storage",
|
|
UnitAmountDecimal = 400,
|
|
Product = new Product { Id = "prod_storage" }
|
|
},
|
|
Quantity = 2,
|
|
CurrentPeriodStart = DateTime.UtcNow,
|
|
CurrentPeriodEnd = currentPeriodEnd
|
|
});
|
|
}
|
|
|
|
return new Subscription
|
|
{
|
|
Id = "sub_test123",
|
|
Status = status,
|
|
Created = DateTime.UtcNow.AddMonths(-1),
|
|
Customer = new Customer
|
|
{
|
|
Id = "cus_test123",
|
|
Discount = null
|
|
},
|
|
Items = new StripeList<SubscriptionItem>
|
|
{
|
|
Data = items
|
|
},
|
|
CancelAt = cancelAt,
|
|
CanceledAt = canceledAt,
|
|
CollectionMethod = collectionMethod,
|
|
Discounts = []
|
|
};
|
|
}
|
|
|
|
private static List<Bit.Core.Billing.Pricing.Premium.Plan> CreatePremiumPlans()
|
|
{
|
|
return
|
|
[
|
|
new()
|
|
{
|
|
Name = "Premium",
|
|
Available = true,
|
|
Seat = new Bit.Core.Billing.Pricing.Premium.Purchasable
|
|
{
|
|
StripePriceId = "price_premium_seat",
|
|
Price = 10.0m,
|
|
Provided = 1
|
|
},
|
|
Storage = new Bit.Core.Billing.Pricing.Premium.Purchasable
|
|
{
|
|
StripePriceId = "price_storage",
|
|
Price = 4.0m,
|
|
Provided = 1
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
private static Invoice CreateInvoice()
|
|
{
|
|
return new Invoice
|
|
{
|
|
Id = "in_test123",
|
|
Created = DateTime.UtcNow.AddDays(-10),
|
|
PeriodEnd = DateTime.UtcNow.AddDays(-5),
|
|
Attempted = true,
|
|
Status = "open"
|
|
};
|
|
}
|
|
|
|
private static Invoice CreateInvoicePreview(long totalTax = 0)
|
|
{
|
|
var taxes = totalTax > 0
|
|
? new List<InvoiceTotalTax> { new() { Amount = totalTax } }
|
|
: new List<InvoiceTotalTax>();
|
|
|
|
return new Invoice
|
|
{
|
|
Id = "in_preview",
|
|
TotalTaxes = taxes
|
|
};
|
|
}
|
|
|
|
private static Discount CreateDiscount(string discountType = "cart", string? productId = null)
|
|
{
|
|
var coupon = new Coupon
|
|
{
|
|
Valid = true,
|
|
PercentOff = 20,
|
|
AppliesTo = discountType == "product" && productId != null
|
|
? new CouponAppliesTo { Products = [productId] }
|
|
: new CouponAppliesTo { Products = [] }
|
|
};
|
|
|
|
return new Discount
|
|
{
|
|
Coupon = coupon
|
|
};
|
|
}
|
|
|
|
#endregion
|
|
}
|