mirror of
https://github.com/bitwarden/server.git
synced 2025-01-13 20:21:22 +01:00
[AC-1795] Provide extra subscription info when past due (#3950)
* Provide past due data on subscription * Add feature flag
This commit is contained in:
parent
48da6eba1c
commit
a048d6d9e3
@ -75,6 +75,10 @@ public class BillingSubscription
|
||||
{
|
||||
Items = sub.Items.Select(i => new BillingSubscriptionItem(i));
|
||||
}
|
||||
CollectionMethod = sub.CollectionMethod;
|
||||
SuspensionDate = sub.SuspensionDate;
|
||||
UnpaidPeriodEndDate = sub.UnpaidPeriodEndDate;
|
||||
GracePeriod = sub.GracePeriod;
|
||||
}
|
||||
|
||||
public DateTime? TrialStartDate { get; set; }
|
||||
@ -86,6 +90,10 @@ public class BillingSubscription
|
||||
public string Status { get; set; }
|
||||
public bool Cancelled { get; set; }
|
||||
public IEnumerable<BillingSubscriptionItem> Items { get; set; } = new List<BillingSubscriptionItem>();
|
||||
public string CollectionMethod { get; set; }
|
||||
public DateTime? SuspensionDate { get; set; }
|
||||
public DateTime? UnpaidPeriodEndDate { get; set; }
|
||||
public int? GracePeriod { get; set; }
|
||||
|
||||
public class BillingSubscriptionItem
|
||||
{
|
||||
|
@ -131,6 +131,7 @@ public static class FeatureFlagKeys
|
||||
public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email";
|
||||
public const string ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners";
|
||||
public const string EnableConsolidatedBilling = "enable-consolidated-billing";
|
||||
public const string AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section";
|
||||
|
||||
public static List<string> GetAllKeys()
|
||||
{
|
||||
|
@ -43,6 +43,9 @@ public class SubscriptionInfo
|
||||
Items = sub.Items.Data.Select(i => new BillingSubscriptionItem(i));
|
||||
}
|
||||
CollectionMethod = sub.CollectionMethod;
|
||||
GracePeriod = sub.CollectionMethod == "charge_automatically"
|
||||
? 14
|
||||
: 30;
|
||||
}
|
||||
|
||||
public DateTime? TrialStartDate { get; set; }
|
||||
@ -56,6 +59,9 @@ public class SubscriptionInfo
|
||||
public bool Cancelled { get; set; }
|
||||
public IEnumerable<BillingSubscriptionItem> Items { get; set; } = new List<BillingSubscriptionItem>();
|
||||
public string CollectionMethod { get; set; }
|
||||
public DateTime? SuspensionDate { get; set; }
|
||||
public DateTime? UnpaidPeriodEndDate { get; set; }
|
||||
public int GracePeriod { get; set; }
|
||||
|
||||
public class BillingSubscriptionItem
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Models.BitStripe;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
@ -16,6 +17,7 @@ public interface IStripeAdapter
|
||||
Task<Stripe.Invoice> InvoiceUpcomingAsync(Stripe.UpcomingInvoiceOptions options);
|
||||
Task<Stripe.Invoice> InvoiceGetAsync(string id, Stripe.InvoiceGetOptions options);
|
||||
Task<List<Stripe.Invoice>> InvoiceListAsync(StripeInvoiceListOptions options);
|
||||
Task<List<Stripe.Invoice>> InvoiceSearchAsync(InvoiceSearchOptions options);
|
||||
Task<Stripe.Invoice> InvoiceUpdateAsync(string id, Stripe.InvoiceUpdateOptions options);
|
||||
Task<Stripe.Invoice> InvoiceFinalizeInvoiceAsync(string id, Stripe.InvoiceFinalizeOptions options);
|
||||
Task<Stripe.Invoice> InvoiceSendInvoiceAsync(string id, Stripe.InvoiceSendOptions options);
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Models.BitStripe;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
@ -103,6 +104,9 @@ public class StripeAdapter : IStripeAdapter
|
||||
return invoices;
|
||||
}
|
||||
|
||||
public async Task<List<Stripe.Invoice>> InvoiceSearchAsync(InvoiceSearchOptions options)
|
||||
=> (await _invoiceService.SearchAsync(options)).Data;
|
||||
|
||||
public Task<Stripe.Invoice> InvoiceUpdateAsync(string id, Stripe.InvoiceUpdateOptions options)
|
||||
{
|
||||
return _invoiceService.UpdateAsync(id, options);
|
||||
|
@ -1603,10 +1603,25 @@ public class StripePaymentService : IPaymentService
|
||||
return subscriptionInfo;
|
||||
}
|
||||
|
||||
var sub = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId);
|
||||
var sub = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, new SubscriptionGetOptions
|
||||
{
|
||||
Expand = ["test_clock"]
|
||||
});
|
||||
|
||||
if (sub != null)
|
||||
{
|
||||
subscriptionInfo.Subscription = new SubscriptionInfo.BillingSubscription(sub);
|
||||
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AC1795_UpdatedSubscriptionStatusSection))
|
||||
{
|
||||
var (suspensionDate, unpaidPeriodEndDate) = await GetSuspensionDateAsync(sub);
|
||||
|
||||
if (suspensionDate.HasValue && unpaidPeriodEndDate.HasValue)
|
||||
{
|
||||
subscriptionInfo.Subscription.SuspensionDate = suspensionDate;
|
||||
subscriptionInfo.Subscription.UnpaidPeriodEndDate = unpaidPeriodEndDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sub is { CanceledAt: not null } || string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
||||
@ -1923,4 +1938,45 @@ public class StripePaymentService : IPaymentService
|
||||
? subscriberName
|
||||
: subscriberName[..30];
|
||||
}
|
||||
|
||||
private async Task<(DateTime?, DateTime?)> GetSuspensionDateAsync(Subscription subscription)
|
||||
{
|
||||
if (subscription.Status is not "past_due" && subscription.Status is not "unpaid")
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
var openInvoices = await _stripeAdapter.InvoiceSearchAsync(new InvoiceSearchOptions
|
||||
{
|
||||
Query = $"subscription:'{subscription.Id}' status:'open'"
|
||||
});
|
||||
|
||||
if (openInvoices.Count == 0)
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
var currentDate = subscription.TestClock?.FrozenTime ?? DateTime.UtcNow;
|
||||
|
||||
switch (subscription.CollectionMethod)
|
||||
{
|
||||
case "charge_automatically":
|
||||
{
|
||||
var firstOverdueInvoice = openInvoices
|
||||
.Where(invoice => invoice.PeriodEnd < currentDate && invoice.Attempted)
|
||||
.MinBy(invoice => invoice.Created);
|
||||
|
||||
return (firstOverdueInvoice?.Created.AddDays(14), firstOverdueInvoice?.PeriodEnd);
|
||||
}
|
||||
case "send_invoice":
|
||||
{
|
||||
var firstOverdueInvoice = openInvoices
|
||||
.Where(invoice => invoice.DueDate < currentDate)
|
||||
.MinBy(invoice => invoice.Created);
|
||||
|
||||
return (firstOverdueInvoice?.DueDate?.AddDays(30), firstOverdueInvoice?.PeriodEnd);
|
||||
}
|
||||
default: return (null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user