Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Api/Models/Response/BillingCustomerDiscount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ public class BillingCustomerDiscount
/// </summary>
public IReadOnlyList<string>? AppliesTo { get; }

/// <summary>
/// True when this discount was surfaced from a price-migration schedule's Phase 2 coupon
/// rather than a genuine customer- or subscription-level discount. Lets clients distinguish
/// a deferred price-migration coupon from a real discount.
/// </summary>
public bool IsFromSchedule { get; }

/// <summary>
/// Creates a BillingCustomerDiscount from a SubscriptionInfo.BillingCustomerDiscount.
/// </summary>
Expand All @@ -73,5 +80,6 @@ public BillingCustomerDiscount(SubscriptionInfo.BillingCustomerDiscount discount
End = discount.End;
DurationInMonths = discount.DurationInMonths;
AppliesTo = discount.AppliesTo;
IsFromSchedule = discount.IsFromSchedule;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -249,42 +249,54 @@ public class PreviewOrganizationTaxCommand(
Quantity = quantity
});

var hasStorage =
subscriptionItemsByPriceId.TryGetValue(newPlan.PasswordManager.StripeStoragePlanId,
out var storage);

if (hasStorage && storage != null)
// Match existing storage by the CURRENT plan's id (as PM seats/SM do), then re-price at the
// new plan — storage ids can differ across the change (e.g. Families' personal-storage vs an
// org plan's shared storage). Guard: some plans have no storage add-on, so the id can be null.
if (!string.IsNullOrEmpty(currentPlan.PasswordManager.StripeStoragePlanId) &&
!string.IsNullOrEmpty(newPlan.PasswordManager.StripeStoragePlanId))
{
items.Add(new InvoiceSubscriptionDetailsItemOptions
var hasStorage =
subscriptionItemsByPriceId.TryGetValue(currentPlan.PasswordManager.StripeStoragePlanId,
out var storage);

if (hasStorage && storage != null)
{
Price = newPlan.PasswordManager.StripeStoragePlanId,
Quantity = storage.Quantity
});
items.Add(new InvoiceSubscriptionDetailsItemOptions
{
Price = newPlan.PasswordManager.StripeStoragePlanId,
Quantity = storage.Quantity
});
}
}

var hasSecretsManagerSeats = subscriptionItemsByPriceId.TryGetValue(
newPlan.SecretsManager.StripeSeatPlanId,
out var secretsManagerSeats);

if (hasSecretsManagerSeats && secretsManagerSeats != null)
// Match existing SM items by the CURRENT plan's ids (as PM seats/storage do above), then re-price
// at the new plan. Guard: SecretsManager is null for Families/Free (PlanAdapter maps it to null).
if (currentPlan.SecretsManager != null && newPlan.SecretsManager != null)
{
items.Add(new InvoiceSubscriptionDetailsItemOptions
{
Price = newPlan.SecretsManager.StripeSeatPlanId,
Quantity = secretsManagerSeats.Quantity
});

var hasServiceAccounts =
subscriptionItemsByPriceId.TryGetValue(newPlan.SecretsManager.StripeServiceAccountPlanId,
out var serviceAccounts);
var hasSecretsManagerSeats = subscriptionItemsByPriceId.TryGetValue(
currentPlan.SecretsManager.StripeSeatPlanId,
out var secretsManagerSeats);

if (hasServiceAccounts && serviceAccounts != null)
if (hasSecretsManagerSeats && secretsManagerSeats != null)
{
items.Add(new InvoiceSubscriptionDetailsItemOptions
{
Price = newPlan.SecretsManager.StripeServiceAccountPlanId,
Quantity = serviceAccounts.Quantity
Price = newPlan.SecretsManager.StripeSeatPlanId,
Quantity = secretsManagerSeats.Quantity
});

var hasServiceAccounts =
subscriptionItemsByPriceId.TryGetValue(currentPlan.SecretsManager.StripeServiceAccountPlanId,
out var serviceAccounts);

if (hasServiceAccounts && serviceAccounts != null)
{
items.Add(new InvoiceSubscriptionDetailsItemOptions
{
Price = newPlan.SecretsManager.StripeServiceAccountPlanId,
Quantity = serviceAccounts.Quantity
});
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,7 @@ private async Task ApplySchedulePhase2DataAsync(Subscription subscription, Subsc
if (phase2Discount?.Coupon != null)
{
subscriptionInfo.CustomerDiscount = new SubscriptionInfo.BillingCustomerDiscount(phase2Discount.Coupon);
subscriptionInfo.CustomerDiscount.IsFromSchedule = true;
}
}
catch (StripeException ex)
Expand Down
7 changes: 7 additions & 0 deletions src/Core/Models/Business/SubscriptionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ public BillingCustomerDiscount(Coupon coupon)
/// </para>
/// </summary>
public IReadOnlyList<string>? AppliesTo { get; set; }

/// <summary>
/// True when this discount was surfaced from a price-migration schedule's Phase 2 coupon
/// rather than a genuine customer- or subscription-level discount. Lets clients distinguish
/// a deferred price-migration coupon from a real discount. Defaults to false.
/// </summary>
public bool IsFromSchedule { get; set; }
}

public class BillingSubscription
Expand Down
Loading
Loading