зеркало из https://github.com/mozilla/fxa.git
Merge pull request #4781 from lmorchard/fxa-1155-subplat-email-payment-fail
feat(payments): email methods for subplat payment failed and expired
This commit is contained in:
Коммит
44f78c9d78
|
@ -20,6 +20,8 @@ const { version: VERSION } = require('../../package.json');
|
|||
|
||||
// Maps template name to email type
|
||||
const EMAIL_TYPES = {
|
||||
subscriptionPaymentExpired: 'subscription_payment_expired',
|
||||
subscriptionPaymentFailed: 'subscription_payment_failed',
|
||||
subscriptionAccountDeletion: 'subscription_account_deletion',
|
||||
subscriptionCancellation: 'subscription_cancellation',
|
||||
subscriptionSubsequentInvoice: 'subscription_subsequent_invoice',
|
||||
|
|
|
@ -35,6 +35,8 @@ module.exports = function(log, config, oauthdb) {
|
|||
// Email template to UTM campaign map, each of these should be unique and
|
||||
// map to exactly one email template.
|
||||
const templateNameToCampaignMap = {
|
||||
subscriptionPaymentExpired: 'subscription-payment-expired',
|
||||
subscriptionPaymentFailed: 'subscription-payment-failed',
|
||||
subscriptionAccountDeletion: 'subscription-account-deletion',
|
||||
subscriptionCancellation: 'subscription-cancellation',
|
||||
subscriptionSubsequentInvoice: 'subscription-subsequent-invoice',
|
||||
|
@ -72,6 +74,8 @@ module.exports = function(log, config, oauthdb) {
|
|||
// Email template to UTM content, this is typically the main call out link/button
|
||||
// in template.
|
||||
const templateNameToContentMap = {
|
||||
subscriptionPaymentExpired: 'subscriptions',
|
||||
subscriptionPaymentFailed: 'subscriptions',
|
||||
subscriptionAccountDeletion: 'subscriptions',
|
||||
subscriptionCancellation: 'subscriptions',
|
||||
subscriptionSubsequentInvoice: 'subscriptions',
|
||||
|
@ -1802,6 +1806,124 @@ module.exports = function(log, config, oauthdb) {
|
|||
});
|
||||
};
|
||||
|
||||
Mailer.prototype.subscriptionPaymentExpiredEmail = async function(message) {
|
||||
const {
|
||||
email,
|
||||
uid,
|
||||
productId,
|
||||
planId,
|
||||
planEmailIconURL,
|
||||
productName,
|
||||
} = message;
|
||||
|
||||
if (!config.subscriptions.transactionalEmails.enabled) {
|
||||
log.trace('mailer.subscriptionPaymentExpired', {
|
||||
enabled: false,
|
||||
email,
|
||||
productId,
|
||||
uid,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
log.trace('mailer.subscriptionPaymentExpired', {
|
||||
enabled: true,
|
||||
email,
|
||||
productId,
|
||||
uid,
|
||||
});
|
||||
|
||||
const query = { plan_id: planId, product_id: productId, uid };
|
||||
const template = 'subscriptionPaymentExpired';
|
||||
const translator = this.translator(message.acceptLanguage);
|
||||
|
||||
const links = this._generateLinks(null, message, query, template);
|
||||
const headers = {};
|
||||
const translatorParams = {
|
||||
productName,
|
||||
uid,
|
||||
email,
|
||||
};
|
||||
const subject = translator.gettext(
|
||||
'Credit card for %(productName)s expiring soon'
|
||||
);
|
||||
|
||||
return this.send({
|
||||
...message,
|
||||
headers,
|
||||
layout: 'subscription',
|
||||
subject,
|
||||
template,
|
||||
templateValues: {
|
||||
...links,
|
||||
...translatorParams,
|
||||
uid,
|
||||
email,
|
||||
icon: planEmailIconURL,
|
||||
product: productName,
|
||||
subject: translator.format(subject, translatorParams),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Mailer.prototype.subscriptionPaymentFailedEmail = async function(message) {
|
||||
const {
|
||||
email,
|
||||
uid,
|
||||
productId,
|
||||
planId,
|
||||
planEmailIconURL,
|
||||
productName,
|
||||
} = message;
|
||||
|
||||
if (!config.subscriptions.transactionalEmails.enabled) {
|
||||
log.trace('mailer.subscriptionPaymentFailed', {
|
||||
enabled: false,
|
||||
email,
|
||||
productId,
|
||||
uid,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
log.trace('mailer.subscriptionPaymentFailed', {
|
||||
enabled: true,
|
||||
email,
|
||||
productId,
|
||||
uid,
|
||||
});
|
||||
|
||||
const query = { plan_id: planId, product_id: productId, uid };
|
||||
const template = 'subscriptionPaymentFailed';
|
||||
const translator = this.translator(message.acceptLanguage);
|
||||
|
||||
const links = this._generateLinks(null, message, query, template);
|
||||
const headers = {};
|
||||
const translatorParams = {
|
||||
productName,
|
||||
uid,
|
||||
email,
|
||||
};
|
||||
const subject = translator.gettext('%(productName)s payment failed');
|
||||
|
||||
return this.send({
|
||||
...message,
|
||||
headers,
|
||||
layout: 'subscription',
|
||||
subject,
|
||||
template,
|
||||
templateValues: {
|
||||
...links,
|
||||
...translatorParams,
|
||||
uid,
|
||||
email,
|
||||
icon: planEmailIconURL,
|
||||
product: productName,
|
||||
subject: translator.format(subject, translatorParams),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Mailer.prototype.subscriptionAccountDeletionEmail = async function(message) {
|
||||
const {
|
||||
email,
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"subscriptionPaymentExpired": 1,
|
||||
"subscriptionPaymentFailed": 1,
|
||||
"subscriptionAccountDeletion": 1,
|
||||
"subscriptionCancellation": 1,
|
||||
"subscriptionSubsequentInvoice": 1,
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
{{> subscriptionIcon}}
|
||||
|
||||
<table class="featured-story featured-story--top" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td style="padding-bottom: 20px;">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td class="featured-story__inner" style="background: #fff;">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td class="featured-story__content-inner" style="padding: 32px 30px 45px;">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td class="featured-story__heading featured-story--top__heading" style="background: #fff;" max-width="640" width="100%" align="left">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Geneva, Tahoma, Verdana, sans-serif; font-size: 22px; color: #464646;text-align:center !important;" width="640" align="center">
|
||||
<b>{{t "Your credit card is about to expire" }}</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="featured-story__copy" style="background: #fff;" width="640" align="center">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Geneva, Tahoma, Verdana, sans-serif; font-size: 16px; line-height: 22px; color: #555555; padding-top: 16px;text-align:left !important;" align="left">
|
||||
{{t "The credit card you're using to make payments for %(productName)s is about to expire." }}
|
||||
<br><br>
|
||||
{{{t "To prevent any interruption to your service, please <a href=\"%(updateBillingUrl)s\">update your payment information</a> as soon as possible." }}}
|
||||
<br><br>
|
||||
{{{t "Questions about your subscription? Our <a href=\"%(subscriptionSupportUrl)s\">support team</a> is here to help you." }}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
|
@ -0,0 +1,13 @@
|
|||
{{{subject}}}
|
||||
|
||||
{{{t "Your credit card is about to expire" }}}
|
||||
|
||||
{{{t "The credit card you're using to make payments for %(productName)s is about to expire." }}}
|
||||
|
||||
{{{t "To prevent any interruption to your service, please update your payment information as soon as possible:" }}}
|
||||
|
||||
{{{updateBillingUrl}}}
|
||||
|
||||
{{{t "Questions about your subscription? Our support team is here to help you:" }}}
|
||||
|
||||
{{{subscriptionSupportUrl}}}
|
|
@ -0,0 +1,52 @@
|
|||
{{> subscriptionIcon}}
|
||||
|
||||
<table class="featured-story featured-story--top" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td style="padding-bottom: 20px;">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td class="featured-story__inner" style="background: #fff;">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td class="featured-story__content-inner" style="padding: 32px 30px 45px;">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td class="featured-story__heading featured-story--top__heading" style="background: #fff;" max-width="640" width="100%" align="left">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Geneva, Tahoma, Verdana, sans-serif; font-size: 22px; color: #464646;text-align:center !important;" width="640" align="center">
|
||||
<b>{{t "Sorry, we're having trouble with your payment" }}</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="featured-story__copy" style="background: #fff;" width="640" align="center">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Geneva, Tahoma, Verdana, sans-serif; font-size: 16px; line-height: 22px; color: #555555; padding-top: 16px;text-align:left !important;" align="left">
|
||||
{{t "We had a problem with your latest payment for %(productName)s." }}
|
||||
<br><br>
|
||||
{{t "It may be that your credit card has expired, or your current payment method is out of date." }}
|
||||
<br><br>
|
||||
{{{t "Help us fix it by <a href=\"%(updateBillingUrl)s\">updating your payment information</a> within three days." }}}
|
||||
<br><br>
|
||||
{{t "Unfortunately, if we don’t hear from you within three days you’ll lose access to %(productName)s." }}
|
||||
<br><br>
|
||||
{{{t "Questions about your subscription? Our <a href=\"%(subscriptionSupportUrl)s\">support team</a> is here to help you." }}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
|
@ -0,0 +1,17 @@
|
|||
{{{subject}}}
|
||||
|
||||
{{{t "Sorry, we're having trouble with your payment" }}}
|
||||
|
||||
{{{t "We had a problem with your latest payment for %(productName)s." }}}
|
||||
|
||||
{{{t "It may be that your credit card has expired, or your current payment method is out of date." }}}
|
||||
|
||||
{{{t "Help us fix it by updating your payment information within three days:" }}}
|
||||
|
||||
{{{updateBillingUrl}}}
|
||||
|
||||
{{{t "Unfortunately, if we don’t hear from you within three days you’ll lose access to %(productName)s." }}}
|
||||
|
||||
{{{t "Questions about your subscription? Our support team is here to help you:" }}}
|
||||
|
||||
{{{subscriptionSupportUrl}}}
|
|
@ -190,6 +190,8 @@ describe('metrics/amplitude', () => {
|
|||
postVerifyAddSecondary: 'registration',
|
||||
postVerifySecondary: 'secondary_email',
|
||||
recovery: 'reset_password',
|
||||
subscriptionPaymentExpired: 'subscription_payment_expired',
|
||||
subscriptionPaymentFailed: 'subscription_payment_failed',
|
||||
subscriptionAccountDeletion: 'subscription_account_deletion',
|
||||
subscriptionCancellation: 'subscription_cancellation',
|
||||
subscriptionFirstInvoice: 'subscription_first_invoice',
|
||||
|
|
|
@ -280,6 +280,46 @@ const TESTS = new Map([
|
|||
{ test: 'notInclude', expected: 'utm_source=email' },
|
||||
]]
|
||||
])],
|
||||
['subscriptionPaymentFailedEmail', new Map([
|
||||
['subject', { test: 'equal', expected: `${MESSAGE.productName} payment failed` }],
|
||||
['headers', new Map([
|
||||
['X-SES-MESSAGE-TAGS', { test: 'equal', expected: sesMessageTagsHeaderValue('subscriptionPaymentFailed') }],
|
||||
['X-Template-Name', { test: 'equal', expected: 'subscriptionPaymentFailed' }],
|
||||
['X-Template-Version', { test: 'equal', expected: TEMPLATE_VERSIONS.subscriptionPaymentFailed }],
|
||||
])],
|
||||
['html', [
|
||||
{ test: 'include', expected: configHref('privacyUrl', 'subscription-payment-failed', 'privacy') },
|
||||
{ test: 'include', expected: configHref('subscriptionSettingsUrl', 'subscription-payment-failed', 'cancel-subscription', 'plan_id', 'product_id', 'uid', 'email') },
|
||||
{ test: 'include', expected: configHref('subscriptionTermsUrl', 'subscription-payment-failed', 'subscription-terms') },
|
||||
{ test: 'include', expected: `latest payment for ${MESSAGE.productName}.` },
|
||||
{ test: 'include', expected: `you’ll lose access to ${MESSAGE.productName}.` },
|
||||
{ test: 'notInclude', expected: 'utm_source=email' },
|
||||
]],
|
||||
['text', [
|
||||
{ test: 'include', expected: `latest payment for ${MESSAGE.productName}.` },
|
||||
{ test: 'include', expected: `you’ll lose access to ${MESSAGE.productName}.` },
|
||||
{ test: 'notInclude', expected: 'utm_source=email' },
|
||||
]]
|
||||
])],
|
||||
['subscriptionPaymentExpiredEmail', new Map([
|
||||
['subject', { test: 'equal', expected: `Credit card for ${MESSAGE.productName} expiring soon` }],
|
||||
['headers', new Map([
|
||||
['X-SES-MESSAGE-TAGS', { test: 'equal', expected: sesMessageTagsHeaderValue('subscriptionPaymentExpired') }],
|
||||
['X-Template-Name', { test: 'equal', expected: 'subscriptionPaymentExpired' }],
|
||||
['X-Template-Version', { test: 'equal', expected: TEMPLATE_VERSIONS.subscriptionPaymentExpired }],
|
||||
])],
|
||||
['html', [
|
||||
{ test: 'include', expected: configHref('privacyUrl', 'subscription-payment-expired', 'privacy') },
|
||||
{ test: 'include', expected: configHref('subscriptionSettingsUrl', 'subscription-payment-expired', 'cancel-subscription', 'plan_id', 'product_id', 'uid', 'email') },
|
||||
{ test: 'include', expected: configHref('subscriptionTermsUrl', 'subscription-payment-expired', 'subscription-terms') },
|
||||
{ test: 'include', expected: `for ${MESSAGE.productName} is about to expire.` },
|
||||
{ test: 'notInclude', expected: 'utm_source=email' },
|
||||
]],
|
||||
['text', [
|
||||
{ test: 'include', expected: `for ${MESSAGE.productName} is about to expire.` },
|
||||
{ test: 'notInclude', expected: 'utm_source=email' },
|
||||
]]
|
||||
])],
|
||||
['lowRecoveryCodesEmail', new Map([
|
||||
['subject', { test: 'equal', expected: '2 recovery codes remaining' }],
|
||||
['headers', new Map([
|
||||
|
|
Загрузка…
Ссылка в новой задаче