feat(payments-next): Add Free Trial section to Subscription Management page#20282
feat(payments-next): Add Free Trial section to Subscription Management page#20282elizabeth-ilina wants to merge 1 commit intomainfrom
Conversation
9a03645 to
a3f3dd4
Compare
libs/payments/management/src/lib/subscriptionManagement.service.ts
Outdated
Show resolved
Hide resolved
| > | ||
| {l10n.getString( | ||
| 'subscription-management-free-trial-heading', | ||
| 'Free trials' |
There was a problem hiding this comment.
To match the rest of the page, where headings are plural like Payment details, Active subscriptions, I wrote "Free trials" (with -s at the end), while Figma has "Free trial". Which is better?
There was a problem hiding this comment.
Let's go with "Free trials", thanks!
2c7b6a5 to
c0057cd
Compare
c0057cd to
bfeffa3
Compare
| case 'daily': | ||
| return { | ||
| ftlId: 'free-trial-content-charge-info-with-tax-day', | ||
| fallbackText: `You will be charged ${amount} + ${tax} tax per day after the free trial ends on ${date}.`, |
There was a problem hiding this comment.
Question: Figma had (what I assume) just total/monthly, not broken up into totalWithoutTax + tax like we do in other places. Should we display just one number, as per Figma, or break it up into totalWithoutTax + tax?
xlisachan
left a comment
There was a problem hiding this comment.
Looks great, Liz!
I came across a scenario where the Subscription Management page did not load after the payment method was removed. It would be an edge case, as Support would have to do this, but we should handle it just in case.
See comments below and let me know if you have any questions. Thanks!
| > | ||
| {l10n.getString( | ||
| 'subscription-management-free-trial-heading', | ||
| 'Free trials' |
There was a problem hiding this comment.
Let's go with "Free trials", thanks!
| const isPastDue = subscription.status === 'past_due'; | ||
| const latestInvoiceId = subscription.latest_invoice; | ||
|
|
||
| const upcomingInvoice = |
There was a problem hiding this comment.
When testing the scenario where a customer had an active free trial, but no default payment method, the Subscription Management page crashed as Stripe could not generate an upcoming invoice (The subscription's trial_settings.end_behavior.missing_payment_method is set to cancel the subscription since there is no default payment method).
Suggestion:
// Stripe throws invoice_upcoming_none when a trialing subscription
// has no payment method and will cancel at trial end — skip the call.
const willCancelAtTrialEnd =
subscription.status === 'trialing' &&
subscription.trial_settings?.end_behavior?.missing_payment_method ===
'cancel' &&
!subscription.default_payment_method;
const upcomingInvoice = willCancelAtTrialEnd
? undefined
: await this.invoiceManager.previewUpcomingSubscription({
customer,
subscription,
});
No need to throw if there is no upcoming invoice for free trials.
| id="free-trial-content-trial-ends" | ||
| vars={{ date: trialEndDateFallback }} | ||
| > | ||
| <p className="text-sm">{`Your free trial ends on ${trialEndDateFallback}.`}</p> |
There was a problem hiding this comment.
Suggestion (non-blocking): Your free trial ends on {trialEndDateFallback}. Update your payment method to keep access after your free trial.
| <p className="text-sm text-yellow-800"> | ||
| {trialEndDateFallback | ||
| ? `Your free trial expires on ${trialEndDateFallback}.` | ||
| : 'Your free trial has been cancelled.'} |
There was a problem hiding this comment.
Separate ftl ids would be needed for these two strings: Your free trial expires on ${trialEndDateFallback}. and Your free trial has been cancelled.
| <LinkExternal | ||
| href={updatePaymentUrl} | ||
| className="border box-border flex font-bold font-header h-10 items-center justify-center rounded-md py-2 px-5 bg-blue-500 hover:bg-blue-700 text-white min-w-[212px] tablet:w-auto" | ||
| aria-label={l10n.getString( |
There was a problem hiding this comment.
We would not need this aria-label for this button, as payment methods cannot be assigned to a product/subscription (it would just be updating the default payment method.
| services. | ||
| </> | ||
| ) : ( | ||
| 'We were unable to process your payment to start your subscription. Please update your payment method to activate your subscription and restore access to your services.' |
There was a problem hiding this comment.
Separate ftls ids would be needed for these two strings.
|
|
||
| <div className="mt-3 tablet:mt-0 flex w-full flex-col tablet:flex-row tablet:justify-end gap-3"> | ||
| <div className="ms-auto tablet:w-auto"> | ||
| {isCancelled ? ( |
There was a problem hiding this comment.
Suggestion (non-blocking):
Figma positioned these buttons like this; however, I believe we should be consistent with the buttons in the other sections (Payment methods, Active Subscriptions), which are the full-width in mobile view.
bfeffa3 to
cafded4
Compare
…t page Because: * We need to add a Free Trials section to the Sub Manage page so that a customer can see what free trials they have. This commit: * Adds a new Free Trial section and component, to be rendered on the Subscription Management page, akin to “Payment details” and “Active subscriptions”, that is only rendered if a user has an active trialing subscription. * If the free trial is active, includes details about their upcoming billing cycle, and the option to cancel their free trial * If the free trial could not be converted due to a failed payment method, includes invoice details, and the option to update their payment info * Implements UI for both Desktop and Mobile views Closes #[PAY-3547](https://mozilla-hub.atlassian.net/browse/PAY-3547)
cafded4 to
2fcbac2
Compare
| free-trial-content-trial-expires = Your free trial expires on { $date }. | ||
| free-trial-content-trial-cancelled = Your free trial has been cancelled. | ||
|
|
||
| # Charge info strings - with tax, per interval |
There was a problem hiding this comment.
| # Charge info strings - with tax, per interval | |
| # Charge info strings - with tax, per interval | |
For internal facing comments you can separate with a newline to avoid exposing to localizers. Otherwise this would act as an individual comment only for the string immediately below it.
| free-trial-content-charge-info-with-tax-year = You will be charged { $amount } + { $tax } tax per year after the free trial ends on { $date }. | ||
| free-trial-content-charge-info-with-tax-default = You will be charged { $amount } + { $tax } tax after the free trial ends on { $date }. | ||
|
|
||
| # Charge info strings - no tax, per interval |
There was a problem hiding this comment.
| # Charge info strings - no tax, per interval | |
| # Charge info strings - no tax, per interval | |
| ## $billedOnDate (Date) - The date of the last bill (e.g., July 20, 2025) | ||
| ## $invoiceTotal (Number) - The invoice total amount excluding tax. It will be formatted as currency. | ||
| ## $taxDue (Number) - The tax amount. It will be formatted as currency. | ||
| free-trial-content-last-bill = Last bill • { $billedOnDate } |
There was a problem hiding this comment.
| ## $billedOnDate (Date) - The date of the last bill (e.g., July 20, 2025) | |
| ## $invoiceTotal (Number) - The invoice total amount excluding tax. It will be formatted as currency. | |
| ## $taxDue (Number) - The tax amount. It will be formatted as currency. | |
| free-trial-content-last-bill = Last bill • { $billedOnDate } | |
| ## $billedOnDate (Date) - The date of the last bill (e.g., July 20, 2025) | |
| ## $invoiceTotal (Number) - The invoice total amount excluding tax. It will be formatted as currency. | |
| ## $taxDue (Number) - The tax amount. It will be formatted as currency. | |
| free-trial-content-last-bill = Last bill • { $billedOnDate } |
Group comments need to be followed by an empty newline.
| free-trial-content-last-bill = Last bill • { $billedOnDate } | ||
| free-trial-content-last-bill-with-tax = { $invoiceTotal } + { $taxDue } tax | ||
| free-trial-content-last-bill-no-tax = { $invoiceTotal } | ||
| free-trial-content-link-view-invoice = View invoice |
There was a problem hiding this comment.
| free-trial-content-link-view-invoice = View invoice | |
| ## | |
| free-trial-content-link-view-invoice = View invoice |
This closes the group comment to ensure the comments to appear for unrelated strings.
| free-trial-content-last-bill-with-tax = { $invoiceTotal } + { $taxDue } tax | ||
| free-trial-content-last-bill-no-tax = { $invoiceTotal } | ||
| free-trial-content-link-view-invoice = View invoice | ||
| free-trial-content-link-view-invoice-aria = View invoice for { $productName } |
There was a problem hiding this comment.
| free-trial-content-link-view-invoice-aria = View invoice for { $productName } | |
| # $productName (String) - The name of the subscribed product, e.g. Mozilla VPN | |
| free-trial-content-link-view-invoice-aria = View invoice for { $productName } |
Adds an individual comment for this string's variable.
| free-trial-content-last-bill-no-tax = { $invoiceTotal } | ||
| free-trial-content-link-view-invoice = View invoice | ||
| free-trial-content-link-view-invoice-aria = View invoice for { $productName } | ||
| ## $date (Date) - The date the free trial ended (e.g., January 16, 2026) |
There was a problem hiding this comment.
| ## $date (Date) - The date the free trial ended (e.g., January 16, 2026) | |
| # $date (Date) - The date the free trial ended (e.g., January 16, 2026) |
This is only pertinent to this string, so let's make this an individual comment.
Because
This pull request
Issue that this pull request solves
Closes #PAY-3547
Checklist
Put an
xin the boxes that applyHow to review (Optional)
Screenshots (Optional)
Tablet+ :
Mobile:

No default payment method:

Other information (Optional)
Any other information that is important to this pull request.