fix: mark payment license as used after usage.

fix: fix system models with createdAt and updatedAt fields.
fix: reset password token expiration time.
This commit is contained in:
Ahmed Bouhuolia
2020-09-07 17:13:37 +02:00
parent d3870974c0
commit ffb0499280
24 changed files with 191 additions and 94 deletions

View File

@@ -203,7 +203,7 @@ export default class AuthenticationService {
// Delete all stored tokens of reset password that associate to the give email.
this.logger.info('[send_reset_password] trying to delete all tokens by email.');
await PasswordReset.query().where('email', email).delete();
this.deletePasswordResetToken(email);
const token = uniqid();
@@ -230,28 +230,44 @@ export default class AuthenticationService {
this.logger.info('[reset_password] token invalid.');
throw new ServiceError('token_invalid');
}
// Different between tokne creation datetime and current time.
if (moment().diff(tokenModel.createdAt, 'seconds') > config.resetPasswordSeconds) {
this.logger.info('[reset_password] token expired.');
// Deletes the expired token by expired token email.
await this.deletePasswordResetToken(tokenModel.email);
throw new ServiceError('token_expired');
}
const user = await SystemUser.query().findOne('email', tokenModel.email)
if (!user) {
throw new ServiceError('user_not_found');
}
const hashedPassword = await hashPassword(password);
this.logger.info('[reset_password] saving a new hashed password.');
await SystemUser.query()
.where('email', tokenModel.email)
.update({
password: hashedPassword,
});
// Delete the reset password token.
await PasswordReset.query().where('email', user.email).delete();
.update({ password: hashedPassword });
// Deletes the used token.
await this.deletePasswordResetToken(tokenModel.email);
// Triggers `onResetPassword` event.
this.eventDispatcher.dispatch(events.auth.resetPassword, { user, token, password });
this.logger.info('[reset_password] reset password success.');
}
/**
* Deletes the password reset token by the given email.
* @param {string} email
* @returns {Promise}
*/
private async deletePasswordResetToken(email: string) {
this.logger.info('[reset_password] trying to delete all tokens by email.');
return PasswordReset.query().where('email', email).delete();
}
/**
* Generates JWT token for the given user.
* @param {ISystemUser} user

View File

@@ -5,7 +5,7 @@ import {
EventDispatcher,
EventDispatcherInterface,
} from '@/decorators/eventDispatcher';
import { ServiceError, ServiceErrors } from "@/exceptions";
import { ServiceError } from "@/exceptions";
import { SystemUser, Invite, Tenant } from "@/system/models";
import { Option } from '@/models';
import { hashPassword } from '@/utils';
@@ -49,8 +49,6 @@ export default class InviteUserService {
this.logger.info('[aceept_invite] trying to hash the user password.');
const hashedPassword = await hashPassword(inviteUserInput.password);
console.log(inviteToken);
this.logger.info('[accept_invite] trying to update user details.');
const updateUserOper = SystemUser.query()
.where('email', inviteToken.email)
@@ -60,7 +58,7 @@ export default class InviteUserService {
invite_accepted_at: moment().format('YYYY-MM-DD'),
password: hashedPassword,
});
this.logger.info('[accept_invite] trying to delete the given token.');
const deleteInviteTokenOper = Invite.query().where('token', inviteToken.token).delete();

View File

@@ -42,7 +42,6 @@ export default class LicenseService {
});
}
/**
*
* @param {number} loop
@@ -52,7 +51,7 @@ export default class LicenseService {
*/
async generateLicenses(
loop = 1,
licensePeriod: numner,
licensePeriod: number,
periodInterval: string = 'days',
planId: number,
) {
@@ -67,7 +66,7 @@ export default class LicenseService {
/**
* Disables the given license id on the storage.
* @param {number} licenseId
* @param {number} licenseId
* @return {Promise}
*/
async disableLicense(licenseId: number) {

View File

@@ -1,14 +1,47 @@
import { License } from "@/system/models";
import PaymentMethod from '@/services/Payment/PaymentMethod';
import { Plan } from '@/system/models';
import { IPaymentMethod, ILicensePaymentModel } from '@/interfaces';
import { ILicensePaymentModel } from "@/interfaces";
import { PaymentInputInvalid, PaymentAmountInvalidWithPlan } from '@/exceptions';
export default class VocuherPaymentMethod extends PaymentMethod implements IPaymentMethod {
export default class LicensePaymentMethod extends PaymentMethod implements IPaymentMethod {
/**
* Payment subscription of organization via license code.
* @param {ILicensePaymentModel}
* @param {ILicensePaymentModel} licensePaymentModel -
*/
async payment(licensePaymentModel: ILicensePaymentModel) {
async payment(licensePaymentModel: ILicensePaymentModel, plan: Plan) {
const license = await this.getLicenseOrThrowInvalid(licensePaymentModel);
this.validatePaymentAmountWithPlan(license, plan);
// Mark the license code as used.
return License.markLicenseAsUsed(licensePaymentModel.licenseCode);
}
/**
* Validates the license code activation on the storage.
* @param {ILicensePaymentModel} licensePaymentModel -
*/
async getLicenseOrThrowInvalid(licensePaymentModel: ILicensePaymentModel) {
const foundLicense = await License.query()
.modify('filterActiveLicense')
.where('license_code', licensePaymentModel.licenseCode)
.first();
if (!foundLicense) {
throw new PaymentInputInvalid();
}
return foundLicense;
}
/**
* Validates the payment amount with given plan price.
* @param {License} license
* @param {Plan} plan
*/
validatePaymentAmountWithPlan(license: License, plan: Plan) {
if (license.planId !== plan.id) {
throw new PaymentAmountInvalidWithPlan();
}
}
}

View File

@@ -1,4 +1,5 @@
import { IPaymentMethod, IPaymentContext } from "@/interfaces";
import { Plan } from '@/system/models';
export default class PaymentContext<PaymentModel> implements IPaymentContext{
paymentMethod: IPaymentMethod;
@@ -15,7 +16,7 @@ export default class PaymentContext<PaymentModel> implements IPaymentContext{
*
* @param {<PaymentModel>} paymentModel
*/
makePayment(paymentModel: PaymentModel) {
this.paymentMethod.makePayment(paymentModel);
makePayment(paymentModel: PaymentModel, plan: Plan) {
return this.paymentMethod.payment(paymentModel, plan);
}
}

View File

@@ -1,7 +1,8 @@
import { Inject } from 'typedi';
import { Inject, Service } from 'typedi';
import { Tenant, Plan } from '@/system/models';
import { IPaymentContext } from '@/interfaces';
import { NotAllowedChangeSubscriptionPlan } from '@/exceptions';
import { NoPaymentModelWithPricedPlan } from '@/exceptions';
export default class Subscription<PaymentModel> {
paymentContext: IPaymentContext|null;
@@ -19,7 +20,7 @@ export default class Subscription<PaymentModel> {
/**
* Subscripe to the given plan.
* @param {Plan} plan
* @param {Plan} plan
* @throws {NotAllowedChangeSubscriptionPlan}
*/
async subscribe(
@@ -28,8 +29,11 @@ export default class Subscription<PaymentModel> {
paymentModel?: PaymentModel,
subscriptionSlug: string = 'main',
) {
if (plan.price < 0) {
await this.paymentContext.makePayment(paymentModel);
this.validateIfPlanHasPriceNoPayment(plan, paymentModel);
// @todo
if (plan.price > 0) {
await this.paymentContext.makePayment(paymentModel, plan);
}
const subscription = await tenant.$relatedQuery('subscriptions')
.modify('subscriptionBySlug', subscriptionSlug)
@@ -39,8 +43,7 @@ export default class Subscription<PaymentModel> {
if (subscription && subscription.active()) {
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()) {
await subscription.renew(plan);
@@ -49,4 +52,15 @@ export default class Subscription<PaymentModel> {
await tenant.newSubscription(subscriptionSlug, plan);
}
}
/**
* Throw error in plan has price and no payment model.
* @param {Plan} plan -
* @param {PaymentModel} paymentModel - payment input.
*/
validateIfPlanHasPriceNoPayment(plan: Plan, paymentModel: PaymentMode) {
if (plan.price > 0 && !paymentModel) {
throw new NoPaymentModelWithPricedPlan();
}
}
}

View File

@@ -1,10 +1,11 @@
import { Service, Inject } from 'typedi';
import { Plan, Tenant, License } from '@/system/models';
import { Plan, Tenant } from '@/system/models';
import Subscription from '@/services/Subscription/Subscription';
import VocuherPaymentMethod from '@/services/Payment/LicensePaymentMethod';
import LicensePaymentMethod from '@/services/Payment/LicensePaymentMethod';
import PaymentContext from '@/services/Payment';
import SubscriptionSMSMessages from '@/services/Subscription/SMSMessages';
import SubscriptionMailMessages from '@/services/Subscription/MailMessages';
import { ILicensePaymentModel } from '@/interfaces';
@Service()
export default class SubscriptionService {
@@ -14,31 +15,37 @@ export default class SubscriptionService {
@Inject()
mailMessages: SubscriptionMailMessages;
@Inject('logger')
logger: any;
/**
* Handles the payment process via license code and than subscribe to
* the given tenant.
*
* @param {number} tenantId
* @param {String} planSlug
* @param {string} licenseCode
*
* @return {Promise}
*/
async subscriptionViaLicense(
tenantId: number,
planSlug: string,
licenseCode: string,
paymentModel?: ILicensePaymentModel,
subscriptionSlug: string = 'main',
) {
this.logger.info('[subscription_via_license] try to subscribe via given license.', {
tenantId, paymentModel
});
const plan = await Plan.query().findOne('slug', planSlug);
const tenant = await Tenant.query().findById(tenantId);
const licenseModel = await License.query().findOne('license_code', licenseCode);
const paymentViaLicense = new VocuherPaymentMethod();
const paymentViaLicense = new LicensePaymentMethod();
const paymentContext = new PaymentContext(paymentViaLicense);
const subscription = new Subscription(paymentContext);
return subscription.subscribe(tenant, plan, licenseModel, subscriptionSlug);
await subscription.subscribe(tenant, plan, paymentModel, subscriptionSlug);
this.logger.info('[subscription_via_license] payment via license done successfully.', {
tenantId, paymentModel
});
}
}