mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
feat: subscription period based on the license code.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { Router, Request, Response } from 'express';
|
import { NextFunction, Router, Request, Response } from 'express';
|
||||||
import { check } from 'express-validator';
|
import { check } from 'express-validator';
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
import PaymentMethodController from 'api/controllers/Subscription/PaymentMethod';
|
import PaymentMethodController from 'api/controllers/Subscription/PaymentMethod';
|
||||||
@@ -8,13 +8,13 @@ import {
|
|||||||
NoPaymentModelWithPricedPlan,
|
NoPaymentModelWithPricedPlan,
|
||||||
PaymentAmountInvalidWithPlan,
|
PaymentAmountInvalidWithPlan,
|
||||||
PaymentInputInvalid,
|
PaymentInputInvalid,
|
||||||
VoucherCodeRequired
|
VoucherCodeRequired,
|
||||||
} from 'exceptions';
|
} from 'exceptions';
|
||||||
import { ILicensePaymentModel } from 'interfaces';
|
import { ILicensePaymentModel } from 'interfaces';
|
||||||
import instance from 'tsyringe/dist/typings/dependency-container';
|
import instance from 'tsyringe/dist/typings/dependency-container';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class PaymentViaLicenseController extends PaymentMethodController {
|
export default class PaymentViaLicenseController extends PaymentMethodController {
|
||||||
@Inject('logger')
|
@Inject('logger')
|
||||||
logger: any;
|
logger: any;
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
|||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validatePlanSlugExistance.bind(this)),
|
asyncMiddleware(this.validatePlanSlugExistance.bind(this)),
|
||||||
asyncMiddleware(this.paymentViaLicense.bind(this)),
|
asyncMiddleware(this.paymentViaLicense.bind(this)),
|
||||||
|
this.handleErrors,
|
||||||
);
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -40,14 +41,14 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
|||||||
get paymentViaLicenseSchema() {
|
get paymentViaLicenseSchema() {
|
||||||
return [
|
return [
|
||||||
check('plan_slug').exists().trim().escape(),
|
check('plan_slug').exists().trim().escape(),
|
||||||
check('license_code').optional().trim().escape(),
|
check('license_code').exists().trim().escape(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the subscription payment via license code.
|
* Handle the subscription payment via license code.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async paymentViaLicense(req: Request, res: Response, next: Function) {
|
async paymentViaLicense(req: Request, res: Response, next: Function) {
|
||||||
@@ -55,11 +56,13 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
|||||||
const { tenant } = req;
|
const { tenant } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const licenseModel: ILicensePaymentModel|null = licenseCode
|
const licenseModel: ILicensePaymentModel = { licenseCode };
|
||||||
? { licenseCode } : null;
|
|
||||||
|
|
||||||
await this.subscriptionService
|
await this.subscriptionService.subscriptionViaLicense(
|
||||||
.subscriptionViaLicense(tenant.id, planSlug, licenseModel);
|
tenant.id,
|
||||||
|
planSlug,
|
||||||
|
licenseModel
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@@ -67,39 +70,56 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
|||||||
message: 'Payment via license has been made successfully.',
|
message: 'Payment via license has been made successfully.',
|
||||||
});
|
});
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
const errorReasons = [];
|
|
||||||
|
|
||||||
if (exception instanceof VoucherCodeRequired) {
|
|
||||||
errorReasons.push({
|
|
||||||
type: 'VOUCHER_CODE_REQUIRED', code: 100,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (exception instanceof NoPaymentModelWithPricedPlan) {
|
|
||||||
errorReasons.push({
|
|
||||||
type: 'NO_PAYMENT_WITH_PRICED_PLAN',
|
|
||||||
code: 140,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (exception instanceof NotAllowedChangeSubscriptionPlan) {
|
|
||||||
errorReasons.push({
|
|
||||||
type: 'NOT.ALLOWED.RENEW.SUBSCRIPTION.WHILE.ACTIVE',
|
|
||||||
code: 120,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (errorReasons.length > 0) {
|
|
||||||
return res.status(400).send({ errors: errorReasons });
|
|
||||||
}
|
|
||||||
if (exception instanceof PaymentInputInvalid) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'LICENSE.CODE.IS.INVALID', code: 120 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (exception instanceof PaymentAmountInvalidWithPlan) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'LICENSE.NOT.FOR.GIVEN.PLAN' }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next(exception);
|
next(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Handle service errors.
|
||||||
|
* @param {Error} error
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
private handleErrors(
|
||||||
|
exception: Error,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const errorReasons = [];
|
||||||
|
|
||||||
|
if (exception instanceof VoucherCodeRequired) {
|
||||||
|
errorReasons.push({
|
||||||
|
type: 'VOUCHER_CODE_REQUIRED',
|
||||||
|
code: 100,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (exception instanceof NoPaymentModelWithPricedPlan) {
|
||||||
|
errorReasons.push({
|
||||||
|
type: 'NO_PAYMENT_WITH_PRICED_PLAN',
|
||||||
|
code: 140,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (exception instanceof NotAllowedChangeSubscriptionPlan) {
|
||||||
|
errorReasons.push({
|
||||||
|
type: 'NOT.ALLOWED.RENEW.SUBSCRIPTION.WHILE.ACTIVE',
|
||||||
|
code: 120,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (errorReasons.length > 0) {
|
||||||
|
return res.status(400).send({ errors: errorReasons });
|
||||||
|
}
|
||||||
|
if (exception instanceof PaymentInputInvalid) {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{ type: 'LICENSE.CODE.IS.INVALID', code: 120 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (exception instanceof PaymentAmountInvalidWithPlan) {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{ type: 'LICENSE.NOT.FOR.GIVEN.PLAN' }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
export default class NotAllowedChangeSubscriptionPlan {
|
export default class NotAllowedChangeSubscriptionPlan {
|
||||||
|
|
||||||
constructor(message: string) {
|
constructor() {
|
||||||
this.name = "NotAllowedChangeSubscriptionPlan";
|
this.name = "NotAllowedChangeSubscriptionPlan";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
} from 'decorators/eventDispatcher';
|
} from 'decorators/eventDispatcher';
|
||||||
import { PasswordReset } from 'system/models';
|
import { PasswordReset, Tenant } from 'system/models';
|
||||||
import {
|
import {
|
||||||
IRegisterDTO,
|
IRegisterDTO,
|
||||||
ITenant,
|
ITenant,
|
||||||
@@ -117,7 +117,7 @@ export default class AuthenticationService implements IAuthenticationService {
|
|||||||
password,
|
password,
|
||||||
user,
|
user,
|
||||||
});
|
});
|
||||||
const tenant = await user.$relatedQuery('tenant');
|
const tenant = await Tenant.query().findById(user.tenantId).withGraphFetched('metadata');
|
||||||
|
|
||||||
// Keep the user object immutable.
|
// Keep the user object immutable.
|
||||||
const outputUser = cloneDeep(user);
|
const outputUser = cloneDeep(user);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { License } from 'system/models';
|
|||||||
import PaymentMethod from 'services/Payment/PaymentMethod';
|
import PaymentMethod from 'services/Payment/PaymentMethod';
|
||||||
import { Plan } from 'system/models';
|
import { Plan } from 'system/models';
|
||||||
import { IPaymentMethod, ILicensePaymentModel } from 'interfaces';
|
import { IPaymentMethod, ILicensePaymentModel } from 'interfaces';
|
||||||
import { ILicensePaymentModel } from 'interfaces';
|
|
||||||
import {
|
import {
|
||||||
PaymentInputInvalid,
|
PaymentInputInvalid,
|
||||||
PaymentAmountInvalidWithPlan,
|
PaymentAmountInvalidWithPlan,
|
||||||
@@ -11,12 +10,13 @@ import {
|
|||||||
|
|
||||||
export default class LicensePaymentMethod
|
export default class LicensePaymentMethod
|
||||||
extends PaymentMethod
|
extends PaymentMethod
|
||||||
implements IPaymentMethod {
|
implements IPaymentMethod
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Payment subscription of organization via license code.
|
* Payment subscription of organization via license code.
|
||||||
* @param {ILicensePaymentModel} licensePaymentModel -
|
* @param {ILicensePaymentModel} licensePaymentModel -
|
||||||
*/
|
*/
|
||||||
async payment(licensePaymentModel: ILicensePaymentModel, plan: Plan) {
|
public async payment(licensePaymentModel: ILicensePaymentModel, plan: Plan) {
|
||||||
this.validateLicensePaymentModel(licensePaymentModel);
|
this.validateLicensePaymentModel(licensePaymentModel);
|
||||||
|
|
||||||
const license = await this.getLicenseOrThrowInvalid(licensePaymentModel);
|
const license = await this.getLicenseOrThrowInvalid(licensePaymentModel);
|
||||||
@@ -30,7 +30,9 @@ export default class LicensePaymentMethod
|
|||||||
* Validates the license code activation on the storage.
|
* Validates the license code activation on the storage.
|
||||||
* @param {ILicensePaymentModel} licensePaymentModel -
|
* @param {ILicensePaymentModel} licensePaymentModel -
|
||||||
*/
|
*/
|
||||||
async getLicenseOrThrowInvalid(licensePaymentModel: ILicensePaymentModel) {
|
private async getLicenseOrThrowInvalid(
|
||||||
|
licensePaymentModel: ILicensePaymentModel
|
||||||
|
) {
|
||||||
const foundLicense = await License.query()
|
const foundLicense = await License.query()
|
||||||
.modify('filterActiveLicense')
|
.modify('filterActiveLicense')
|
||||||
.where('license_code', licensePaymentModel.licenseCode)
|
.where('license_code', licensePaymentModel.licenseCode)
|
||||||
@@ -47,7 +49,7 @@ export default class LicensePaymentMethod
|
|||||||
* @param {License} license
|
* @param {License} license
|
||||||
* @param {Plan} plan
|
* @param {Plan} plan
|
||||||
*/
|
*/
|
||||||
validatePaymentAmountWithPlan(license: License, plan: Plan) {
|
private validatePaymentAmountWithPlan(license: License, plan: Plan) {
|
||||||
if (license.planId !== plan.id) {
|
if (license.planId !== plan.id) {
|
||||||
throw new PaymentAmountInvalidWithPlan();
|
throw new PaymentAmountInvalidWithPlan();
|
||||||
}
|
}
|
||||||
@@ -57,7 +59,7 @@ export default class LicensePaymentMethod
|
|||||||
* Validate voucher payload.
|
* Validate voucher payload.
|
||||||
* @param {ILicensePaymentModel} licenseModel -
|
* @param {ILicensePaymentModel} licenseModel -
|
||||||
*/
|
*/
|
||||||
validateLicensePaymentModel(licenseModel: ILicensePaymentModel) {
|
private validateLicensePaymentModel(licenseModel: ILicensePaymentModel) {
|
||||||
if (!licenseModel || !licenseModel.licenseCode) {
|
if (!licenseModel || !licenseModel.licenseCode) {
|
||||||
throw new VoucherCodeRequired();
|
throw new VoucherCodeRequired();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject } from 'typedi';
|
||||||
import { Tenant, Plan } from 'system/models';
|
import { Tenant, Plan } from 'system/models';
|
||||||
import { IPaymentContext } from 'interfaces';
|
import { IPaymentContext } from 'interfaces';
|
||||||
import { NotAllowedChangeSubscriptionPlan } from 'exceptions';
|
import { NotAllowedChangeSubscriptionPlan } from 'exceptions';
|
||||||
import { NoPaymentModelWithPricedPlan } from 'exceptions';
|
|
||||||
|
|
||||||
export default class Subscription<PaymentModel> {
|
export default class Subscription<PaymentModel> {
|
||||||
paymentContext: IPaymentContext|null;
|
paymentContext: IPaymentContext | null;
|
||||||
|
|
||||||
@Inject('logger')
|
@Inject('logger')
|
||||||
logger: any;
|
logger: any;
|
||||||
@@ -19,46 +18,63 @@ export default class Subscription<PaymentModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscripe to the given plan.
|
* Give the tenant a new subscription.
|
||||||
* @param {Plan} plan
|
* @param {Tenant} tenant
|
||||||
* @throws {NotAllowedChangeSubscriptionPlan}
|
* @param {Plan} plan
|
||||||
|
* @param {string} invoiceInterval
|
||||||
|
* @param {number} invoicePeriod
|
||||||
|
* @param {string} subscriptionSlug
|
||||||
*/
|
*/
|
||||||
async subscribe(
|
protected async newSubscribtion(
|
||||||
tenant: Tenant,
|
tenant,
|
||||||
plan: Plan,
|
plan,
|
||||||
paymentModel?: PaymentModel,
|
invoiceInterval: string,
|
||||||
subscriptionSlug: string = 'main',
|
invoicePeriod: number,
|
||||||
|
subscriptionSlug: string = 'main'
|
||||||
) {
|
) {
|
||||||
this.validateIfPlanHasPriceNoPayment(plan, paymentModel);
|
const subscription = await tenant
|
||||||
|
.$relatedQuery('subscriptions')
|
||||||
await this.paymentContext.makePayment(paymentModel, plan);
|
|
||||||
|
|
||||||
const subscription = await tenant.$relatedQuery('subscriptions')
|
|
||||||
.modify('subscriptionBySlug', subscriptionSlug)
|
.modify('subscriptionBySlug', subscriptionSlug)
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
// No allowed to re-new the the subscription while the subscription is active.
|
// No allowed to re-new the the subscription while the subscription is active.
|
||||||
if (subscription && subscription.active()) {
|
if (subscription && subscription.active()) {
|
||||||
throw new NotAllowedChangeSubscriptionPlan;
|
throw new NotAllowedChangeSubscriptionPlan();
|
||||||
|
|
||||||
// In case there is already subscription associated to the given tenant renew it.
|
// In case there is already subscription associated to the given tenant renew it.
|
||||||
} else if(subscription && subscription.inactive()) {
|
} else if (subscription && subscription.inactive()) {
|
||||||
await subscription.renew(plan);
|
await subscription.renew(invoiceInterval, invoicePeriod);
|
||||||
|
|
||||||
// No stored past tenant subscriptions create new one.
|
// No stored past tenant subscriptions create new one.
|
||||||
} else {
|
} else {
|
||||||
await tenant.newSubscription(subscriptionSlug, plan);
|
await tenant.newSubscription(
|
||||||
}
|
plan.id,
|
||||||
|
invoiceInterval,
|
||||||
|
invoicePeriod,
|
||||||
|
subscriptionSlug
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throw error in plan has price and no payment model.
|
* Subscripe to the given plan.
|
||||||
* @param {Plan} plan -
|
* @param {Plan} plan
|
||||||
* @param {PaymentModel} paymentModel - payment input.
|
* @throws {NotAllowedChangeSubscriptionPlan}
|
||||||
*/
|
*/
|
||||||
validateIfPlanHasPriceNoPayment(plan: Plan, paymentModel: PaymentMode) {
|
public async subscribe(
|
||||||
if (plan.price > 0 && !paymentModel) {
|
tenant: Tenant,
|
||||||
throw new NoPaymentModelWithPricedPlan();
|
plan: Plan,
|
||||||
}
|
paymentModel?: PaymentModel,
|
||||||
|
subscriptionSlug: string = 'main'
|
||||||
|
) {
|
||||||
|
await this.paymentContext.makePayment(paymentModel, plan);
|
||||||
|
|
||||||
|
return this.newSubscribtion(
|
||||||
|
tenant,
|
||||||
|
plan,
|
||||||
|
plan.invoiceInterval,
|
||||||
|
plan.invoicePeriod,
|
||||||
|
subscriptionSlug
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { Plan, PlanSubscription } from 'system/models';
|
import { Plan, PlanSubscription, Tenant } from 'system/models';
|
||||||
import Subscription from 'services/Subscription/Subscription';
|
import Subscription from 'services/Subscription/Subscription';
|
||||||
import LicensePaymentMethod from 'services/Payment/LicensePaymentMethod';
|
import LicensePaymentMethod from 'services/Payment/LicensePaymentMethod';
|
||||||
import PaymentContext from 'services/Payment';
|
import PaymentContext from 'services/Payment';
|
||||||
import SubscriptionSMSMessages from 'services/Subscription/SMSMessages';
|
import SubscriptionSMSMessages from 'services/Subscription/SMSMessages';
|
||||||
import SubscriptionMailMessages from 'services/Subscription/MailMessages';
|
import SubscriptionMailMessages from 'services/Subscription/MailMessages';
|
||||||
import { ILicensePaymentModel } from 'interfaces';
|
import { ILicensePaymentModel } from 'interfaces';
|
||||||
|
import SubscriptionViaLicense from './SubscriptionViaLicense';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SubscriptionService {
|
export default class SubscriptionService {
|
||||||
@@ -22,46 +23,47 @@ export default class SubscriptionService {
|
|||||||
sysRepositories: any;
|
sysRepositories: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the payment process via license code and than subscribe to
|
* Handles the payment process via license code and than subscribe to
|
||||||
* the given tenant.
|
* the given tenant.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {String} planSlug
|
* @param {String} planSlug
|
||||||
* @param {string} licenseCode
|
* @param {string} licenseCode
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
public async subscriptionViaLicense(
|
public async subscriptionViaLicense(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
planSlug: string,
|
planSlug: string,
|
||||||
paymentModel?: ILicensePaymentModel,
|
paymentModel: ILicensePaymentModel,
|
||||||
subscriptionSlug: string = 'main',
|
subscriptionSlug: string = 'main'
|
||||||
) {
|
) {
|
||||||
this.logger.info('[subscription_via_license] try to subscribe via given license.', {
|
// Retrieve plan details.
|
||||||
tenantId, paymentModel
|
|
||||||
});
|
|
||||||
const { tenantRepository } = this.sysRepositories;
|
|
||||||
|
|
||||||
const plan = await Plan.query().findOne('slug', planSlug);
|
const plan = await Plan.query().findOne('slug', planSlug);
|
||||||
const tenant = await tenantRepository.findOneById(tenantId);
|
|
||||||
|
|
||||||
|
// Retrieve tenant details.
|
||||||
|
const tenant = await Tenant.query().findById(tenantId);
|
||||||
|
|
||||||
|
// License payment method.
|
||||||
const paymentViaLicense = new LicensePaymentMethod();
|
const paymentViaLicense = new LicensePaymentMethod();
|
||||||
|
|
||||||
|
// Payment context.
|
||||||
const paymentContext = new PaymentContext(paymentViaLicense);
|
const paymentContext = new PaymentContext(paymentViaLicense);
|
||||||
|
|
||||||
const subscription = new Subscription(paymentContext);
|
// Subscription.
|
||||||
|
const subscription = new SubscriptionViaLicense(paymentContext);
|
||||||
|
|
||||||
|
// Subscribe.
|
||||||
await subscription.subscribe(tenant, plan, paymentModel, subscriptionSlug);
|
await subscription.subscribe(tenant, plan, paymentModel, subscriptionSlug);
|
||||||
this.logger.info('[subscription_via_license] payment via license done successfully.', {
|
|
||||||
tenantId, paymentModel
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve all subscription of the given tenant.
|
* Retrieve all subscription of the given tenant.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
*/
|
*/
|
||||||
public async getSubscriptions(tenantId: number) {
|
public async getSubscriptions(tenantId: number) {
|
||||||
this.logger.info('[subscription] trying to get tenant subscriptions.', { tenantId });
|
const subscriptions = await PlanSubscription.query().where(
|
||||||
const subscriptions = await PlanSubscription.query().where('tenant_id', tenantId);
|
'tenant_id',
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
return subscriptions;
|
return subscriptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
54
server/src/services/Subscription/SubscriptionViaLicense.ts
Normal file
54
server/src/services/Subscription/SubscriptionViaLicense.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { License, Tenant, Plan } from 'system/models';
|
||||||
|
import Subscription from './Subscription';
|
||||||
|
import { PaymentModel } from 'interfaces';
|
||||||
|
|
||||||
|
export default class SubscriptionViaLicense extends Subscription<PaymentModel> {
|
||||||
|
/**
|
||||||
|
* Subscripe to the given plan.
|
||||||
|
* @param {Plan} plan
|
||||||
|
* @throws {NotAllowedChangeSubscriptionPlan}
|
||||||
|
*/
|
||||||
|
public async subscribe(
|
||||||
|
tenant: Tenant,
|
||||||
|
plan: Plan,
|
||||||
|
paymentModel?: PaymentModel,
|
||||||
|
subscriptionSlug: string = 'main'
|
||||||
|
): Promise<void> {
|
||||||
|
await this.paymentContext.makePayment(paymentModel, plan);
|
||||||
|
|
||||||
|
return this.newSubscriptionFromLicense(
|
||||||
|
tenant,
|
||||||
|
plan,
|
||||||
|
paymentModel.licenseCode,
|
||||||
|
subscriptionSlug
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New subscription from the given license.
|
||||||
|
* @param {Tanant} tenant
|
||||||
|
* @param {Plab} plan
|
||||||
|
* @param {string} licenseCode
|
||||||
|
* @param {string} subscriptionSlug
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
private async newSubscriptionFromLicense(
|
||||||
|
tenant,
|
||||||
|
plan,
|
||||||
|
licenseCode: string,
|
||||||
|
subscriptionSlug: string = 'main'
|
||||||
|
): Promise<void> {
|
||||||
|
// License information.
|
||||||
|
const licenseInfo = await License.query().findOne(
|
||||||
|
'licenseCode',
|
||||||
|
licenseCode
|
||||||
|
);
|
||||||
|
return this.newSubscribtion(
|
||||||
|
tenant,
|
||||||
|
plan,
|
||||||
|
licenseInfo.periodInterval,
|
||||||
|
licenseInfo.licensePeriod,
|
||||||
|
subscriptionSlug
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,9 +16,6 @@ const ERRORS = {
|
|||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class UsersService {
|
export default class UsersService {
|
||||||
@Inject()
|
|
||||||
tenancy: TenancyService;
|
|
||||||
|
|
||||||
@Inject('logger')
|
@Inject('logger')
|
||||||
logger: any;
|
logger: any;
|
||||||
|
|
||||||
|
|||||||
@@ -41,18 +41,18 @@ export default class License extends SystemModel {
|
|||||||
// Filters licenses list.
|
// Filters licenses list.
|
||||||
filter(builder, licensesFilter) {
|
filter(builder, licensesFilter) {
|
||||||
if (licensesFilter.active) {
|
if (licensesFilter.active) {
|
||||||
builder.modify('filterActiveLicense')
|
builder.modify('filterActiveLicense');
|
||||||
}
|
}
|
||||||
if (licensesFilter.disabled) {
|
if (licensesFilter.disabled) {
|
||||||
builder.whereNot('disabled_at', null);
|
builder.whereNot('disabled_at', null);
|
||||||
}
|
}
|
||||||
if (licensesFilter.used) {
|
if (licensesFilter.used) {
|
||||||
builder.whereNot('used_at', null);
|
builder.whereNot('used_at', null);
|
||||||
}
|
}
|
||||||
if (licensesFilter.sent) {
|
if (licensesFilter.sent) {
|
||||||
builder.whereNot('sent_at', null);
|
builder.whereNot('sent_at', null);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,38 +76,32 @@ export default class License extends SystemModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the given license code from the storage.
|
* Deletes the given license code from the storage.
|
||||||
* @param {string} licenseCode
|
* @param {string} licenseCode
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
static deleteLicense(licenseCode, viaAttribute = 'license_code') {
|
static deleteLicense(licenseCode, viaAttribute = 'license_code') {
|
||||||
return this.query()
|
return this.query().where(viaAttribute, licenseCode).delete();
|
||||||
.where(viaAttribute, licenseCode)
|
|
||||||
.delete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given license code as disabled on the storage.
|
* Marks the given license code as disabled on the storage.
|
||||||
* @param {string} licenseCode
|
* @param {string} licenseCode
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
static markLicenseAsDisabled(licenseCode, viaAttribute = 'license_code') {
|
static markLicenseAsDisabled(licenseCode, viaAttribute = 'license_code') {
|
||||||
return this.query()
|
return this.query().where(viaAttribute, licenseCode).patch({
|
||||||
.where(viaAttribute, licenseCode)
|
disabled_at: moment().toMySqlDateTime(),
|
||||||
.patch({
|
});
|
||||||
disabled_at: moment().toMySqlDateTime(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given license code as sent on the storage.
|
* Marks the given license code as sent on the storage.
|
||||||
* @param {string} licenseCode
|
* @param {string} licenseCode
|
||||||
*/
|
*/
|
||||||
static markLicenseAsSent(licenseCode, viaAttribute = 'license_code') {
|
static markLicenseAsSent(licenseCode, viaAttribute = 'license_code') {
|
||||||
return this.query()
|
return this.query().where(viaAttribute, licenseCode).patch({
|
||||||
.where(viaAttribute, licenseCode)
|
sent_at: moment().toMySqlDateTime(),
|
||||||
.patch({
|
});
|
||||||
sent_at: moment().toMySqlDateTime(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,20 +110,20 @@ export default class License extends SystemModel {
|
|||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
static markLicenseAsUsed(licenseCode, viaAttribute = 'license_code') {
|
static markLicenseAsUsed(licenseCode, viaAttribute = 'license_code') {
|
||||||
return this.query()
|
return this.query().where(viaAttribute, licenseCode).patch({
|
||||||
.where(viaAttribute, licenseCode)
|
used_at: moment().toMySqlDateTime(),
|
||||||
.patch({
|
});
|
||||||
used_at: moment().toMySqlDateTime()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {IIPlan} plan
|
* @param {IIPlan} plan
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
isEqualPlanPeriod(plan) {
|
isEqualPlanPeriod(plan) {
|
||||||
return (this.invoicePeriod === plan.invoiceInterval &&
|
return (
|
||||||
license.licensePeriod === license.periodInterval);
|
this.invoicePeriod === plan.invoiceInterval &&
|
||||||
|
license.licensePeriod === license.periodInterval
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import uniqid from 'uniqid';
|
|||||||
import SubscriptionPeriod from 'services/Subscription/SubscriptionPeriod';
|
import SubscriptionPeriod from 'services/Subscription/SubscriptionPeriod';
|
||||||
import BaseModel from 'models/Model';
|
import BaseModel from 'models/Model';
|
||||||
import TenantMetadata from './TenantMetadata';
|
import TenantMetadata from './TenantMetadata';
|
||||||
|
import PlanSubscription from './Subscriptions/PlanSubscription';
|
||||||
|
|
||||||
export default class Tenant extends BaseModel {
|
export default class Tenant extends BaseModel {
|
||||||
/**
|
/**
|
||||||
@@ -88,20 +89,40 @@ export default class Tenant extends BaseModel {
|
|||||||
return chain(subscriptions).map('planId').unq();
|
return chain(subscriptions).map('planId').unq();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} planId
|
||||||
|
* @param {*} invoiceInterval
|
||||||
|
* @param {*} invoicePeriod
|
||||||
|
* @param {*} subscriptionSlug
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
newSubscription(planId, invoiceInterval, invoicePeriod, subscriptionSlug) {
|
||||||
|
return Tenant.newSubscription(
|
||||||
|
this.id,
|
||||||
|
planId,
|
||||||
|
invoiceInterval,
|
||||||
|
invoicePeriod,
|
||||||
|
subscriptionSlug,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records a new subscription for the associated tenant.
|
* Records a new subscription for the associated tenant.
|
||||||
*/
|
*/
|
||||||
newSubscription(subscriptionSlug, plan) {
|
static newSubscription(
|
||||||
const trial = new SubscriptionPeriod(plan.trialInterval, plan.trialPeriod);
|
tenantId,
|
||||||
const period = new SubscriptionPeriod(
|
planId,
|
||||||
plan.invoiceInterval,
|
invoiceInterval,
|
||||||
plan.invoicePeriod,
|
invoicePeriod,
|
||||||
trial.getEndDate()
|
subscriptionSlug
|
||||||
);
|
) {
|
||||||
|
const period = new SubscriptionPeriod(invoiceInterval, invoicePeriod);
|
||||||
|
|
||||||
return this.$relatedQuery('subscriptions').insert({
|
return PlanSubscription.query().insert({
|
||||||
|
tenantId,
|
||||||
slug: subscriptionSlug,
|
slug: subscriptionSlug,
|
||||||
planId: plan.id,
|
planId,
|
||||||
startsAt: period.getStartDate(),
|
startsAt: period.getStartDate(),
|
||||||
endsAt: period.getEndDate(),
|
endsAt: period.getEndDate(),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user