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([