Skip to content

Commit

Permalink
Merge pull request #624 from bigcapitalhq/subscription-middleware
Browse files Browse the repository at this point in the history
fix: Subscription middleware
  • Loading branch information
abouolia authored Aug 30, 2024
2 parents c986585 + 410c4ea commit 2227cea
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 15 deletions.
8 changes: 6 additions & 2 deletions packages/server/src/api/middleware/SubscriptionMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Container } from 'typedi';
import { Request, Response, NextFunction } from 'express';

const SupportedMethods = ['POST', 'PUT'];

export default (subscriptionSlug = 'main') =>
async (req: Request, res: Response, next: NextFunction) => {
const { tenant, tenantId } = req;
Expand All @@ -19,8 +21,10 @@ export default (subscriptionSlug = 'main') =>
errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }],
});
}
// Validate in case the subscription is inactive.
else if (subscription.inactive()) {
const isMethodSupported = SupportedMethods.includes(req.method);
const isSubscriptionInactive = subscription.inactive();

if (isMethodSupported && isSubscriptionInactive) {
return res.boom.badRequest(null, {
errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,6 +14,8 @@ export default class PlanSubscription extends mixin(SystemModel) {

public trialEndsAt: Date;

public paymentStatus: SubscriptionPaymentStatus;

/**
* Table name.
*/
Expand All @@ -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',
];
}

/**
Expand Down Expand Up @@ -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);
},
};
}

Expand Down Expand Up @@ -108,10 +136,13 @@ export default class PlanSubscription extends mixin(SystemModel) {

/**
* Check if the subscription is active.
* Crtiria should be active:
* - During the trial period should NOT be canceled.
* - Out of trial period should NOT be ended.
* @return {Boolean}
*/
public active() {
return this.onTrial() || !this.ended();
return this.onTrial() ? !this.canceled() : !this.ended();
}

/**
Expand Down Expand Up @@ -200,4 +231,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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
9 changes: 9 additions & 0 deletions packages/webapp/src/containers/GlobalErrors/GlobalErrors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
16 changes: 12 additions & 4 deletions packages/webapp/src/hooks/useRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 2227cea

Please sign in to comment.