diff --git a/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/en.ftl b/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/en.ftl index 12657f864fb..6a7e0614efa 100644 --- a/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/en.ftl +++ b/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/en.ftl @@ -8,6 +8,7 @@ subscriptionRenewalReminder-content-greeting = Dear { $productName } customer, # Variables # $reminderLength (String) - The number of days until the current subscription is set to automatically renew, e.g. 14 subscriptionRenewalReminder-content-intro = Your current subscription is set to automatically renew in { $reminderLength } days. +subscriptionRenewalReminder-content-discount-change = Your next invoice reflects a change in pricing, as a previous discount has ended and a new discount has been applied. subscriptionRenewalReminder-content-discount-ending = Because a previous discount has ended, your subscription will renew at the standard price. # Variables # $invoiceTotal (String) - The amount of the subscription invoice, including currency, e.g. $10.00 diff --git a/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.mjml b/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.mjml index 9238e2016f1..36f8a49d008 100644 --- a/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.mjml +++ b/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.mjml @@ -24,6 +24,12 @@ Because a previous discount has ended, your subscription will renew at the standard price. + <% } else if (locals.hasDifferentDiscount) { %> + + + Your next invoice reflects a change in pricing, as a previous discount has ended and a new discount has been applied. + + <% } %> diff --git a/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.stories.ts b/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.stories.ts index 4ecb3b0065a..f6ab9097e6a 100644 --- a/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.stories.ts +++ b/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.stories.ts @@ -19,6 +19,7 @@ const data = { subscriptionSupportUrl: 'http://localhost:3030/support', updateBillingUrl: 'http://localhost:3030/subscriptions', hadDiscount: false, + hasDifferentDiscount: false, }; const createStory = subplatStoryWithProps( @@ -58,6 +59,28 @@ export const YearlyPlanDiscountEnding = createStory( reminderLength: '15', invoiceTotal: '$199.99', hadDiscount: true, + hasDifferentDiscount: false, }, 'Yearly Plan - Discount Ending' ); + +export const MonthlyPlanDiscountChanging = createStory( + { + hadDiscount: true, + hasDifferentDiscount: true, + invoiceTotal: '$14.00', + }, + 'Monthly Plan - Discount Changing' +); + +export const YearlyPlanDiscountChanging = createStory( + { + planInterval: 'year', + planIntervalCount: '1', + reminderLength: '15', + invoiceTotal: '$139.99', + hadDiscount: true, + hasDifferentDiscount: true, + }, + 'Yearly Plan - Discount Changing' +); diff --git a/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.ts b/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.ts index 6a6d1c68f63..2cf0db296fa 100644 --- a/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.ts +++ b/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.ts @@ -15,10 +15,11 @@ export type TemplateData = SubscriptionSupportContactTemplateData & subscriptionSupportUrl: string; updateBillingUrl: string; hadDiscount?: boolean; + hasDifferentDiscount?: boolean; }; export const template = 'subscriptionRenewalReminder'; -export const version = 3; +export const version = 4; export const layout = 'subscription'; export const includes = { subject: { diff --git a/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.txt b/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.txt index 3705d2e5502..27a6f1de155 100644 --- a/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.txt +++ b/libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.txt @@ -8,11 +8,11 @@ subscriptionRenewalReminder-content-intro = "Your current subscription is set to <% if (locals.hadDiscount) { %> subscriptionRenewalReminder-content-discount-ending = "Because a previous discount has ended, your subscription will renew at the standard price." +<% } else if (locals.hasDifferentDiscount) { %> +subscriptionRenewalReminder-content-discount-change = "Your next invoice reflects a change in pricing, as a previous discount has ended and a new discount has been applied." +<% } %> subscriptionRenewalReminder-content-charge = "At that time, Mozilla will renew your <%- planIntervalCount %> <%- planInterval %> subscription and a charge of <%- invoiceTotal %> will be applied to the payment method on your account." -<% } else { %> -subscriptionRenewalReminder-content-charge = "At that time, Mozilla will renew your <%- planIntervalCount %> <%- planInterval %> subscription and a charge of <%- invoiceTotal %> will be applied to the payment method on your account." -<% } %> <%- include ('/partials/subscriptionUpdateBillingEnsure/index.txt') %> diff --git a/packages/fxa-auth-server/lib/payments/subscription-reminders.ts b/packages/fxa-auth-server/lib/payments/subscription-reminders.ts index d2e3a82b517..6bb7136bdb0 100644 --- a/packages/fxa-auth-server/lib/payments/subscription-reminders.ts +++ b/packages/fxa-auth-server/lib/payments/subscription-reminders.ts @@ -268,7 +268,6 @@ export class SubscriptionReminders { /** * Determine if a discount is ending by checking that the subscription currently * has a discount but the upcoming invoice does not. - * TODO in PAY-3485: Handle the case where the discount changes without ending. */ private hasDiscountEnding( subscription: Stripe.Subscription, @@ -277,6 +276,20 @@ export class SubscriptionReminders { return !!subscription.discount && !invoicePreview.discount; } + /** + * Determine if the upcoming invoice has a discount that is different from + * the current subscription's discount. + */ + private hasDifferentDiscount( + subscription: Stripe.Subscription, + invoicePreview: Stripe.UpcomingInvoice + ): boolean { + if (!subscription.discount || !invoicePreview.discount) { + return false; + } + return subscription.discount.id !== invoicePreview.discount.id; + } + /** * Send out a renewal reminder email if we haven't already sent one. */ @@ -341,6 +354,8 @@ export class SubscriptionReminders { // Detect if discount is ending const hadDiscount = this.hasDiscountEnding(subscription, invoicePreview); + // Detect if renewal has a different discount + const hasDifferentDiscount = this.hasDifferentDiscount(subscription, invoicePreview); await this.mailer.sendSubscriptionRenewalReminderEmail( account.emails, @@ -359,6 +374,7 @@ export class SubscriptionReminders { productMetadata: formattedSubscription.productMetadata, planConfig: formattedSubscription.planConfig, hadDiscount, + hasDifferentDiscount, } ); await this.updateSentEmail( diff --git a/packages/fxa-auth-server/lib/senders/email.js b/packages/fxa-auth-server/lib/senders/email.js index 61cd303f6be..8f7e23c3d25 100644 --- a/packages/fxa-auth-server/lib/senders/email.js +++ b/packages/fxa-auth-server/lib/senders/email.js @@ -3253,6 +3253,7 @@ module.exports = function (log, config, bounces, statsd) { message.acceptLanguage ), hadDiscount: message.hadDiscount || false, + hasDifferentDiscount: message.hasDifferentDiscount || false, }, }); }; diff --git a/packages/fxa-auth-server/lib/senders/emails/templates/_versions.json b/packages/fxa-auth-server/lib/senders/emails/templates/_versions.json index d1555bf7a97..03a6be1e370 100644 --- a/packages/fxa-auth-server/lib/senders/emails/templates/_versions.json +++ b/packages/fxa-auth-server/lib/senders/emails/templates/_versions.json @@ -1,6 +1,6 @@ { "subscriptionReactivation": 2, - "subscriptionRenewalReminder": 3, + "subscriptionRenewalReminder": 4, "subscriptionEndingReminder": 1, "subscriptionUpgrade": 7, "subscriptionDowngrade": 2, diff --git a/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/en.ftl b/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/en.ftl index 12657f864fb..6a7e0614efa 100644 --- a/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/en.ftl +++ b/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/en.ftl @@ -8,6 +8,7 @@ subscriptionRenewalReminder-content-greeting = Dear { $productName } customer, # Variables # $reminderLength (String) - The number of days until the current subscription is set to automatically renew, e.g. 14 subscriptionRenewalReminder-content-intro = Your current subscription is set to automatically renew in { $reminderLength } days. +subscriptionRenewalReminder-content-discount-change = Your next invoice reflects a change in pricing, as a previous discount has ended and a new discount has been applied. subscriptionRenewalReminder-content-discount-ending = Because a previous discount has ended, your subscription will renew at the standard price. # Variables # $invoiceTotal (String) - The amount of the subscription invoice, including currency, e.g. $10.00 diff --git a/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.mjml b/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.mjml index b3eda94dbf8..d7ab2116079 100644 --- a/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.mjml +++ b/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.mjml @@ -24,6 +24,12 @@ Because a previous discount has ended, your subscription will renew at the standard price. + <% } else if (hasDifferentDiscount) { %> + + + Your next invoice reflects a change in pricing, as a previous discount has ended and a new discount has been applied. + + <% } %> diff --git a/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.stories.ts b/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.stories.ts index 1d439259787..785dc3bd07e 100644 --- a/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.stories.ts +++ b/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.stories.ts @@ -21,6 +21,7 @@ const createStory = subplatStoryWithProps( subscriptionSupportUrl: 'http://localhost:3030/support', updateBillingUrl: 'http://localhost:3030/subscriptions', hadDiscount: false, + hasDifferentDiscount: false, } ); @@ -54,6 +55,28 @@ export const YearlyPlanDiscountEnding = createStory( reminderLength: '15', invoiceTotal: '$199.99', hadDiscount: true, + hasDifferentDiscount: false, }, 'Yearly Plan - Discount Ending' ); + +export const MonthlyPlanDiscountChanging = createStory( + { + hadDiscount: true, + hasDifferentDiscount: true, + invoiceTotal: '$14.00', + }, + 'Monthly Plan - Discount Changing' +); + +export const YearlyPlanDiscountChanging = createStory( + { + planInterval: 'year', + planIntervalCount: '1', + reminderLength: '15', + invoiceTotal: '$139.99', + hadDiscount: true, + hasDifferentDiscount: true, + }, + 'Yearly Plan - Discount Changing' +); diff --git a/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.txt b/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.txt index b2ebcd9b19a..1103404d8af 100644 --- a/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.txt +++ b/packages/fxa-auth-server/lib/senders/emails/templates/subscriptionRenewalReminder/index.txt @@ -8,11 +8,11 @@ subscriptionRenewalReminder-content-intro = "Your current subscription is set to <% if (hadDiscount) { %> subscriptionRenewalReminder-content-discount-ending = "Because a previous discount has ended, your subscription will renew at the standard price." +<% } else if (hasDifferentDiscount) { %> +subscriptionRenewalReminder-content-discount-change = "Your next invoice reflects a change in pricing, as a previous discount has ended and a new discount has been applied." +<% } %> subscriptionRenewalReminder-content-charge = "At that time, Mozilla will renew your <%- planIntervalCount %> <%- planInterval %> subscription and a charge of <%- invoiceTotal %> will be applied to the payment method on your account." -<% } else { %> -subscriptionRenewalReminder-content-charge = "At that time, Mozilla will renew your <%- planIntervalCount %> <%- planInterval %> subscription and a charge of <%- invoiceTotal %> will be applied to the payment method on your account." -<% } %> <%- include ('/partials/subscriptionUpdateBillingEnsure/index.txt') %> diff --git a/packages/fxa-auth-server/test/local/payments/subscription-reminders.js b/packages/fxa-auth-server/test/local/payments/subscription-reminders.js index f1f492a3f4f..d9a99b3bacc 100644 --- a/packages/fxa-auth-server/test/local/payments/subscription-reminders.js +++ b/packages/fxa-auth-server/test/local/payments/subscription-reminders.js @@ -400,6 +400,7 @@ describe('SubscriptionReminders', () => { productMetadata: formattedSubscription.productMetadata, planConfig, hadDiscount: false, + hasDifferentDiscount: false, } ); sinon.assert.calledOnceWithExactly( diff --git a/packages/fxa-auth-server/test/local/senders/emails.ts b/packages/fxa-auth-server/test/local/senders/emails.ts index 8de74f16ac5..be7ffe24260 100644 --- a/packages/fxa-auth-server/test/local/senders/emails.ts +++ b/packages/fxa-auth-server/test/local/senders/emails.ts @@ -2824,6 +2824,72 @@ const TESTS: [string, any, Record?][] = [ ]] ]), {updateTemplateValues: x => ({...x, productName: MESSAGE.subscription.productName })}], + ['subscriptionRenewalReminderEmail', new Map([ + ['subject', { test: 'equal', expected: `${MESSAGE.subscription.productName} automatic renewal notice` }], + ['headers', new Map([ + ['X-SES-MESSAGE-TAGS', { test: 'equal', expected: sesMessageTagsHeaderValue('subscriptionRenewalReminder') }], + ['X-Template-Name', { test: 'equal', expected: 'subscriptionRenewalReminder' }], + ['X-Template-Version', { test: 'equal', expected: TEMPLATE_VERSIONS.subscriptionRenewalReminder }], + ])], + ['html', [ + { test: 'include', expected: decodeUrl(configHref('subscriptionTermsUrl', 'subscription-renewal-reminder', 'subscription-terms')) }, + { test: 'include', expected: decodeUrl(configHref('subscriptionSettingsUrl', 'subscription-renewal-reminder', 'update-billing', 'plan_id', 'product_id', 'uid', 'email')) }, + { test: 'include', expected: decodeUrl(configHref('subscriptionSupportUrl', 'subscription-renewal-reminder', 'subscription-support')) }, + { test: 'include', expected: `Dear ${MESSAGE.subscription.productName} customer` }, + { test: 'include', expected: `Your current subscription is set to automatically renew in ${MESSAGE.reminderLength} days.` }, + { test: 'include', expected: `Because a previous discount has ended, your subscription will renew at the standard price.` }, + { test: 'notInclude', expected: `Your next invoice reflects a change in pricing, as a previous discount has ended and a new discount has been applied.` }, + { test: 'include', expected: `At that time, Mozilla will renew your ${MESSAGE.planIntervalCount} ${MESSAGE.planInterval} subscription and a charge of ${MESSAGE_FORMATTED.invoiceTotal} will be applied to the payment method on your account.` }, + { test: 'include', expected: "Sincerely," }, + { test: 'include', expected: `The ${MESSAGE.subscription.productName} team` }, + { test: 'notInclude', expected: 'utm_source=email' }, + ]], + ['text', [ + { test: 'include', expected: `${MESSAGE.subscription.productName} automatic renewal notice` }, + { test: 'include', expected: `Dear ${MESSAGE.subscription.productName} customer` }, + { test: 'include', expected: `Your current subscription is set to automatically renew in ${MESSAGE.reminderLength} days.` }, + { test: 'include', expected: `Because a previous discount has ended, your subscription will renew at the standard price.` }, + { test: 'notInclude', expected: `Your next invoice reflects a change in pricing, as a previous discount has ended and a new discount has been applied.` }, + { test: 'include', expected: `At that time, Mozilla will renew your ${MESSAGE.planIntervalCount} ${MESSAGE.planInterval} subscription and a charge of ${MESSAGE_FORMATTED.invoiceTotal} will be applied to the payment method on your account.` }, + { test: 'include', expected: "Sincerely," }, + { test: 'include', expected: `The ${MESSAGE.subscription.productName} team` }, + { test: 'notInclude', expected: 'utm_source=email' }, + ]] + ]), {updateTemplateValues: x => ({...x, productName: MESSAGE.subscription.productName, hadDiscount: true, hasDifferentDiscount: false })}], + + ['subscriptionRenewalReminderEmail', new Map([ + ['subject', { test: 'equal', expected: `${MESSAGE.subscription.productName} automatic renewal notice` }], + ['headers', new Map([ + ['X-SES-MESSAGE-TAGS', { test: 'equal', expected: sesMessageTagsHeaderValue('subscriptionRenewalReminder') }], + ['X-Template-Name', { test: 'equal', expected: 'subscriptionRenewalReminder' }], + ['X-Template-Version', { test: 'equal', expected: TEMPLATE_VERSIONS.subscriptionRenewalReminder }], + ])], + ['html', [ + { test: 'include', expected: decodeUrl(configHref('subscriptionTermsUrl', 'subscription-renewal-reminder', 'subscription-terms')) }, + { test: 'include', expected: decodeUrl(configHref('subscriptionSettingsUrl', 'subscription-renewal-reminder', 'update-billing', 'plan_id', 'product_id', 'uid', 'email')) }, + { test: 'include', expected: decodeUrl(configHref('subscriptionSupportUrl', 'subscription-renewal-reminder', 'subscription-support')) }, + { test: 'include', expected: `Dear ${MESSAGE.subscription.productName} customer` }, + { test: 'include', expected: `Your current subscription is set to automatically renew in ${MESSAGE.reminderLength} days.` }, + { test: 'include', expected: `Your next invoice reflects a change in pricing, as a previous discount has ended and a new discount has been applied.` }, + { test: 'notInclude', expected: `Because a previous discount has ended, your subscription will renew at the standard price.` }, + { test: 'include', expected: `At that time, Mozilla will renew your ${MESSAGE.planIntervalCount} ${MESSAGE.planInterval} subscription and a charge of ${MESSAGE_FORMATTED.invoiceTotal} will be applied to the payment method on your account.` }, + { test: 'include', expected: "Sincerely," }, + { test: 'include', expected: `The ${MESSAGE.subscription.productName} team` }, + { test: 'notInclude', expected: 'utm_source=email' }, + ]], + ['text', [ + { test: 'include', expected: `${MESSAGE.subscription.productName} automatic renewal notice` }, + { test: 'include', expected: `Dear ${MESSAGE.subscription.productName} customer` }, + { test: 'include', expected: `Your current subscription is set to automatically renew in ${MESSAGE.reminderLength} days.` }, + { test: 'include', expected: `Your next invoice reflects a change in pricing, as a previous discount has ended and a new discount has been applied.` }, + { test: 'notInclude', expected: `Because a previous discount has ended, your subscription will renew at the standard price.` }, + { test: 'include', expected: `At that time, Mozilla will renew your ${MESSAGE.planIntervalCount} ${MESSAGE.planInterval} subscription and a charge of ${MESSAGE_FORMATTED.invoiceTotal} will be applied to the payment method on your account.` }, + { test: 'include', expected: "Sincerely," }, + { test: 'include', expected: `The ${MESSAGE.subscription.productName} team` }, + { test: 'notInclude', expected: 'utm_source=email' }, + ]] + ]), {updateTemplateValues: x => ({...x, productName: MESSAGE.subscription.productName, hadDiscount: false, hasDifferentDiscount: true })}], + ['subscriptionEndingReminderEmail', new Map([ ['subject', { test: 'equal', expected: `Your ${MESSAGE.subscription.productName} subscription will expire soon` }], ['headers', new Map([