Skip to content
32 changes: 25 additions & 7 deletions src/Exceptionless.Web/Api/Handlers/OrganizationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,11 +465,8 @@ public async Task<Result<ChangePlanResult>> Handle(ChangeOrganizationPlan messag
organization.CardLast4 = model.Last4;
await repository.SaveAsync(organization, o => o.ImmediateConsistency().Cache());

var subscriptionOptions = new SubscriptionCreateOptions
{
Customer = customer.Id,
Items = [new SubscriptionItemOptions { Price = model.PlanId }]
};
var subscriptionOptions = CreateMonthAlignedSubscriptionOptions(customer.Id);
subscriptionOptions.Items.Add(new SubscriptionItemOptions { Price = model.PlanId });

if (isPaymentMethod)
subscriptionOptions.DefaultPaymentMethod = model.StripeToken;
Expand All @@ -485,8 +482,12 @@ public async Task<Result<ChangePlanResult>> Handle(ChangeOrganizationPlan messag
}
else
{
var update = new SubscriptionUpdateOptions { Items = [] };
var create = new SubscriptionCreateOptions { Customer = organization.StripeCustomerId, Items = [] };
var update = new SubscriptionUpdateOptions
{
Items = [],
ProrationBehavior = "create_prorations"
};
var create = CreateMonthAlignedSubscriptionOptions(organization.StripeCustomerId);
bool cardUpdated = false;

var customerUpdateOptions = new CustomerUpdateOptions { Description = organization.Name };
Expand Down Expand Up @@ -625,6 +626,23 @@ public async Task<Result<ChangePlanResult>> Handle(ChangeOrganizationPlan messag
return new ChangePlanResult { Success = true };
}

private static SubscriptionCreateOptions CreateMonthAlignedSubscriptionOptions(string customerId)
{
return new SubscriptionCreateOptions
{
Customer = customerId,
Items = [],
BillingCycleAnchorConfig = new SubscriptionBillingCycleAnchorConfigOptions
{
DayOfMonth = 1,
Hour = 0,
Minute = 0,
Second = 0
},
ProrationBehavior = "create_prorations"
};
}

public async Task<Result<User>> Handle(AddOrganizationUser message)
{
if (String.IsNullOrEmpty(message.Id) || !message.Context.Request.CanAccessOrganization(message.Id) || String.IsNullOrEmpty(message.Email))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,12 @@ public async Task ChangePlanAsync_WithNewCustomer_CreatesStripeCustomerAndSubscr
var item = Assert.Single(subscription.Items);
Assert.Equal(_plans.SmallPlan.Id, item.Price);
Assert.Equal("coupon_10", Assert.Single(subscription.Discounts).Coupon);
Assert.Equal("create_prorations", subscription.ProrationBehavior);
Assert.NotNull(subscription.BillingCycleAnchorConfig);
Assert.Equal(1, subscription.BillingCycleAnchorConfig.DayOfMonth);
Assert.Equal(0, subscription.BillingCycleAnchorConfig.Hour);
Assert.Equal(0, subscription.BillingCycleAnchorConfig.Minute);
Assert.Equal(0, subscription.BillingCycleAnchorConfig.Second);

var organization = await _organizationRepository.GetByIdAsync(SampleDataService.FREE_ORG_ID);
Assert.NotNull(organization);
Expand Down Expand Up @@ -1512,6 +1518,7 @@ public async Task ChangePlanAsync_WithExistingCustomer_UpdatesPaymentMethodAndSu
Assert.Equal("si_active", item.Id);
Assert.Equal(_plans.SmallPlan.Id, item.Price);
Assert.Equal("coupon_10", Assert.Single(updatedSubscription.Options.Discounts).Coupon);
Assert.Equal("create_prorations", updatedSubscription.Options.ProrationBehavior);
Assert.Empty(StripeBillingClient.CreatedSubscriptionOptions);

var organization = await _organizationRepository.GetByIdAsync(SampleDataService.FREE_ORG_ID);
Expand All @@ -1523,6 +1530,40 @@ public async Task ChangePlanAsync_WithExistingCustomer_UpdatesPaymentMethodAndSu
Assert.Equal(BillingStatus.Active, organization.BillingStatus);
}

[Fact]
public async Task ChangePlanAsync_ExistingCustomerWithoutSubscriptionCreatesMonthAlignedSubscription()
{
await SetStripeCustomerIdAsync(SampleDataService.FREE_ORG_ID, "cus_existing");

var result = await WithBillingEnabledAsync(() =>
SendRequestAsAsync<ChangePlanResult>(r => r
.AsFreeOrganizationUser()
.Post()
.AppendPaths("organizations", SampleDataService.FREE_ORG_ID, "change-plan")
.Content(new ChangePlanRequest
{
PlanId = _plans.SmallPlan.Id,
StripeToken = "pm_card_visa",
Last4 = "4242"
})
.StatusCodeShouldBeOk()
));

Assert.NotNull(result);
Assert.True(result.Success, result.Message);

var subscription = Assert.Single(StripeBillingClient.CreatedSubscriptionOptions);
Assert.Equal("cus_existing", subscription.Customer);
Assert.Equal(_plans.SmallPlan.Id, Assert.Single(subscription.Items).Price);
Assert.Equal("create_prorations", subscription.ProrationBehavior);
Assert.NotNull(subscription.BillingCycleAnchorConfig);
Assert.Equal(1, subscription.BillingCycleAnchorConfig.DayOfMonth);
Assert.Equal(0, subscription.BillingCycleAnchorConfig.Hour);
Assert.Equal(0, subscription.BillingCycleAnchorConfig.Minute);
Assert.Equal(0, subscription.BillingCycleAnchorConfig.Second);
Assert.Empty(StripeBillingClient.UpdatedSubscriptions);
}

[Fact]
public async Task ChangePlanAsync_WithFreePlan_CancelsActiveStripeSubscriptions()
{
Expand Down
Loading