mirror of
https://github.com/bitwarden/server.git
synced 2025-01-22 21:51:22 +01:00
[PM 5864] Resolve root cause of double-charging customers with implementation of PM-3892 (#3762)
* Getting dollar threshold to work * Added billing cycle anchor to invoice upcoming call * Added comments for further work * add featureflag Signed-off-by: Cy Okeke <cokeke@bitwarden.com> * resolve pr comments Signed-off-by: Cy Okeke <cokeke@bitwarden.com> * Resolve pr comment Signed-off-by: Cy Okeke <cokeke@bitwarden.com> --------- Signed-off-by: Cy Okeke <cokeke@bitwarden.com> Co-authored-by: Conner Turnbull <cturnbull@bitwarden.com>
This commit is contained in:
parent
0258f4949c
commit
accff663c5
@ -35,6 +35,20 @@ public static class Constants
|
||||
/// If true, the organization plan assigned to that provider is updated to a 2020 plan.
|
||||
/// </summary>
|
||||
public static readonly DateTime ProviderCreatedPriorNov62023 = new DateTime(2023, 11, 6);
|
||||
|
||||
/// <summary>
|
||||
/// When you set the ProrationBehavior to create_prorations,
|
||||
/// Stripe will automatically create prorations for any changes made to the subscription,
|
||||
/// such as changing the plan, adding or removing quantities, or applying discounts.
|
||||
/// </summary>
|
||||
public const string CreateProrations = "create_prorations";
|
||||
|
||||
/// <summary>
|
||||
/// When you set the ProrationBehavior to always_invoice,
|
||||
/// Stripe will always generate an invoice when a subscription update occurs,
|
||||
/// regardless of whether there is a proration or not.
|
||||
/// </summary>
|
||||
public const string AlwaysInvoice = "always_invoice";
|
||||
}
|
||||
|
||||
public static class AuthConstants
|
||||
@ -117,6 +131,7 @@ public static class FeatureFlagKeys
|
||||
public const string FlexibleCollectionsMigration = "flexible-collections-migration";
|
||||
public const string AC1607_PresentUsersWithOffboardingSurvey = "AC-1607_present-user-offboarding-survey";
|
||||
public const string PM5766AutomaticTax = "PM-5766-automatic-tax";
|
||||
public const string PM5864DollarThreshold = "PM-5864-dollar-threshold";
|
||||
|
||||
public static List<string> GetAllKeys()
|
||||
{
|
||||
|
@ -230,7 +230,7 @@ public class StripePaymentService : IPaymentService
|
||||
null;
|
||||
var subscriptionUpdate = new SponsorOrganizationSubscriptionUpdate(existingPlan, sponsoredPlan, applySponsorship);
|
||||
|
||||
await FinalizeSubscriptionChangeAsync(org, subscriptionUpdate, DateTime.UtcNow);
|
||||
await FinalizeSubscriptionChangeAsync(org, subscriptionUpdate, DateTime.UtcNow, true);
|
||||
|
||||
var sub = await _stripeAdapter.SubscriptionGetAsync(org.GatewaySubscriptionId);
|
||||
org.ExpirationDate = sub.CurrentPeriodEnd;
|
||||
@ -743,12 +743,14 @@ public class StripePaymentService : IPaymentService
|
||||
return subItemOptions.Select(si => new Stripe.InvoiceSubscriptionItemOptions
|
||||
{
|
||||
Plan = si.Plan,
|
||||
Quantity = si.Quantity
|
||||
Price = si.Price,
|
||||
Quantity = si.Quantity,
|
||||
Id = si.Id
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private async Task<string> FinalizeSubscriptionChangeAsync(IStorableSubscriber storableSubscriber,
|
||||
SubscriptionUpdate subscriptionUpdate, DateTime? prorationDate)
|
||||
SubscriptionUpdate subscriptionUpdate, DateTime? prorationDate, bool invoiceNow = false)
|
||||
{
|
||||
// remember, when in doubt, throw
|
||||
var sub = await _stripeAdapter.SubscriptionGetAsync(storableSubscriber.GatewaySubscriptionId);
|
||||
@ -762,15 +764,37 @@ public class StripePaymentService : IPaymentService
|
||||
var daysUntilDue = sub.DaysUntilDue;
|
||||
var chargeNow = collectionMethod == "charge_automatically";
|
||||
var updatedItemOptions = subscriptionUpdate.UpgradeItemsOptions(sub);
|
||||
var isPm5864DollarThresholdEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5864DollarThreshold);
|
||||
|
||||
var subUpdateOptions = new Stripe.SubscriptionUpdateOptions
|
||||
{
|
||||
Items = updatedItemOptions,
|
||||
ProrationBehavior = "always_invoice",
|
||||
ProrationBehavior = !isPm5864DollarThresholdEnabled || invoiceNow
|
||||
? Constants.AlwaysInvoice
|
||||
: Constants.CreateProrations,
|
||||
DaysUntilDue = daysUntilDue ?? 1,
|
||||
CollectionMethod = "send_invoice",
|
||||
ProrationDate = prorationDate,
|
||||
};
|
||||
var immediatelyInvoice = false;
|
||||
if (!invoiceNow && isPm5864DollarThresholdEnabled)
|
||||
{
|
||||
var upcomingInvoiceWithChanges = await _stripeAdapter.InvoiceUpcomingAsync(new UpcomingInvoiceOptions
|
||||
{
|
||||
Customer = storableSubscriber.GatewayCustomerId,
|
||||
Subscription = storableSubscriber.GatewaySubscriptionId,
|
||||
SubscriptionItems = ToInvoiceSubscriptionItemOptions(updatedItemOptions),
|
||||
SubscriptionProrationBehavior = Constants.CreateProrations,
|
||||
SubscriptionProrationDate = prorationDate,
|
||||
SubscriptionBillingCycleAnchor = SubscriptionBillingCycleAnchor.Now
|
||||
});
|
||||
|
||||
immediatelyInvoice = upcomingInvoiceWithChanges.AmountRemaining >= 50000;
|
||||
|
||||
subUpdateOptions.BillingCycleAnchor = immediatelyInvoice
|
||||
? SubscriptionBillingCycleAnchor.Now
|
||||
: SubscriptionBillingCycleAnchor.Unchanged;
|
||||
}
|
||||
|
||||
var pm5766AutomaticTaxIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax);
|
||||
if (pm5766AutomaticTaxIsEnabled)
|
||||
@ -820,19 +844,21 @@ public class StripePaymentService : IPaymentService
|
||||
{
|
||||
try
|
||||
{
|
||||
if (chargeNow)
|
||||
if (!isPm5864DollarThresholdEnabled || immediatelyInvoice || invoiceNow)
|
||||
{
|
||||
paymentIntentClientSecret = await PayInvoiceAfterSubscriptionChangeAsync(
|
||||
storableSubscriber, invoice);
|
||||
}
|
||||
else
|
||||
{
|
||||
invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(subResponse.LatestInvoiceId, new Stripe.InvoiceFinalizeOptions
|
||||
if (chargeNow)
|
||||
{
|
||||
AutoAdvance = false,
|
||||
});
|
||||
await _stripeAdapter.InvoiceSendInvoiceAsync(invoice.Id, new Stripe.InvoiceSendOptions());
|
||||
paymentIntentClientSecret = null;
|
||||
paymentIntentClientSecret = await PayInvoiceAfterSubscriptionChangeAsync(storableSubscriber, invoice);
|
||||
}
|
||||
else
|
||||
{
|
||||
invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(subResponse.LatestInvoiceId, new Stripe.InvoiceFinalizeOptions
|
||||
{
|
||||
AutoAdvance = false,
|
||||
});
|
||||
await _stripeAdapter.InvoiceSendInvoiceAsync(invoice.Id, new Stripe.InvoiceSendOptions());
|
||||
paymentIntentClientSecret = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
@ -896,7 +922,7 @@ public class StripePaymentService : IPaymentService
|
||||
PurchasedAdditionalSecretsManagerServiceAccounts = newlyPurchasedAdditionalSecretsManagerServiceAccounts,
|
||||
PurchasedAdditionalStorage = newlyPurchasedAdditionalStorage
|
||||
}),
|
||||
prorationDate);
|
||||
prorationDate, true);
|
||||
|
||||
public Task<string> AdjustSeatsAsync(Organization organization, StaticStore.Plan plan, int additionalSeats, DateTime? prorationDate = null)
|
||||
{
|
||||
@ -1703,7 +1729,9 @@ public class StripePaymentService : IPaymentService
|
||||
public async Task<string> AddSecretsManagerToSubscription(Organization org, StaticStore.Plan plan, int additionalSmSeats,
|
||||
int additionalServiceAccount, DateTime? prorationDate = null)
|
||||
{
|
||||
return await FinalizeSubscriptionChangeAsync(org, new SecretsManagerSubscribeUpdate(org, plan, additionalSmSeats, additionalServiceAccount), prorationDate);
|
||||
return await FinalizeSubscriptionChangeAsync(org,
|
||||
new SecretsManagerSubscribeUpdate(org, plan, additionalSmSeats, additionalServiceAccount), prorationDate,
|
||||
true);
|
||||
}
|
||||
|
||||
public async Task<bool> RisksSubscriptionFailure(Organization organization)
|
||||
|
Loading…
Reference in New Issue
Block a user