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:
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user