diff --git a/packages/backend/src/graphql/queries/get-billing-and-usage.ee.ts b/packages/backend/src/graphql/queries/get-billing-and-usage.ee.ts index 7c786b96..f8d99e18 100644 --- a/packages/backend/src/graphql/queries/get-billing-and-usage.ee.ts +++ b/packages/backend/src/graphql/queries/get-billing-and-usage.ee.ts @@ -44,7 +44,9 @@ const paidSubscription = (subscription: Subscription): TSubscription => { }, }, nextBillAmount: { - title: '€' + subscription.nextBillAmount, + title: subscription.nextBillAmount + ? '€' + subscription.nextBillAmount + : '---', action: { type: 'link', text: 'Update payment method', @@ -52,7 +54,7 @@ const paidSubscription = (subscription: Subscription): TSubscription => { }, }, nextBillDate: { - title: subscription.nextBillDate, + title: subscription.nextBillDate ? subscription.nextBillDate : '---', action: { type: 'text', text: '(monthly payment)', diff --git a/packages/backend/src/helpers/billing/webhooks.ee.ts b/packages/backend/src/helpers/billing/webhooks.ee.ts index 78e2fa60..8390153e 100644 --- a/packages/backend/src/helpers/billing/webhooks.ee.ts +++ b/packages/backend/src/helpers/billing/webhooks.ee.ts @@ -25,10 +25,6 @@ const handleSubscriptionCancelled = async (request: IRequest) => { }); await subscription.$query().patchAndFetch(formatSubscription(request)); - - // Have a background job that deletes the subscription in cancellation effective date - // Architect in a way that it will behave as cron. - // await subscription.$query().delete(); }; const handleSubscriptionPaymentSucceeded = async (request: IRequest) => { diff --git a/packages/backend/src/helpers/create-bull-board-handler.ts b/packages/backend/src/helpers/create-bull-board-handler.ts index 28a39330..6bab1db9 100644 --- a/packages/backend/src/helpers/create-bull-board-handler.ts +++ b/packages/backend/src/helpers/create-bull-board-handler.ts @@ -6,6 +6,7 @@ import triggerQueue from '../queues/trigger'; import actionQueue from '../queues/action'; import emailQueue from '../queues/email'; import deleteUserQueue from '../queues/delete-user.ee'; +import removeCancelledSubscriptionsQueue from '../queues/remove-cancelled-subscriptions.ee'; import appConfig from '../config/app'; const serverAdapter = new ExpressAdapter(); @@ -25,6 +26,7 @@ const createBullBoardHandler = async (serverAdapter: ExpressAdapter) => { new BullMQAdapter(actionQueue), new BullMQAdapter(emailQueue), new BullMQAdapter(deleteUserQueue), + new BullMQAdapter(removeCancelledSubscriptionsQueue), ], serverAdapter: serverAdapter, }); diff --git a/packages/backend/src/queues/remove-cancelled-subscriptions.ee.ts b/packages/backend/src/queues/remove-cancelled-subscriptions.ee.ts new file mode 100644 index 00000000..ec7983b3 --- /dev/null +++ b/packages/backend/src/queues/remove-cancelled-subscriptions.ee.ts @@ -0,0 +1,35 @@ +import process from 'process'; +import { Queue } from 'bullmq'; +import redisConfig from '../config/redis'; +import logger from '../helpers/logger'; + +const CONNECTION_REFUSED = 'ECONNREFUSED'; + +const redisConnection = { + connection: redisConfig, +}; + +const removeCancelledSubscriptionsQueue = new Queue( + 'remove-cancelled-subscriptions', + redisConnection +); + +process.on('SIGTERM', async () => { + await removeCancelledSubscriptionsQueue.close(); +}); + +removeCancelledSubscriptionsQueue.on('error', (err) => { + if ((err as any).code === CONNECTION_REFUSED) { + logger.error('Make sure you have installed Redis and it is running.', err); + process.exit(); + } +}); + +removeCancelledSubscriptionsQueue.add('remove-cancelled-subscriptions', null, { + jobId: 'remove-cancelled-subscriptions', + repeat: { + pattern: '0 1 * * *', + }, +}); + +export default removeCancelledSubscriptionsQueue; diff --git a/packages/backend/src/worker.ts b/packages/backend/src/worker.ts index 141ac78b..ae63d224 100644 --- a/packages/backend/src/worker.ts +++ b/packages/backend/src/worker.ts @@ -9,6 +9,8 @@ import './workers/trigger'; import './workers/action'; import './workers/email'; import './workers/delete-user.ee'; +import './workers/remove-cancelled-subscriptions.ee'; +import './queues/remove-cancelled-subscriptions.ee'; import telemetry from './helpers/telemetry'; telemetry.setServiceType('worker'); diff --git a/packages/backend/src/workers/remove-cancelled-subscriptions.ee.ts b/packages/backend/src/workers/remove-cancelled-subscriptions.ee.ts new file mode 100644 index 00000000..91430a6b --- /dev/null +++ b/packages/backend/src/workers/remove-cancelled-subscriptions.ee.ts @@ -0,0 +1,46 @@ +import { Worker } from 'bullmq'; +import { DateTime } from 'luxon'; +import * as Sentry from '../helpers/sentry.ee'; +import redisConfig from '../config/redis'; +import logger from '../helpers/logger'; +import Subscription from '../models/subscription.ee'; + +export const worker = new Worker( + 'remove-cancelled-subscriptions', + async () => { + await Subscription.query() + .delete() + .where({ + status: 'cancelled', + }) + .andWhere( + 'cancellation_effective_date', + '<=', + DateTime.now().startOf('day').toISODate() + ); + }, + { connection: redisConfig } +); + +worker.on('completed', (job) => { + logger.info( + `JOB ID: ${job.id} - The cancelled subscriptions have been removed!` + ); +}); + +worker.on('failed', (job, err) => { + const errorMessage = ` + JOB ID: ${job.id} - ERROR: The cancelled subscriptions can not be removed! ${err.message} + \n ${err.stack} + `; + logger.error(errorMessage); + Sentry.captureException(err, { + extra: { + jobId: job.id, + }, + }); +}); + +process.on('SIGTERM', async () => { + await worker.close(); +});