From e486333c96fbf4e4b2e33d27892e904cff31d38e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 14 Apr 2024 12:44:02 +0200 Subject: [PATCH] feat: sweep up the Lemon Squeezy integration --- .../src/api/controllers/Webhooks/Webhooks.ts | 2 +- .../api/middleware/SubscriptionMiddleware.ts | 62 ++++----- packages/server/src/config/index.ts | 11 +- .../NoPaymentModelWithPricedPlan.ts | 8 -- .../PaymentAmountInvalidWithPlan.ts | 7 - .../src/exceptions/PaymentInputInvalid.ts | 3 - .../src/exceptions/VoucherCodeRequired.ts | 5 - packages/server/src/exceptions/index.ts | 10 +- .../src/jobs/MailNotificationSubscribeEnd.ts | 34 ----- .../src/jobs/MailNotificationTrialEnd.ts | 34 ----- .../src/jobs/SMSNotificationSubscribeEnd.ts | 28 ---- .../src/jobs/SMSNotificationTrialEnd.ts | 28 ---- packages/server/src/loaders/jobs.ts | 24 ---- .../src/services/Subscription/MailMessages.ts | 30 ---- .../src/services/Subscription/SMSMessages.ts | 40 ------ .../server/src/services/Subscription/utils.ts | 6 +- .../system/models/Subscriptions/License.ts | 129 ------------------ 17 files changed, 39 insertions(+), 422 deletions(-) delete mode 100644 packages/server/src/exceptions/NoPaymentModelWithPricedPlan.ts delete mode 100644 packages/server/src/exceptions/PaymentAmountInvalidWithPlan.ts delete mode 100644 packages/server/src/exceptions/PaymentInputInvalid.ts delete mode 100644 packages/server/src/exceptions/VoucherCodeRequired.ts delete mode 100644 packages/server/src/jobs/MailNotificationSubscribeEnd.ts delete mode 100644 packages/server/src/jobs/MailNotificationTrialEnd.ts delete mode 100644 packages/server/src/jobs/SMSNotificationSubscribeEnd.ts delete mode 100644 packages/server/src/jobs/SMSNotificationTrialEnd.ts delete mode 100644 packages/server/src/services/Subscription/MailMessages.ts delete mode 100644 packages/server/src/services/Subscription/SMSMessages.ts delete mode 100644 packages/server/src/system/models/Subscriptions/License.ts diff --git a/packages/server/src/api/controllers/Webhooks/Webhooks.ts b/packages/server/src/api/controllers/Webhooks/Webhooks.ts index 662eff4ed..4ff3acc76 100644 --- a/packages/server/src/api/controllers/Webhooks/Webhooks.ts +++ b/packages/server/src/api/controllers/Webhooks/Webhooks.ts @@ -29,7 +29,7 @@ export class Webhooks extends BaseController { } /** - * Listens to LemonSqueezy webhooks events. + * Listens to Lemon Squeezy webhooks events. * @param {Request} req * @param {Response} res * @returns {Response} diff --git a/packages/server/src/api/middleware/SubscriptionMiddleware.ts b/packages/server/src/api/middleware/SubscriptionMiddleware.ts index ce7d45258..6d840c666 100644 --- a/packages/server/src/api/middleware/SubscriptionMiddleware.ts +++ b/packages/server/src/api/middleware/SubscriptionMiddleware.ts @@ -1,41 +1,29 @@ -import { Request, Response, NextFunction } from 'express'; import { Container } from 'typedi'; +import { Request, Response, NextFunction } from 'express'; -export default (subscriptionSlug = 'main') => async ( - req: Request, - res: Response, - next: NextFunction -) => { - const { tenant, tenantId } = req; - const Logger = Container.get('logger'); - const { subscriptionRepository } = Container.get('repositories'); +export default (subscriptionSlug = 'main') => + async (req: Request, res: Response, next: NextFunction) => { + const { tenant, tenantId } = req; + const { subscriptionRepository } = Container.get('repositories'); - if (!tenant) { - throw new Error('Should load `TenancyMiddlware` before this middleware.'); - } - Logger.info('[subscription_middleware] trying get tenant main subscription.'); - const subscription = await subscriptionRepository.getBySlugInTenant( - subscriptionSlug, - tenantId - ); - // Validate in case there is no any already subscription. - if (!subscription) { - Logger.info('[subscription_middleware] tenant has no subscription.', { - tenantId, - }); - return res.boom.badRequest('Tenant has no subscription.', { - errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }], - }); - } - // Validate in case the subscription is inactive. - else if (subscription.inactive()) { - Logger.info( - '[subscription_middleware] tenant main subscription is expired.', - { tenantId } + if (!tenant) { + throw new Error('Should load `TenancyMiddlware` before this middleware.'); + } + const subscription = await subscriptionRepository.getBySlugInTenant( + subscriptionSlug, + tenantId ); - return res.boom.badRequest(null, { - errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }], - }); - } - next(); -}; + // Validate in case there is no any already subscription. + if (!subscription) { + return res.boom.badRequest('Tenant has no subscription.', { + errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }], + }); + } + // Validate in case the subscription is inactive. + else if (subscription.inactive()) { + return res.boom.badRequest(null, { + errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }], + }); + } + next(); + }; diff --git a/packages/server/src/config/index.ts b/packages/server/src/config/index.ts index 12938038f..175056d7a 100644 --- a/packages/server/src/config/index.ts +++ b/packages/server/src/config/index.ts @@ -190,6 +190,15 @@ module.exports = { secretSandbox: process.env.PLAID_SECRET_SANDBOX, redirectSandBox: process.env.PLAID_SANDBOX_REDIRECT_URI, redirectDevelopment: process.env.PLAID_DEVELOPMENT_REDIRECT_URI, - linkWebhook: process.env.PLAID_LINK_WEBHOOK + linkWebhook: process.env.PLAID_LINK_WEBHOOK, + }, + + /** + * Lemon Squeezy. + */ + lemonSqueezy: { + key: process.env.LEMONSQUEEZY_API_KEY, + storeId: process.env.LEMONSQUEEZY_STORE_ID, + webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET, }, }; diff --git a/packages/server/src/exceptions/NoPaymentModelWithPricedPlan.ts b/packages/server/src/exceptions/NoPaymentModelWithPricedPlan.ts deleted file mode 100644 index 938ec8b4a..000000000 --- a/packages/server/src/exceptions/NoPaymentModelWithPricedPlan.ts +++ /dev/null @@ -1,8 +0,0 @@ - - -export default class NoPaymentModelWithPricedPlan { - - constructor() { - - } -} \ No newline at end of file diff --git a/packages/server/src/exceptions/PaymentAmountInvalidWithPlan.ts b/packages/server/src/exceptions/PaymentAmountInvalidWithPlan.ts deleted file mode 100644 index 834e8cbe1..000000000 --- a/packages/server/src/exceptions/PaymentAmountInvalidWithPlan.ts +++ /dev/null @@ -1,7 +0,0 @@ - - -export default class PaymentAmountInvalidWithPlan{ - constructor() { - - } -} \ No newline at end of file diff --git a/packages/server/src/exceptions/PaymentInputInvalid.ts b/packages/server/src/exceptions/PaymentInputInvalid.ts deleted file mode 100644 index 2a024ff05..000000000 --- a/packages/server/src/exceptions/PaymentInputInvalid.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default class PaymentInputInvalid { - constructor() {} -} diff --git a/packages/server/src/exceptions/VoucherCodeRequired.ts b/packages/server/src/exceptions/VoucherCodeRequired.ts deleted file mode 100644 index a3bef6ff2..000000000 --- a/packages/server/src/exceptions/VoucherCodeRequired.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default class VoucherCodeRequired { - constructor() { - this.name = 'VoucherCodeRequired'; - } -} diff --git a/packages/server/src/exceptions/index.ts b/packages/server/src/exceptions/index.ts index a18746d02..f04873ba5 100644 --- a/packages/server/src/exceptions/index.ts +++ b/packages/server/src/exceptions/index.ts @@ -1,25 +1,17 @@ import NotAllowedChangeSubscriptionPlan from './NotAllowedChangeSubscriptionPlan'; import ServiceError from './ServiceError'; import ServiceErrors from './ServiceErrors'; -import NoPaymentModelWithPricedPlan from './NoPaymentModelWithPricedPlan'; -import PaymentInputInvalid from './PaymentInputInvalid'; -import PaymentAmountInvalidWithPlan from './PaymentAmountInvalidWithPlan'; import TenantAlreadyInitialized from './TenantAlreadyInitialized'; import TenantAlreadySeeded from './TenantAlreadySeeded'; import TenantDBAlreadyExists from './TenantDBAlreadyExists'; import TenantDatabaseNotBuilt from './TenantDatabaseNotBuilt'; -import VoucherCodeRequired from './VoucherCodeRequired'; export { NotAllowedChangeSubscriptionPlan, - NoPaymentModelWithPricedPlan, - PaymentAmountInvalidWithPlan, ServiceError, ServiceErrors, - PaymentInputInvalid, TenantAlreadyInitialized, TenantAlreadySeeded, TenantDBAlreadyExists, TenantDatabaseNotBuilt, - VoucherCodeRequired, -}; \ No newline at end of file +}; diff --git a/packages/server/src/jobs/MailNotificationSubscribeEnd.ts b/packages/server/src/jobs/MailNotificationSubscribeEnd.ts deleted file mode 100644 index a2b54778b..000000000 --- a/packages/server/src/jobs/MailNotificationSubscribeEnd.ts +++ /dev/null @@ -1,34 +0,0 @@ -import Container from 'typedi'; -import SubscriptionService from '@/services/Subscription/Subscription'; - -export default class MailNotificationSubscribeEnd { - /** - * Job handler. - * @param {Job} job - - */ - handler(job) { - const { tenantId, phoneNumber, remainingDays } = job.attrs.data; - - const subscriptionService = Container.get(SubscriptionService); - const Logger = Container.get('logger'); - - Logger.info( - `Send mail notification subscription end soon - started: ${job.attrs.data}` - ); - - try { - subscriptionService.mailMessages.sendRemainingTrialPeriod( - phoneNumber, - remainingDays - ); - Logger.info( - `Send mail notification subscription end soon - finished: ${job.attrs.data}` - ); - } catch (error) { - Logger.info( - `Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}` - ); - done(e); - } - } -} diff --git a/packages/server/src/jobs/MailNotificationTrialEnd.ts b/packages/server/src/jobs/MailNotificationTrialEnd.ts deleted file mode 100644 index 82d8bd53c..000000000 --- a/packages/server/src/jobs/MailNotificationTrialEnd.ts +++ /dev/null @@ -1,34 +0,0 @@ -import Container from 'typedi'; -import SubscriptionService from '@/services/Subscription/Subscription'; - -export default class MailNotificationTrialEnd { - /** - * - * @param {Job} job - - */ - handler(job) { - const { tenantId, phoneNumber, remainingDays } = job.attrs.data; - - const subscriptionService = Container.get(SubscriptionService); - const Logger = Container.get('logger'); - - Logger.info( - `Send mail notification subscription end soon - started: ${job.attrs.data}` - ); - - try { - subscriptionService.mailMessages.sendRemainingTrialPeriod( - phoneNumber, - remainingDays - ); - Logger.info( - `Send mail notification subscription end soon - finished: ${job.attrs.data}` - ); - } catch (error) { - Logger.info( - `Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}` - ); - done(e); - } - } -} diff --git a/packages/server/src/jobs/SMSNotificationSubscribeEnd.ts b/packages/server/src/jobs/SMSNotificationSubscribeEnd.ts deleted file mode 100644 index d203c1d6b..000000000 --- a/packages/server/src/jobs/SMSNotificationSubscribeEnd.ts +++ /dev/null @@ -1,28 +0,0 @@ -import Container from 'typedi'; -import SubscriptionService from '@/services/Subscription/Subscription'; - -export default class SMSNotificationSubscribeEnd { - - /** - * - * @param {Job}job - */ - handler(job) { - const { tenantId, phoneNumber, remainingDays } = job.attrs.data; - - const subscriptionService = Container.get(SubscriptionService); - const Logger = Container.get('logger'); - - Logger.info(`Send SMS notification subscription end soon - started: ${job.attrs.data}`); - - try { - subscriptionService.smsMessages.sendRemainingSubscriptionPeriod( - phoneNumber, remainingDays, - ); - Logger.info(`Send SMS notification subscription end soon - finished: ${job.attrs.data}`); - } catch(error) { - Logger.info(`Send SMS notification subscription end soon - failed: ${job.attrs.data}, error: ${e}`); - done(e); - } - } -} \ No newline at end of file diff --git a/packages/server/src/jobs/SMSNotificationTrialEnd.ts b/packages/server/src/jobs/SMSNotificationTrialEnd.ts deleted file mode 100644 index a3e5c5420..000000000 --- a/packages/server/src/jobs/SMSNotificationTrialEnd.ts +++ /dev/null @@ -1,28 +0,0 @@ -import Container from 'typedi'; -import SubscriptionService from '@/services/Subscription/Subscription'; - -export default class SMSNotificationTrialEnd { - - /** - * - * @param {Job}job - */ - handler(job) { - const { tenantId, phoneNumber, remainingDays } = job.attrs.data; - - const subscriptionService = Container.get(SubscriptionService); - const Logger = Container.get('logger'); - - Logger.info(`Send notification subscription end soon - started: ${job.attrs.data}`); - - try { - subscriptionService.smsMessages.sendRemainingTrialPeriod( - phoneNumber, remainingDays, - ); - Logger.info(`Send notification subscription end soon - finished: ${job.attrs.data}`); - } catch(error) { - Logger.info(`Send notification subscription end soon - failed: ${job.attrs.data}, error: ${e}`); - done(e); - } - } -} \ No newline at end of file diff --git a/packages/server/src/loaders/jobs.ts b/packages/server/src/loaders/jobs.ts index db232656b..075005bf1 100644 --- a/packages/server/src/loaders/jobs.ts +++ b/packages/server/src/loaders/jobs.ts @@ -2,10 +2,6 @@ import Agenda from 'agenda'; import ResetPasswordMailJob from 'jobs/ResetPasswordMail'; import ComputeItemCost from 'jobs/ComputeItemCost'; import RewriteInvoicesJournalEntries from 'jobs/WriteInvoicesJEntries'; -import SendSMSNotificationSubscribeEnd from 'jobs/SMSNotificationSubscribeEnd'; -import SendSMSNotificationTrialEnd from 'jobs/SMSNotificationTrialEnd'; -import SendMailNotificationSubscribeEnd from 'jobs/MailNotificationSubscribeEnd'; -import SendMailNotificationTrialEnd from 'jobs/MailNotificationTrialEnd'; import UserInviteMailJob from 'jobs/UserInviteMail'; import OrganizationSetupJob from 'jobs/OrganizationSetup'; import OrganizationUpgrade from 'jobs/OrganizationUpgrade'; @@ -35,25 +31,5 @@ export default ({ agenda }: { agenda: Agenda }) => { agenda.start().then(() => { agenda.every('1 hours', 'delete-expired-imported-files', {}); }); - // agenda.define( - // 'send-sms-notification-subscribe-end', - // { priority: 'nromal', concurrency: 1, }, - // new SendSMSNotificationSubscribeEnd().handler, - // ); - // agenda.define( - // 'send-sms-notification-trial-end', - // { priority: 'normal', concurrency: 1, }, - // new SendSMSNotificationTrialEnd().handler, - // ); - // agenda.define( - // 'send-mail-notification-subscribe-end', - // { priority: 'high', concurrency: 1, }, - // new SendMailNotificationSubscribeEnd().handler - // ); - // agenda.define( - // 'send-mail-notification-trial-end', - // { priority: 'high', concurrency: 1, }, - // new SendMailNotificationTrialEnd().handler - // ); agenda.start(); }; diff --git a/packages/server/src/services/Subscription/MailMessages.ts b/packages/server/src/services/Subscription/MailMessages.ts deleted file mode 100644 index 4c50a5243..000000000 --- a/packages/server/src/services/Subscription/MailMessages.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Service } from "typedi"; - -@Service() -export default class SubscriptionMailMessages { - /** - * - * @param phoneNumber - * @param remainingDays - */ - public async sendRemainingSubscriptionPeriod(phoneNumber: string, remainingDays: number) { - const message: string = ` - Your remaining subscription is ${remainingDays} days, - please renew your subscription before expire. - `; - this.smsClient.sendMessage(phoneNumber, message); - } - - /** - * - * @param phoneNumber - * @param remainingDays - */ - public async sendRemainingTrialPeriod(phoneNumber: string, remainingDays: number) { - const message: string = ` - Your remaining free trial is ${remainingDays} days, - please subscription before ends, if you have any quation to contact us.`; - - this.smsClient.sendMessage(phoneNumber, message); - } -} \ No newline at end of file diff --git a/packages/server/src/services/Subscription/SMSMessages.ts b/packages/server/src/services/Subscription/SMSMessages.ts deleted file mode 100644 index 9cb7de273..000000000 --- a/packages/server/src/services/Subscription/SMSMessages.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Service, Inject } from 'typedi'; -import SMSClient from '@/services/SMSClient'; - -@Service() -export default class SubscriptionSMSMessages { - @Inject('SMSClient') - smsClient: SMSClient; - - /** - * Send remaining subscription period SMS message. - * @param {string} phoneNumber - - * @param {number} remainingDays - - */ - public async sendRemainingSubscriptionPeriod( - phoneNumber: string, - remainingDays: number - ): Promise { - const message: string = ` - Your remaining subscription is ${remainingDays} days, - please renew your subscription before expire. - `; - this.smsClient.sendMessage(phoneNumber, message); - } - - /** - * Send remaining trial period SMS message. - * @param {string} phoneNumber - - * @param {number} remainingDays - - */ - public async sendRemainingTrialPeriod( - phoneNumber: string, - remainingDays: number - ): Promise { - const message: string = ` - Your remaining free trial is ${remainingDays} days, - please subscription before ends, if you have any quation to contact us.`; - - this.smsClient.sendMessage(phoneNumber, message); - } -} diff --git a/packages/server/src/services/Subscription/utils.ts b/packages/server/src/services/Subscription/utils.ts index b41e8ce5e..56dc7e4b4 100644 --- a/packages/server/src/services/Subscription/utils.ts +++ b/packages/server/src/services/Subscription/utils.ts @@ -23,11 +23,9 @@ export function configureLemonSqueezy() { lemonSqueezySetup({ apiKey: process.env.LEMONSQUEEZY_API_KEY, onError: (error) => { - console.log(error); - // console.log('LL', error.message); // eslint-disable-next-line no-console -- allow logging - // console.error(error); - // throw new Error(`Lemon Squeezy API error: ${error.message}`); + console.error(error); + throw new Error(`Lemon Squeezy API error: ${error.message}`); }, }); } diff --git a/packages/server/src/system/models/Subscriptions/License.ts b/packages/server/src/system/models/Subscriptions/License.ts deleted file mode 100644 index 97bbc87a7..000000000 --- a/packages/server/src/system/models/Subscriptions/License.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Model, mixin } from 'objection'; -import moment from 'moment'; -import SystemModel from '@/system/models/SystemModel'; - -export default class License extends SystemModel { - /** - * Table name. - */ - static get tableName() { - return 'subscription_licenses'; - } - - /** - * Timestamps columns. - */ - get timestamps() { - return ['createdAt', 'updatedAt']; - } - - /** - * Model modifiers. - */ - static get modifiers() { - return { - // Filters active licenses. - filterActiveLicense(query) { - query.where('disabled_at', null); - query.where('used_at', null); - }, - - // Find license by its code or id. - findByCodeOrId(query, id, code) { - if (id) { - query.where('id', id); - } - if (code) { - query.where('license_code', code); - } - }, - - // Filters licenses list. - filter(builder, licensesFilter) { - if (licensesFilter.active) { - builder.modify('filterActiveLicense'); - } - if (licensesFilter.disabled) { - builder.whereNot('disabled_at', null); - } - if (licensesFilter.used) { - builder.whereNot('used_at', null); - } - if (licensesFilter.sent) { - builder.whereNot('sent_at', null); - } - }, - }; - } - - /** - * Relationship mapping. - */ - static get relationMappings() { - const Plan = require('system/models/Subscriptions/Plan'); - - return { - plan: { - relation: Model.BelongsToOneRelation, - modelClass: Plan.default, - join: { - from: 'subscription_licenses.planId', - to: 'subscriptions_plans.id', - }, - }, - }; - } - - /** - * Deletes the given license code from the storage. - * @param {string} licenseCode - * @return {Promise} - */ - static deleteLicense(licenseCode, viaAttribute = 'license_code') { - return this.query().where(viaAttribute, licenseCode).delete(); - } - - /** - * Marks the given license code as disabled on the storage. - * @param {string} licenseCode - * @return {Promise} - */ - static markLicenseAsDisabled(licenseCode, viaAttribute = 'license_code') { - return this.query().where(viaAttribute, licenseCode).patch({ - disabled_at: moment().toMySqlDateTime(), - }); - } - - /** - * Marks the given license code as sent on the storage. - * @param {string} licenseCode - */ - static markLicenseAsSent(licenseCode, viaAttribute = 'license_code') { - return this.query().where(viaAttribute, licenseCode).patch({ - sent_at: moment().toMySqlDateTime(), - }); - } - - /** - * Marks the given license code as used on the storage. - * @param {string} licenseCode - * @return {Promise} - */ - static markLicenseAsUsed(licenseCode, viaAttribute = 'license_code') { - return this.query().where(viaAttribute, licenseCode).patch({ - used_at: moment().toMySqlDateTime(), - }); - } - - /** - * - * @param {IIPlan} plan - * @return {boolean} - */ - isEqualPlanPeriod(plan) { - return ( - this.invoicePeriod === plan.invoiceInterval && - license.licensePeriod === license.periodInterval - ); - } -}