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

[PM-3892] Implement dollar threshold for all subscriptions (#3283)

* Initial commit

* Fix the failing text

* Fix the unpaid invoice issue

* fix the unpaid invoice issue

* Changes for the threshold amount

* remove the billing threshold

* Add some comments to the old method

* Fixing issues on secret manager test

* import missing package

* Resolve pr comments

* Refactor PreviewUpcomingInvoiceAndPayAsync method

* Resolve some pr comments

* Resolving the comment around constant

* Resolve pr comment

* Add new class

* Resolve pr comments

* Change the prorateThreshold from 5 to 500 dollars

* Fix the failing test

* Fix the server returns a 500 error with the banner
This commit is contained in:
cyprain-okeke
2023-11-01 15:19:28 +01:00
committed by GitHub
parent f9fc43dbb1
commit d9faa9a6df
8 changed files with 692 additions and 56 deletions

View File

@ -739,4 +739,300 @@ public class StripePaymentServiceTests
Assert.Null(result);
}
[Theory, BitAutoData]
public async Task PreviewUpcomingInvoiceAndPayAsync_WithInAppPaymentMethod_ThrowsBadRequestException(SutProvider<StripePaymentService> sutProvider,
Organization subscriber, List<Stripe.InvoiceSubscriptionItemOptions> subItemOptions)
{
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerGetAsync(Arg.Any<string>(), Arg.Any<Stripe.CustomerGetOptions>())
.Returns(new Stripe.Customer { Metadata = new Dictionary<string, string> { { "appleReceipt", "dummyData" } } });
var ex = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PreviewUpcomingInvoiceAndPayAsync(subscriber, subItemOptions));
Assert.Equal("Cannot perform this action with in-app purchase payment method. Contact support.", ex.Message);
}
[Theory, BitAutoData]
public async void PreviewUpcomingInvoiceAndPayAsync_UpcomingInvoiceBelowThreshold_DoesNotInvoiceNow(SutProvider<StripePaymentService> sutProvider,
Organization subscriber, List<Stripe.InvoiceSubscriptionItemOptions> subItemOptions)
{
var prorateThreshold = 50000;
var invoiceAmountBelowThreshold = prorateThreshold - 100;
var customer = MockStripeCustomer(subscriber);
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(default, default).ReturnsForAnyArgs(customer);
var invoiceItem = MockInoviceItemList(subscriber, "planId", invoiceAmountBelowThreshold, customer);
sutProvider.GetDependency<IStripeAdapter>().InvoiceItemListAsync(new Stripe.InvoiceItemListOptions
{
Customer = subscriber.GatewayCustomerId
}).ReturnsForAnyArgs(invoiceItem);
var invoiceLineItem = CreateInvoiceLineTime(subscriber, "planId", invoiceAmountBelowThreshold);
sutProvider.GetDependency<IStripeAdapter>().InvoiceUpcomingAsync(new Stripe.UpcomingInvoiceOptions
{
Customer = subscriber.GatewayCustomerId,
Subscription = subscriber.GatewaySubscriptionId,
SubscriptionItems = subItemOptions
}).ReturnsForAnyArgs(invoiceLineItem);
sutProvider.GetDependency<IStripeAdapter>().InvoiceCreateAsync(Arg.Is<Stripe.InvoiceCreateOptions>(options =>
options.CollectionMethod == "send_invoice" &&
options.DaysUntilDue == 1 &&
options.Customer == subscriber.GatewayCustomerId &&
options.Subscription == subscriber.GatewaySubscriptionId &&
options.DefaultPaymentMethod == customer.InvoiceSettings.DefaultPaymentMethod.Id
)).ReturnsForAnyArgs(new Stripe.Invoice
{
Id = "mockInvoiceId",
CollectionMethod = "send_invoice",
DueDate = DateTime.Now.AddDays(1),
Customer = customer,
Subscription = new Stripe.Subscription
{
Id = "mockSubscriptionId",
Customer = customer,
Status = "active",
CurrentPeriodStart = DateTime.UtcNow,
CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1),
CollectionMethod = "charge_automatically",
},
DefaultPaymentMethod = customer.InvoiceSettings.DefaultPaymentMethod,
AmountDue = invoiceAmountBelowThreshold,
Currency = "usd",
Status = "draft",
});
var result = await sutProvider.Sut.PreviewUpcomingInvoiceAndPayAsync(subscriber, new List<Stripe.InvoiceSubscriptionItemOptions>(), prorateThreshold);
Assert.False(result.IsInvoicedNow);
Assert.Null(result.PaymentIntentClientSecret);
}
[Theory, BitAutoData]
public async void PreviewUpcomingInvoiceAndPayAsync_NoPaymentMethod_ThrowsBadRequestException(SutProvider<StripePaymentService> sutProvider,
Organization subscriber, List<Stripe.InvoiceSubscriptionItemOptions> subItemOptions, string planId)
{
var prorateThreshold = 120000;
var invoiceAmountBelowThreshold = prorateThreshold;
var customer = new Stripe.Customer
{
Metadata = new Dictionary<string, string>(),
Id = subscriber.GatewayCustomerId,
DefaultSource = null,
InvoiceSettings = new Stripe.CustomerInvoiceSettings
{
DefaultPaymentMethod = null
}
};
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(default, default).ReturnsForAnyArgs(customer);
var invoiceItem = MockInoviceItemList(subscriber, planId, invoiceAmountBelowThreshold, customer);
sutProvider.GetDependency<IStripeAdapter>().InvoiceItemListAsync(new Stripe.InvoiceItemListOptions
{
Customer = subscriber.GatewayCustomerId
}).ReturnsForAnyArgs(invoiceItem);
var invoiceLineItem = CreateInvoiceLineTime(subscriber, planId, invoiceAmountBelowThreshold);
sutProvider.GetDependency<IStripeAdapter>().InvoiceUpcomingAsync(new Stripe.UpcomingInvoiceOptions
{
Customer = subscriber.GatewayCustomerId,
Subscription = subscriber.GatewaySubscriptionId,
SubscriptionItems = subItemOptions
}).ReturnsForAnyArgs(invoiceLineItem);
var ex = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PreviewUpcomingInvoiceAndPayAsync(subscriber, subItemOptions));
Assert.Equal("No payment method is available.", ex.Message);
}
[Theory, BitAutoData]
public async void PreviewUpcomingInvoiceAndPayAsync_UpcomingInvoiceAboveThreshold_DoesInvoiceNow(SutProvider<StripePaymentService> sutProvider,
Organization subscriber, List<Stripe.InvoiceSubscriptionItemOptions> subItemOptions, string planId)
{
var prorateThreshold = 50000;
var invoiceAmountBelowThreshold = 100000;
var customer = MockStripeCustomer(subscriber);
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(default, default).ReturnsForAnyArgs(customer);
var invoiceItem = MockInoviceItemList(subscriber, planId, invoiceAmountBelowThreshold, customer);
sutProvider.GetDependency<IStripeAdapter>().InvoiceItemListAsync(new Stripe.InvoiceItemListOptions
{
Customer = subscriber.GatewayCustomerId
}).ReturnsForAnyArgs(invoiceItem);
var invoiceLineItem = CreateInvoiceLineTime(subscriber, planId, invoiceAmountBelowThreshold);
sutProvider.GetDependency<IStripeAdapter>().InvoiceUpcomingAsync(new Stripe.UpcomingInvoiceOptions
{
Customer = subscriber.GatewayCustomerId,
Subscription = subscriber.GatewaySubscriptionId,
SubscriptionItems = subItemOptions
}).ReturnsForAnyArgs(invoiceLineItem);
var invoice = MockInVoice(customer, invoiceAmountBelowThreshold);
sutProvider.GetDependency<IStripeAdapter>().InvoiceCreateAsync(Arg.Is<Stripe.InvoiceCreateOptions>(options =>
options.CollectionMethod == "send_invoice" &&
options.DaysUntilDue == 1 &&
options.Customer == subscriber.GatewayCustomerId &&
options.Subscription == subscriber.GatewaySubscriptionId &&
options.DefaultPaymentMethod == customer.InvoiceSettings.DefaultPaymentMethod.Id
)).ReturnsForAnyArgs(invoice);
var result = await sutProvider.Sut.PreviewUpcomingInvoiceAndPayAsync(subscriber, new List<Stripe.InvoiceSubscriptionItemOptions>(), prorateThreshold);
await sutProvider.GetDependency<IStripeAdapter>().Received(1).InvoicePayAsync(invoice.Id,
Arg.Is<Stripe.InvoicePayOptions>((options =>
options.OffSession == true
)));
Assert.True(result.IsInvoicedNow);
Assert.Null(result.PaymentIntentClientSecret);
}
private static Stripe.Invoice MockInVoice(Stripe.Customer customer, int invoiceAmountBelowThreshold) =>
new()
{
Id = "mockInvoiceId",
CollectionMethod = "send_invoice",
DueDate = DateTime.Now.AddDays(1),
Customer = customer,
Subscription = new Stripe.Subscription
{
Id = "mockSubscriptionId",
Customer = customer,
Status = "active",
CurrentPeriodStart = DateTime.UtcNow,
CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1),
CollectionMethod = "charge_automatically",
},
DefaultPaymentMethod = customer.InvoiceSettings.DefaultPaymentMethod,
AmountDue = invoiceAmountBelowThreshold,
Currency = "usd",
Status = "draft",
};
private static List<Stripe.InvoiceItem> MockInoviceItemList(Organization subscriber, string planId, int invoiceAmountBelowThreshold, Stripe.Customer customer) =>
new()
{
new Stripe.InvoiceItem
{
Id = "ii_1234567890",
Amount = invoiceAmountBelowThreshold,
Currency = "usd",
CustomerId = subscriber.GatewayCustomerId,
Description = "Sample invoice item 1",
Date = DateTime.UtcNow,
Discountable = true,
InvoiceId = "548458365"
},
new Stripe.InvoiceItem
{
Id = "ii_0987654321",
Amount = invoiceAmountBelowThreshold,
Currency = "usd",
CustomerId = customer.Id,
Description = "Sample invoice item 2",
Date = DateTime.UtcNow.AddDays(-5),
Discountable = false,
InvoiceId = null,
Proration = true,
Plan = new Stripe.Plan
{
Id = planId,
Amount = invoiceAmountBelowThreshold,
Currency = "usd",
Interval = "month",
IntervalCount = 1,
},
}
};
private static Stripe.Customer MockStripeCustomer(Organization subscriber)
{
var customer = new Stripe.Customer
{
Metadata = new Dictionary<string, string>(),
Id = subscriber.GatewayCustomerId,
DefaultSource = new Stripe.Card
{
Id = "card_12345",
Last4 = "1234",
Brand = "Visa",
ExpYear = 2025,
ExpMonth = 12
},
InvoiceSettings = new Stripe.CustomerInvoiceSettings
{
DefaultPaymentMethod = new Stripe.PaymentMethod
{
Id = "pm_12345",
Type = "card",
Card = new Stripe.PaymentMethodCard
{
Last4 = "1234",
Brand = "Visa",
ExpYear = 2025,
ExpMonth = 12
}
}
}
};
return customer;
}
private static Stripe.Invoice CreateInvoiceLineTime(Organization subscriber, string planId, int invoiceAmountBelowThreshold) =>
new()
{
AmountDue = invoiceAmountBelowThreshold,
AmountPaid = 0,
AmountRemaining = invoiceAmountBelowThreshold,
CustomerId = subscriber.GatewayCustomerId,
SubscriptionId = subscriber.GatewaySubscriptionId,
ApplicationFeeAmount = 0,
Currency = "usd",
Description = "Upcoming Invoice",
Discount = null,
DueDate = DateTime.UtcNow.AddDays(1),
EndingBalance = 0,
Number = "INV12345",
Paid = false,
PeriodStart = DateTime.UtcNow,
PeriodEnd = DateTime.UtcNow.AddMonths(1),
ReceiptNumber = null,
StartingBalance = 0,
Status = "draft",
Id = "ii_0987654321",
Total = invoiceAmountBelowThreshold,
Lines = new Stripe.StripeList<Stripe.InvoiceLineItem>
{
Data = new List<Stripe.InvoiceLineItem>
{
new Stripe.InvoiceLineItem
{
Amount = invoiceAmountBelowThreshold,
Currency = "usd",
Description = "Sample line item",
Id = "ii_0987654321",
Livemode = false,
Object = "line_item",
Discountable = false,
Period = new Stripe.InvoiceLineItemPeriod()
{
Start = DateTime.UtcNow,
End = DateTime.UtcNow.AddMonths(1)
},
Plan = new Stripe.Plan
{
Id = planId,
Amount = invoiceAmountBelowThreshold,
Currency = "usd",
Interval = "month",
IntervalCount = 1,
},
Proration = true,
Quantity = 1,
Subscription = subscriber.GatewaySubscriptionId,
SubscriptionItem = "si_12345",
Type = "subscription",
UnitAmountExcludingTax = invoiceAmountBelowThreshold,
}
}
}
};
}