Skip to content

feat(payments-next): Add Free Trial section to Subscription Management page#20282

Open
elizabeth-ilina wants to merge 1 commit intomainfrom
PAY-3547-add-free-trial-section-to-subscription-management-page
Open

feat(payments-next): Add Free Trial section to Subscription Management page#20282
elizabeth-ilina wants to merge 1 commit intomainfrom
PAY-3547-add-free-trial-section-to-subscription-management-page

Conversation

@elizabeth-ilina
Copy link
Copy Markdown
Contributor

@elizabeth-ilina elizabeth-ilina commented Mar 27, 2026

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 pull request

  • 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 cancelling and resubscribing to a free trial
  • Implements UI for both Desktop and Mobile views
  • Adds mobile "Jump to" Free Trials nav link

Issue that this pull request solves

Closes #PAY-3547

Checklist

Put an x in the boxes that apply

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).
  • I have manually reviewed all AI generated code.

How to review (Optional)

  • Key files/areas to focus on:
  • Suggested review order:
  • Risky or complex parts:

Screenshots (Optional)

Tablet+ :

  • Trialing, with and without tax:
image image
  • Failed payment method, with and without tax:
image image
  • Trial cancelled:
image
  • Error resubscribing:
image
  • Error cancelling:
image

Mobile:
image

image image image image

No default payment method:
image

image

Other information (Optional)

Any other information that is important to this pull request.

@elizabeth-ilina elizabeth-ilina force-pushed the PAY-3547-add-free-trial-section-to-subscription-management-page branch 2 times, most recently from 9a03645 to a3f3dd4 Compare March 30, 2026 16:15
>
{l10n.getString(
'subscription-management-free-trial-heading',
'Free trials'
Copy link
Copy Markdown
Contributor Author

@elizabeth-ilina elizabeth-ilina Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with "Free trials", thanks!

@elizabeth-ilina elizabeth-ilina force-pushed the PAY-3547-add-free-trial-section-to-subscription-management-page branch 5 times, most recently from 2c7b6a5 to c0057cd Compare March 30, 2026 19:02
@elizabeth-ilina elizabeth-ilina force-pushed the PAY-3547-add-free-trial-section-to-subscription-management-page branch from c0057cd to bfeffa3 Compare March 30, 2026 19:27
@elizabeth-ilina elizabeth-ilina marked this pull request as ready for review March 30, 2026 19:55
@elizabeth-ilina elizabeth-ilina requested review from a team as code owners March 30, 2026 19:55
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}.`,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor

@xlisachan xlisachan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with "Free trials", thanks!

const isPastDue = subscription.status === 'past_due';
const latestInvoiceId = subscription.latest_invoice;

const upcomingInvoice =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.'}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ? (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@elizabeth-ilina elizabeth-ilina force-pushed the PAY-3547-add-free-trial-section-to-subscription-management-page branch from bfeffa3 to cafded4 Compare April 6, 2026 19:22
…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)
@elizabeth-ilina elizabeth-ilina force-pushed the PAY-3547-add-free-trial-section-to-subscription-management-page branch from cafded4 to 2fcbac2 Compare April 6, 2026 19:43
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Charge info strings - no tax, per interval
# Charge info strings - no tax, per interval

Comment on lines +36 to +39
## $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 }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## $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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## $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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants