diff --git a/packages/server/src/api/middleware/SubscriptionMiddleware.ts b/packages/server/src/api/middleware/SubscriptionMiddleware.ts index 6d840c666..16b758478 100644 --- a/packages/server/src/api/middleware/SubscriptionMiddleware.ts +++ b/packages/server/src/api/middleware/SubscriptionMiddleware.ts @@ -1,6 +1,9 @@ import { Container } from 'typedi'; import { Request, Response, NextFunction } from 'express'; +const SupportedMethods = ['POST', 'PUT']; +const Excluded = []; + export default (subscriptionSlug = 'main') => async (req: Request, res: Response, next: NextFunction) => { const { tenant, tenantId } = req; @@ -19,8 +22,7 @@ export default (subscriptionSlug = 'main') => errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }], }); } - // Validate in case the subscription is inactive. - else if (subscription.inactive()) { + if (SupportedMethods.includes(req.method) && subscription.inactive()) { return res.boom.badRequest(null, { errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }], }); diff --git a/packages/server/src/system/models/Subscriptions/PlanSubscription.ts b/packages/server/src/system/models/Subscriptions/PlanSubscription.ts index d7c988d8f..ff1f7d4fd 100644 --- a/packages/server/src/system/models/Subscriptions/PlanSubscription.ts +++ b/packages/server/src/system/models/Subscriptions/PlanSubscription.ts @@ -2,6 +2,7 @@ import { Model, mixin } from 'objection'; import SystemModel from '@/system/models/SystemModel'; import moment from 'moment'; import SubscriptionPeriod from '@/services/Subscription/SubscriptionPeriod'; +import { SubscriptionPaymentStatus } from '@/interfaces'; export default class PlanSubscription extends mixin(SystemModel) { public lemonSubscriptionId: number; @@ -13,6 +14,8 @@ export default class PlanSubscription extends mixin(SystemModel) { public trialEndsAt: Date; + public paymentStatus: SubscriptionPaymentStatus; + /** * Table name. */ @@ -31,7 +34,16 @@ export default class PlanSubscription extends mixin(SystemModel) { * Defined virtual attributes. */ static get virtualAttributes() { - return ['active', 'inactive', 'ended', 'canceled', 'onTrial', 'status']; + return [ + 'active', + 'inactive', + 'ended', + 'canceled', + 'onTrial', + 'status', + 'isPaymentFailed', + 'isPaymentSucceed', + ]; } /** @@ -69,6 +81,22 @@ export default class PlanSubscription extends mixin(SystemModel) { builder.where('trial_ends_at', '<=', endDate); }, + + /** + * Filter the failed payment. + * @param builder + */ + failedPayment(builder) { + builder.where('payment_status', SubscriptionPaymentStatus.Failed); + }, + + /** + * Filter the succeed payment. + * @param builder + */ + succeedPayment(builder) { + builder.where('payment_status', SubscriptionPaymentStatus.Succeed); + }, }; } @@ -108,6 +136,10 @@ export default class PlanSubscription extends mixin(SystemModel) { /** * Check if the subscription is active. + * Crtiria should be: + * - During the trial period and NOT canceled. + * - Not ended. + * * @return {Boolean} */ public active() { @@ -200,4 +232,20 @@ export default class PlanSubscription extends mixin(SystemModel) { ); return this.$query().update({ startsAt, endsAt }); } + + /** + * Detarmines the subscription payment whether is failed. + * @returns {boolean} + */ + public isPaymentFailed() { + return this.paymentStatus === SubscriptionPaymentStatus.Failed; + } + + /** + * Detarmines the subscription payment whether is succeed. + * @returns {boolean} + */ + public isPaymentSucceed() { + return this.paymentStatus === SubscriptionPaymentStatus.Succeed; + } } diff --git a/packages/server/src/system/repositories/SubscriptionRepository.ts b/packages/server/src/system/repositories/SubscriptionRepository.ts index 44962b0b8..004d501cb 100644 --- a/packages/server/src/system/repositories/SubscriptionRepository.ts +++ b/packages/server/src/system/repositories/SubscriptionRepository.ts @@ -15,12 +15,8 @@ export default class SubscriptionRepository extends SystemRepository { * @param {number} tenantId */ getBySlugInTenant(slug: string, tenantId: number) { - const cacheKey = this.getCacheKey('getBySlugInTenant', slug, tenantId); - - return this.cache.get(cacheKey, () => { - return PlanSubscription.query() - .findOne('slug', slug) - .where('tenant_id', tenantId); - }); + return PlanSubscription.query() + .findOne('slug', slug) + .where('tenant_id', tenantId); } } diff --git a/packages/webapp/src/containers/GlobalErrors/GlobalErrors.tsx b/packages/webapp/src/containers/GlobalErrors/GlobalErrors.tsx index e972733d8..c4907dd33 100644 --- a/packages/webapp/src/containers/GlobalErrors/GlobalErrors.tsx +++ b/packages/webapp/src/containers/GlobalErrors/GlobalErrors.tsx @@ -77,6 +77,15 @@ function GlobalErrors({ }, }); } + if (globalErrors.subscriptionInactive) { + AppToaster.show({ + message: `You can't add new data to Bigcapital because your subscription is inactive. Make sure your billing information is up-to-date from Preferences > Billing page.`, + intent: Intent.DANGER, + onDismiss: () => { + globalErrorsSet({ subscriptionInactive: false }); + }, + }); + } if (globalErrors.userInactive) { AppToaster.show({ message: intl.get('global_error.authorized_user_inactive'), diff --git a/packages/webapp/src/hooks/useRequest.tsx b/packages/webapp/src/hooks/useRequest.tsx index 3ce81ae3c..542ccb81d 100644 --- a/packages/webapp/src/hooks/useRequest.tsx +++ b/packages/webapp/src/hooks/useRequest.tsx @@ -64,12 +64,20 @@ export default function useApiRequest() { setGlobalErrors({ too_many_requests: true }); } if (status === 400) { - const lockedError = data.errors.find( - (error) => error.type === 'TRANSACTIONS_DATE_LOCKED', - ); - if (lockedError) { + if ( + data.errors.find( + (error) => error.type === 'TRANSACTIONS_DATE_LOCKED', + ) + ) { setGlobalErrors({ transactionsLocked: { ...lockedError.data } }); } + if ( + data.errors.find( + (e) => e.type === 'ORGANIZATION.SUBSCRIPTION.INACTIVE', + ) + ) { + setGlobalErrors({ subscriptionInactive: true }); + } if (data.errors.find((e) => e.type === 'USER_INACTIVE')) { setGlobalErrors({ userInactive: true }); setLogout();