Skip to content

Commit

Permalink
feat/issue-2898: Modify transactional emails
Browse files Browse the repository at this point in the history
  • Loading branch information
lesleyjanenorton committed Mar 1, 2021
1 parent 9b094b3 commit fe38349
Show file tree
Hide file tree
Showing 17 changed files with 286 additions and 125 deletions.
21 changes: 18 additions & 3 deletions packages/fxa-auth-server/lib/payments/stripe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,10 +352,10 @@ export class StripeHelper {

/**
* Get Invoice object based on invoice Id
*
* @param id
*
* @param id
*/
async getInvoice(id: string) : Promise<Stripe.Invoice> {
async getInvoice(id: string): Promise<Stripe.Invoice> {
return this.stripe.invoices.retrieve(id);
}

Expand Down Expand Up @@ -557,6 +557,18 @@ export class StripeHelper {
return await this.stripe.paymentMethods.retrieve(paymentMethodId);
}

getPaymentProvider(customer: Stripe.Customer) {
const subscription = customer.subscriptions?.data.find(
(sub: { status: string }) => ['active', 'past_due'].includes(sub.status)
);
if (subscription) {
return subscription.collection_method === 'send_invoice'
? 'paypal'
: 'stripe';
}
return 'not_chosen';
}

async detachPaymentMethod(
paymentMethodId: string
): Promise<Stripe.PaymentMethod> {
Expand Down Expand Up @@ -1326,11 +1338,14 @@ export class StripeHelper {
charge,
});

const payment_provider = this.getPaymentProvider(customer);

return {
uid,
email,
cardType,
lastFour,
payment_provider,
invoiceNumber,
invoiceTotalInCents,
invoiceTotalCurrency,
Expand Down
19 changes: 5 additions & 14 deletions packages/fxa-auth-server/lib/routes/subscriptions/stripe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,22 +299,13 @@ export class StripeHandler {
return activeSubscriptions;
}

getPaymentProvider(customer: Stripe.Customer) {
const subscription = customer.subscriptions?.data.find(
(sub: { status: string }) => ['active', 'past_due'].includes(sub.status)
);
if (subscription) {
return subscription.collection_method === 'send_invoice'
? 'paypal'
: 'stripe';
}
return 'not_chosen';
}
/**
* Extracts billing details if a customer has a source on file.
*/
extractBillingDetails(customer: Stripe.Customer) {
const defaultPayment = customer.invoice_settings.default_payment_method;
const paymentProvider = this.stripeHelper.getPaymentProvider(customer);

if (defaultPayment) {
if (typeof defaultPayment === 'string') {
// This should always be expanded here.
Expand All @@ -324,7 +315,7 @@ export class StripeHandler {
if (defaultPayment.card) {
return {
billing_name: defaultPayment.billing_details.name,
payment_provider: this.getPaymentProvider(customer),
payment_provider: paymentProvider,
payment_type: defaultPayment.card.funding,
last4: defaultPayment.card.last4,
exp_month: defaultPayment.card.exp_month,
Expand All @@ -340,7 +331,7 @@ export class StripeHandler {
if (src.object === 'card') {
return {
billing_name: src.name,
payment_provider: this.getPaymentProvider(customer),
payment_provider: paymentProvider,
payment_type: src.funding,
last4: src.last4,
exp_month: src.exp_month,
Expand All @@ -351,7 +342,7 @@ export class StripeHandler {
}

return {
payment_provider: this.getPaymentProvider(customer),
payment_provider: paymentProvider,
};
}

Expand Down
6 changes: 6 additions & 0 deletions packages/fxa-auth-server/lib/senders/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -2197,6 +2197,7 @@ module.exports = function (log, config) {
cardType,
lastFour,
nextInvoiceDate,
payment_provider,
} = message;

const enabled = config.subscriptions.transactionalEmails.enabled;
Expand Down Expand Up @@ -2245,6 +2246,7 @@ module.exports = function (log, config) {
invoiceTotalCurrency,
message.acceptLanguage
),
payment_provider,
cardType: cardTypeToText(cardType),
lastFour,
nextInvoiceDate,
Expand All @@ -2269,6 +2271,7 @@ module.exports = function (log, config) {
cardType,
lastFour,
nextInvoiceDate,
payment_provider,
paymentProratedInCents,
paymentProratedCurrency,
} = message;
Expand Down Expand Up @@ -2335,6 +2338,7 @@ module.exports = function (log, config) {
invoiceTotalCurrency,
message.acceptLanguage
),
payment_provider,
cardType: cardTypeToText(cardType),
lastFour,
nextInvoiceDate,
Expand All @@ -2356,6 +2360,7 @@ module.exports = function (log, config) {
invoiceDate,
invoiceTotalInCents,
invoiceTotalCurrency,
payment_provider,
cardType,
lastFour,
nextInvoiceDate,
Expand Down Expand Up @@ -2412,6 +2417,7 @@ module.exports = function (log, config) {
invoiceTotalCurrency,
message.acceptLanguage
),
payment_provider,
cardType: cardTypeToText(cardType),
lastFour,
nextInvoiceDate,
Expand Down
9 changes: 9 additions & 0 deletions packages/fxa-auth-server/lib/senders/templates/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ async function init(log) {
handlebars.txt.registerHelper('t', translate);
handlebars.html.registerHelper('or', orHelper);
handlebars.txt.registerHelper('or', orHelper);
handlebars.html.registerHelper('ifEquals', isEqualHelper);
handlebars.txt.registerHelper('ifEquals', isEqualHelper);

// helpers from https://gist.github.com/servel333/21e1eedbd70db5a7cfff327526c72bc5
const reduceOp = function (args, reducer) {
Expand All @@ -47,6 +49,13 @@ async function init(log) {
return reduceOp(arguments, (a, b) => a || b);
}

function isEqualHelper(a, b, options) {
if (a === b) {
return options.fn(this);
}
return options.inverse(this);
}

await forEachTemplate(PARTIALS_DIR, (template, name, type) => {
handlebars[type].registerPartial(name, template);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<br>
{{#ifEquals payment_provider "paypal"}}
{{t "PayPal" }}
{{/ifEquals}}

{{#ifEquals payment_provider "stripe"}}
{{#if lastFour}}
{{t "%(cardType)s card ending in %(lastFour)s" }}
{{/if}}
{{/ifEquals}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{{#ifEquals payment_provider "paypal"}}
{{t "PayPal" }}
{{/ifEquals}}

{{#ifEquals payment_provider "stripe"}}
{{#if lastFour}}
{{t "%(cardType)s card ending in %(lastFour)s" }}
{{/if}}
{{/ifEquals}}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@
{{{t "Invoice Number: <b>%(invoiceNumber)s</b>" }}}
<br>
{{t "Charged %(invoiceTotal)s on %(invoiceDateOnly)s" }}
{{#if lastFour}}
<br>
{{t "%(cardType)s card ending in %(lastFour)s" }}
{{/if}}
{{> paymentProvider }}
<br><br>
{{t "Next Invoice: %(nextInvoiceDateOnly)s" }}
<br><br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
{{{t "Invoice Number: %(invoiceNumber)s" }}}

{{{t "Charged %(invoiceTotal)s on %(invoiceDateOnly)s" }}}

{{#if lastFour}}{{{t "%(cardType)s card ending in %(lastFour)s" }}}{{/if}}
{{> paymentProvider }}

{{{t "Next Invoice: %(nextInvoiceDateOnly)s" }}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
<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." }}}
{{{t "Help us fix it by <a href=\"%(updateBillingUrl)s\">updating your payment information</a> within 24 hours." }}}
<br><br>
{{t "Unfortunately, if we don’t hear from you within three days, you’ll lose access to %(productName)s." }}
{{t "Unfortunately, if we don’t hear from you within 24 hours, 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." }}}
{{/inline}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

{{{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:" }}}
{{{t "Help us fix it by updating your payment information within 24 hours:" }}}

{{{updateBillingUrl}}}

{{{t "Unfortunately, if we don’t hear from you within three days, you’ll lose access to %(productName)s." }}}
{{{t "Unfortunately, if we don’t hear from you within 24 hours, you’ll lose access to %(productName)s." }}}

{{{t "Questions about your subscription? Our support team is here to help you:" }}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
{{/inline}}

{{#*inline "content"}}
{{#if lastFour}}
{{t "Your billing cycle and payment will remain the same. Your next charge will be %(invoiceTotal)s to the %(cardType)s card ending in %(lastFour)s on %(nextInvoiceDateOnly)s. Your subscription will automatically renew each billing period unless you choose to cancel." }}
{{else}}
{{t "Your billing cycle and payment will remain the same. Your next charge will be %(invoiceTotal)s on %(nextInvoiceDateOnly)s. Your subscription will automatically renew each billing period unless you choose to cancel." }}
{{/if}}
{{t "Your billing cycle and payment will remain the same. Your next charge will be %(invoiceTotal)s on %(nextInvoiceDateOnly)s. Your subscription will automatically renew each billing period unless you choose to cancel." }}

<br><br>
{{{t "Questions about your subscription? Our <a href=\"%(subscriptionSupportUrl)s\">support team</a> is here to help you." }}}
{{/inline}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

{{{t "Thank you for reactivating your %(productName)s subscription!" }}}

{{#if lastFour}}
{{t "Your billing cycle and payment will remain the same. Your next charge will be %(invoiceTotal)s to the %(cardType)s card ending in %(lastFour)s on %(nextInvoiceDateOnly)s. Your subscription will automatically renew each billing period unless you choose to cancel." }}
{{else}}
{{t "Your billing cycle and payment will remain the same. Your next charge will be %(invoiceTotal)s on %(nextInvoiceDateOnly)s. Your subscription will automatically renew each billing period unless you choose to cancel." }}
{{/if}}

{{{t "Questions about your subscription? Our support team is here to help you:" }}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@
<br>
{{/showProratedAmount}}
{{t "Charged %(invoiceTotal)s on %(invoiceDateOnly)s" }}
{{#if lastFour}}
<br>
{{t "%(cardType)s card ending in %(lastFour)s" }}
{{/if}}
{{> paymentProvider }}
<br><br>
{{t "Next Invoice: %(nextInvoiceDateOnly)s" }}
<br><br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
{{#showProratedAmount}}{{{t "Plan change: %(paymentProrated)s" }}}{{/showProratedAmount}}

{{{t "Charged %(invoiceTotal)s on %(invoiceDateOnly)s" }}}

{{#if lastFour}}{{{t "%(cardType)s card ending in %(lastFour)s" }}}{{/if}}
{{> paymentProvider }}

{{{t "Next Invoice: %(nextInvoiceDateOnly)s" }}}

Expand Down
68 changes: 56 additions & 12 deletions packages/fxa-auth-server/test/local/payments/stripe.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,50 @@ describe('StripeHelper', () => {
});
});

describe('getPaymentProvider', () => {
let customerExpanded;
beforeEach(() => {
customerExpanded = deepCopy(customer1);
});
describe('returns correct value based on collection_method', () => {
describe('when collection_method is "send_invoice"', () => {
it('payment_provider is "paypal"', async () => {
subscription2.collection_method = 'send_invoice';
customerExpanded.subscriptions.data[0] = subscription2;
assert(
stripeHelper.getPaymentProvider(customerExpanded) === 'paypal'
);
});
});
describe('when the customer has a canceled subscription', () => {
it('payment_provider is "not_chosen"', async () => {
customerExpanded.subscriptions.data[0] = cancelledSubscription;
assert(
stripeHelper.getPaymentProvider(customerExpanded) === 'not_chosen'
);
});
});
describe('when the customer has no subscriptions', () => {
it('payment_provider is "not_chosen"', async () => {
customerExpanded.subscriptions.data = [];
assert(
stripeHelper.getPaymentProvider(customerExpanded) === 'not_chosen'
);
});
});
describe('when collection_method is "instant"', () => {
it('payment_provider is "stripe"', async () => {
subscription2.collection_method = 'instant';
customerExpanded.subscriptions.data[0] = subscription2;
assert.equal(
stripeHelper.getPaymentProvider(customerExpanded),
'stripe'
);
});
});
});
});

describe('detachPaymentMethod', () => {
it('calls the Stripe api', async () => {
const paymentMethodId = 'pm_9001';
Expand Down Expand Up @@ -681,18 +725,17 @@ describe('StripeHelper', () => {
});

describe('getInvoice', () => {
it('works successfully',
async () => {
sandbox
.stub(stripeHelper.stripe.invoices, 'retrieve')
.resolves(unpaidInvoice);
const actual = await stripeHelper.getInvoice(unpaidInvoice.id);
assert.deepEqual(actual, unpaidInvoice);
sinon.assert.calledOnceWithExactly(
stripeHelper.stripe.invoices.retrieve,
unpaidInvoice.id
);
});
it('works successfully', async () => {
sandbox
.stub(stripeHelper.stripe.invoices, 'retrieve')
.resolves(unpaidInvoice);
const actual = await stripeHelper.getInvoice(unpaidInvoice.id);
assert.deepEqual(actual, unpaidInvoice);
sinon.assert.calledOnceWithExactly(
stripeHelper.stripe.invoices.retrieve,
unpaidInvoice.id
);
});
});

describe('finalizeInvoice', () => {
Expand Down Expand Up @@ -2535,6 +2578,7 @@ describe('StripeHelper', () => {
invoiceTotalInCents: 500,
invoiceDate: new Date('2020-03-24T22:23:40.000Z'),
nextInvoiceDate: new Date('2020-04-24T22:23:40.000Z'),
payment_provider: 'stripe',
productId,
productName,
planId,
Expand Down

0 comments on commit fe38349

Please sign in to comment.