From 512c3d9935f0f364abcc9bd1c1fe4deb4580e6dc Mon Sep 17 00:00:00 2001 From: Maxime BV Date: Mon, 31 Aug 2026 17:27:05 +0200 Subject: [PATCH 1/4] customer-portal: require approval for reset proration on same-interval plan changes --- .../CustomerPortal/CustomerChangePlanModal.tsx | 9 ++++++--- .../CustomerPortal/CustomerSeatQuantityManager.tsx | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx b/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx index 455525645b4..f189009577f 100644 --- a/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx +++ b/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx @@ -160,11 +160,13 @@ const CustomerChangePlanModal = ({ switch (prorationBehavior) { case 'invoice': - return "I'll be charged immediately with a proration for the current month." + return "I'll be charged immediately, with a proration for the current period." case 'prorate': - return 'Your next invoice will include the new plan plus the proration for the current month.' + return 'Your next invoice will include the new plan plus the proration for the current period.' case 'next_period': return 'The new plan will be applied on your next billing cycle.' + case 'reset': + return "I'll be charged the full amount for the new plan immediately, and my billing period restarts today." } }, [ selectedProduct, @@ -177,7 +179,8 @@ const CustomerChangePlanModal = ({ const willIssueInvoice = trialOutcome?.kind === 'ends' || willTriggerImmediateCycle || - prorationBehavior === 'invoice' + prorationBehavior === 'invoice' || + prorationBehavior === 'reset' const [approveImmediateInvoice, setApproveImmediateInvoice] = useState(false) const canChangePlan = useMemo(() => { diff --git a/clients/apps/web/src/components/CustomerPortal/CustomerSeatQuantityManager.tsx b/clients/apps/web/src/components/CustomerPortal/CustomerSeatQuantityManager.tsx index d8c760da207..028d1dfa8c7 100644 --- a/clients/apps/web/src/components/CustomerPortal/CustomerSeatQuantityManager.tsx +++ b/clients/apps/web/src/components/CustomerPortal/CustomerSeatQuantityManager.tsx @@ -47,11 +47,13 @@ export const CustomerSeatQuantityManager = ({ if (!prorationBehavior) return null switch (prorationBehavior) { case 'invoice': - return "I'll be charged immediately with a proration for the current month." + return "You'll be charged immediately, with a proration for the current period." case 'prorate': - return 'Your next invoice will include the updated seats plus the proration for the current month.' + return 'Your next invoice will include the updated seats plus the proration for the current period.' case 'next_period': return 'The seat update will be applied on your next billing cycle.' + case 'reset': + return "You'll be charged the full new amount immediately, and your billing period restarts today." } }, [prorationBehavior]) From 3f8c4b1c0833e3a3e7a13a88e2231e339415717f Mon Sep 17 00:00:00 2001 From: Maxime BV Date: Mon, 31 Aug 2026 17:51:37 +0200 Subject: [PATCH 2/4] fix: don't require invoice approval during a trial --- .../src/components/CustomerPortal/CustomerChangePlanModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx b/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx index f189009577f..25a08965cf1 100644 --- a/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx +++ b/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx @@ -179,8 +179,8 @@ const CustomerChangePlanModal = ({ const willIssueInvoice = trialOutcome?.kind === 'ends' || willTriggerImmediateCycle || - prorationBehavior === 'invoice' || - prorationBehavior === 'reset' + (!isTrialing && + (prorationBehavior === 'invoice' || prorationBehavior === 'reset')) const [approveImmediateInvoice, setApproveImmediateInvoice] = useState(false) const canChangePlan = useMemo(() => { From aadb8e511d0cfa7d0b554721502ece332966a1bb Mon Sep 17 00:00:00 2001 From: Maxime BV Date: Tue, 1 Sep 2026 08:18:41 +0200 Subject: [PATCH 3/4] customer-portal: remove the immediate-charge confirmation --- .../CustomerChangePlanModal.tsx | 45 ++++--------------- 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx b/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx index 25a08965cf1..6da1dcf78ed 100644 --- a/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx +++ b/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx @@ -10,7 +10,6 @@ import { formatTrialEnd, useTrialChangeOutcome } from '@/utils/trial-change' import { Client, schemas } from '@polar-sh/client' import { Button } from '@polar-sh/orbit' import { List, ListItem } from '@polar-sh/orbit' -import { Checkbox } from '@polar-sh/orbit' import { useRouter } from 'next/navigation' import { useCallback, useMemo, useState } from 'react' import { resolveBenefitIcon } from '../Benefit/utils' @@ -152,21 +151,21 @@ const CustomerChangePlanModal = ({ selectedProduct.recurring_interval === 'month' ? 'monthly' : 'yearly' if (nextInvoiceType === 'charge') { - return `I'll be charged immediately for the new ${newPeriod} plan.` + return `You'll be charged immediately for the new ${newPeriod} plan.` } else { - return `My previous payment will appear as a credit on my next invoice.` + return `Your previous payment will appear as a credit on your next invoice.` } } switch (prorationBehavior) { case 'invoice': - return "I'll be charged immediately, with a proration for the current period." + return "You'll be charged immediately, with a proration for the current period." case 'prorate': return 'Your next invoice will include the new plan plus the proration for the current period.' case 'next_period': return 'The new plan will be applied on your next billing cycle.' case 'reset': - return "I'll be charged the full amount for the new plan immediately, and my billing period restarts today." + return "You'll be charged the full amount for the new plan immediately, and your billing period restarts today." } }, [ selectedProduct, @@ -176,32 +175,17 @@ const CustomerChangePlanModal = ({ trialOutcome, ]) - const willIssueInvoice = - trialOutcome?.kind === 'ends' || - willTriggerImmediateCycle || - (!isTrialing && - (prorationBehavior === 'invoice' || prorationBehavior === 'reset')) - const [approveImmediateInvoice, setApproveImmediateInvoice] = useState(false) - const canChangePlan = useMemo(() => { if (!selectedProduct) return false const isSamePlan = selectedProduct?.id === subscription.product_id if (isSamePlan) return false - if (willIssueInvoice && !approveImmediateInvoice) return false - const selectedPlanIsFree = selectedProduct?.prices.some(isFreePrice) if (selectedPlanIsFree) return true return hasPaymentMethod - }, [ - hasPaymentMethod, - selectedProduct, - subscription, - willIssueInvoice, - approveImmediateInvoice, - ]) + }, [hasPaymentMethod, selectedProduct, subscription]) const updateSubscription = useCustomerUpdateSubscription(api) const onConfirm = useCallback(async () => { @@ -321,22 +305,9 @@ const CustomerChangePlanModal = ({ )} {invoicingMessage && ( - + + {invoicingMessage} + )} {needToAddPaymentMethod && ( From 127ea03ed555ffee56d9397d3a32ca1972b9d16d Mon Sep 17 00:00:00 2001 From: Maxime BV Date: Tue, 1 Sep 2026 08:22:29 +0200 Subject: [PATCH 4/4] customer-portal: make the proration message switch exhaustive --- .../src/components/CustomerPortal/CustomerChangePlanModal.tsx | 2 +- .../components/CustomerPortal/CustomerSeatQuantityManager.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx b/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx index 6da1dcf78ed..e2287ec5f9e 100644 --- a/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx +++ b/clients/apps/web/src/components/CustomerPortal/CustomerChangePlanModal.tsx @@ -135,7 +135,7 @@ const CustomerChangePlanModal = ({ return [willTrigger, chargeOrCredit] }, [selectedProduct, prorationBehavior, subscription, isTrialing]) - const invoicingMessage = useMemo(() => { + const invoicingMessage = useMemo((): string | null => { if (!selectedProduct) return null if (trialOutcome?.kind === 'continues') { diff --git a/clients/apps/web/src/components/CustomerPortal/CustomerSeatQuantityManager.tsx b/clients/apps/web/src/components/CustomerPortal/CustomerSeatQuantityManager.tsx index 028d1dfa8c7..b8f6ba44c86 100644 --- a/clients/apps/web/src/components/CustomerPortal/CustomerSeatQuantityManager.tsx +++ b/clients/apps/web/src/components/CustomerPortal/CustomerSeatQuantityManager.tsx @@ -43,7 +43,7 @@ export const CustomerSeatQuantityManager = ({ const canDecrease = seats !== undefined && seats > assignedSeats const hasChanges = seats !== totalSeats - const invoicingMessage = useMemo(() => { + const invoicingMessage = useMemo((): string | null => { if (!prorationBehavior) return null switch (prorationBehavior) { case 'invoice':