[web] fix bug where pending downgrades are removed when subscriptions change (#30420)

* preserve pending changes when generating change requests
* re-apply pending term_end changes after immediate updates
* block changes when Stripe subscription has multiple phases
* handle MultiplePendingChangesError & rm PendingChangeError

GitOrigin-RevId: 0af11044766ff48e683d684ad6d62b732d17290c
This commit is contained in:
Kristina
2026-02-03 09:05:55 +00:00
committed by Copybot
parent ee4b5f515c
commit 4c5cdecffa
19 changed files with 1179 additions and 129 deletions
@@ -6,6 +6,7 @@ import {
annualActiveSubscription,
annualActiveSubscriptionEuro,
annualActiveSubscriptionPro,
pendingAddOnChange,
pendingSubscriptionChange,
} from '../../../../../fixtures/subscriptions'
import { ActiveSubscription } from '../../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
@@ -69,6 +70,17 @@ describe('<ChangePlanModal />', function () {
screen.getByRole('button', { name: 'Keep my current plan' })
})
it('renders "Your plan" when there is a pending add-on change but no plan change', async function () {
renderActiveSubscription(pendingAddOnChange)
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
await screen.findByText('Your plan')
expect(screen.queryByRole('button', { name: 'Keep my current plan' })).to.be
.null
})
it('does not render when Recurly did not load', function () {
const { container } = renderWithSubscriptionDashContext(
<ActiveSubscription subscription={annualActiveSubscription} />,
@@ -409,6 +409,63 @@ export const canceledSubscription: PaidSubscription = {
},
}
export const pendingAddOnChange: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],
invited_emails: [],
groupPlan: false,
membersLimit: 0,
_id: 'add-on-change-123',
admin_id: 'abc123',
teamInvites: [],
planCode: 'collaborator-annual',
plan: {
planCode: 'collaborator-annual',
name: 'Standard (Collaborator) Annual',
price_in_cents: 21900,
annual: true,
featureDescription: [],
canUseFlexibleLicensing: false,
},
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/payment/billing-details',
accountManagementLink: '/user/subscription/payment/account-management',
additionalLicenses: 0,
totalLicenses: 0,
nextPaymentDueAt,
nextPaymentDueDate,
currency: 'USD',
state: 'active',
trialEndsAtFormatted: null,
trialEndsAt: null,
activeCoupons: [],
accountEmail: 'fake@example.com',
hasPastDueInvoice: false,
displayPrice: '$199.00',
planOnlyDisplayPrice: '$199.00',
addOns: [
{
code: 'AI',
quantity: 1,
unitPrice: 1000,
name: 'AI Add-on',
},
],
addOnDisplayPricesWithoutAdditionalLicense: {},
isEligibleForGroupPlan: true,
isEligibleForPause: false,
isEligibleForDowngradeUpsell: false,
},
pendingPlan: {
planCode: 'collaborator-annual',
name: 'Standard (Collaborator) Annual',
price_in_cents: 21900,
annual: true,
featureDescription: [],
},
}
export const pendingSubscriptionChange: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],