Skip to content

Commit

Permalink
feat: send a "thank you for renewing" email
Browse files Browse the repository at this point in the history
  • Loading branch information
th0rgall committed Feb 5, 2024
1 parent 0228272 commit ae12198
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"svelte"
],
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[svelte]": {
"editor.defaultFormatter": "svelte.svelte-vscode"
},
Expand Down
38 changes: 38 additions & 0 deletions api/src/mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,41 @@ exports.sendSubscriptionEndedEmail = (email, firstName, language) => {

return send(msg);
};

/**
* @param {string} email
* @param {string} firstName
* @param {string} language
* @returns
*/
exports.sendSubscriptionRenewalThankYouEmail = (email, firstName, language) => {
let templateId;
switch (language) {
case 'fr':
templateId = 'd-180e7b9764c64552a5f9715606432858';
break;
case 'nl':
templateId = 'd-04a338e3ecee43a1bf2a941a9b39ffdb';
break;
default:
templateId = 'd-bda2656ade9a4efd97650ed9df43de39';
break;
}

const msg = {
to: email,
from: 'Welcome To My Garden <support@welcometomygarden.org>',
templateId,
dynamic_template_data: {
firstName
}
};

if (!canSendMail) {
console.warn(NO_API_KEY_WARNING);
console.info(JSON.stringify(msg));
return Promise.resolve();
}

return send(msg);
};
12 changes: 11 additions & 1 deletion api/src/subscriptions/stripeEventHandlers/invoiceCreated.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,17 @@ module.exports = async (event, res) => {
const uid = await getFirebaseUserId(invoice.customer);

// Finalize the invoice
const finalizedInvoice = await stripe.invoices.finalizeInvoice(invoice.id);
/** @type {import('stripe').Stripe.Invoice} */
let finalizedInvoice;
try {
finalizedInvoice = await stripe.invoices.finalizeInvoice(invoice.id);
} catch (e) {
// The invoice may already be finalized immediately after this event is sent when we're working with test clocks,
// but before this function executes.
// TODO: verify that this is indeed the error here. Via code?
// > Error: This invoice is already finalized, you can't re-finalize a non-draft invoice.
finalizedInvoice = await stripe.invoices.retrieve(invoice.id);
}

const { renewalInvoiceLinkKey, latestInvoiceStatusKey } = stripeSubscriptionKeys;

Expand Down
40 changes: 27 additions & 13 deletions api/src/subscriptions/stripeEventHandlers/invoicePaid.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const getFirebaseUserId = require('../getFirebaseUserId');
const { sendSubscriptionConfirmationEmail } = require('../../mail');
const {
sendSubscriptionConfirmationEmail,
sendSubscriptionRenewalThankYouEmail
} = require('../../mail');
const { stripeSubscriptionKeys } = require('../constants');
const { db } = require('../../firebase');

Expand Down Expand Up @@ -39,18 +42,29 @@ module.exports = async (event, res) => {
}

// Send a confirmation email in case it was not sent yet
// (we already send a confirmation email on paymentProcessing)
if (
!paymentWasProcessing &&
(invoice.billing_reason === 'subscription_create' ||
invoice.metadata.billing_reason_override === 'subscription_create')
) {
// this is the paid invoice for the first subscription
sendSubscriptionConfirmationEmail(
invoice.customer_email,
publicUserProfileData.firstName,
privateUserProfileData.communicationLanguage
);
// (we already send a confirmation email in paymentIntentProcessing.js when this flag is true)
if (!paymentWasProcessing) {
if (
invoice.billing_reason === 'subscription_create' ||
invoice.metadata.billing_reason_override === 'subscription_create'
) {
// this is the paid invoice for the first subscription
sendSubscriptionConfirmationEmail(
invoice.customer_email,
publicUserProfileData.firstName,
privateUserProfileData.communicationLanguage
);
}

if (invoice.billing_reason === 'subscription_cycle') {
// Overrides of invoices should not be possible on subscription cycles (at the time of writing)
// But with SOFORT, paymetnProcessing on renewals is (or should be) possible.
sendSubscriptionRenewalThankYouEmail(
invoice.customer_email,
publicUserProfileData.firstName,
privateUserProfileDocRef.communicationLanguage
);
}
}

return res.sendStatus(200);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
// @ts-check
/* eslint-disable camelcase */
const getFirebaseUserId = require('../getFirebaseUserId');
const { sendSubscriptionConfirmationEmail } = require('../../mail');
const {
sendSubscriptionConfirmationEmail,
sendSubscriptionRenewalThankYouEmail
} = require('../../mail');
const { stripeSubscriptionKeys } = require('../constants');
const { db } = require('../../firebase');
const stripe = require('../stripe');

const { latestInvoiceStatusKey, paymentProcessingKey } = stripeSubscriptionKeys;

/**
* Inform Firebase of an approved, processing payment + send membership email
*
* Inform Firebase of an approved, processing payment + send membership confirmation email
* Special case handlig of SOFORT, where we consider an "network approved" initiated payment
* already fully closed & settled.
* ("first thank you" email, or the "thank you for renewing" email)
* @param {import('stripe').Stripe.Event} event
* @returns
*/
Expand Down Expand Up @@ -51,8 +58,13 @@ module.exports = async (event, res) => {
// Check if the invoice is related to subscription creation
if (
!(
invoice.billing_reason === 'subscription_create' ||
invoice.metadata?.billing_reason_override === 'subscription_create'
// when creating
(
invoice.billing_reason === 'subscription_create' ||
invoice.metadata?.billing_reason_override === 'subscription_create' ||
// when renewing
invoice.billing_reason === 'subscription_cycle'
)
)
) {
return res.sendStatus(200);
Expand All @@ -75,12 +87,23 @@ module.exports = async (event, res) => {
return res.sendStatus(500);
}

// Send a superfan email
sendSubscriptionConfirmationEmail(
invoice.customer_email,
publicUserProfileData.firstName,
privateUserProfileData.communicationLanguage
);
// Send a payment confirmation email
if (
invoice.billing_reason === 'subscription_create' ||
invoice.metadata?.billing_reason_override === 'subscription_create'
) {
sendSubscriptionConfirmationEmail(
invoice.customer_email,
publicUserProfileData.firstName,
privateUserProfileData.communicationLanguage
);
} else if (invoice.billing_reason === 'subscription_cycle') {
sendSubscriptionRenewalThankYouEmail(
invoice.customer_email,
publicUserProfileData.firstName,
privateUserProfileData.communicationLanguage
);
}

return res.sendStatus(200);
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"firebase:debug": "firebase --project demo-test emulators:start --inspect-functions",
"firebase:staging": "firebase --project wtmg-dev emulators:start --only functions",
"firebase:staging-all": "firebase --project wtmg-dev emulators:start",
"firebase:staging-push": "STAGING=true firebase --project wtmg-dev emulators:exec --ui api/seeders/simple.js"
"firebase:staging-push": "STAGING=true firebase --project wtmg-dev emulators:exec --ui api/seeders/simple.js",
"stripe:staging": "stripe listen --events customer.subscription.deleted,customer.subscription.updated,invoice.finalized,invoice.created,invoice.paid,payment_intent.processing --forward-to http://127.0.0.1:5001/wtmg-dev/europe-west1/stripeWebhooks"
},
"publishConfig": {
"access": "public"
Expand Down

0 comments on commit ae12198

Please sign in to comment.