This commit is contained in:
Simon Larsen 2022-11-25 14:57:58 +00:00
parent a7ddd65a0c
commit 75d773ab58
No known key found for this signature in database
GPG Key ID: AB45983AA9C81CDE
19 changed files with 132 additions and 124 deletions

View File

@ -54,7 +54,8 @@ export default class SubscriptionPlan {
}
public static isFreePlan(planId: string): boolean {
const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById(planId);
const plan: SubscriptionPlan | undefined =
this.getSubscriptionPlanById(planId);
if (plan) {
if (
plan.getMonthlyPlanId() === planId &&
@ -75,7 +76,8 @@ export default class SubscriptionPlan {
}
public static isCustomPricingPlan(planId: string): boolean {
const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById(planId);
const plan: SubscriptionPlan | undefined =
this.getSubscriptionPlanById(planId);
if (plan) {
if (plan.getMonthlyPlanId() === planId && plan.isCustomPricing()) {
return true;
@ -145,7 +147,8 @@ export default class SubscriptionPlan {
}
public static getPlanSelect(planId: string): PlanSelect {
const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById(planId);
const plan: SubscriptionPlan | undefined =
this.getSubscriptionPlanById(planId);
if (!plan) {
throw new BadDataException('Plan ID is invalid');
}
@ -156,9 +159,10 @@ export default class SubscriptionPlan {
public static getSubscriptionPlanFromPlanSelect(
planSelect: PlanSelect
): SubscriptionPlan {
const plan: SubscriptionPlan | undefined = this.getSubscriptionPlans().find((plan: SubscriptionPlan) => {
return plan.getName() === planSelect;
});
const plan: SubscriptionPlan | undefined =
this.getSubscriptionPlans().find((plan: SubscriptionPlan) => {
return plan.getName() === planSelect;
});
if (!plan) {
throw new BadDataException('Invalid Plan');

View File

@ -1,7 +1,7 @@
export default class Sleep {
public static async sleep(ms: number): Promise<void> {
return new Promise((resolve: Function) => {
return setTimeout(resolve, ms);
setTimeout(resolve, ms);
});
}
}

View File

@ -152,7 +152,8 @@ export default class BaseAPI<
): Promise<Array<UserPermission>> {
const permissions: Array<UserPermission> = [];
const props: DatabaseCommonInteractionProps = await this.getDatabaseCommonInteractionProps(req);
const props: DatabaseCommonInteractionProps =
await this.getDatabaseCommonInteractionProps(req);
if (
props &&
@ -217,7 +218,10 @@ export default class BaseAPI<
}
if (IsBillingEnabled && props.tenantId) {
const plan: { plan: PlanSelect | null; isSubscriptionUnpaid: boolean } = await ProjectService.getCurrentPlan(props.tenantId!);
const plan: {
plan: PlanSelect | null;
isSubscriptionUnpaid: boolean;
} = await ProjectService.getCurrentPlan(props.tenantId!);
props.currentPlan = plan.plan || undefined;
props.isSubscriptionUnpaid = plan.isSubscriptionUnpaid;
}

View File

@ -1,7 +1,7 @@
import BaseModel from 'Common/Models/BaseModel';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { JSONObject } from 'Common/Types/JSON';
import Permission from 'Common/Types/Permission';
import Permission, { UserPermission } from 'Common/Types/Permission';
import BillingInvoice from 'Model/Models/BillingInvoice';
import Project from 'Model/Models/Project';
import { IsBillingEnabled } from '../Config';
@ -47,11 +47,9 @@ export default class UserAPI extends BaseAPI<
);
}
const userPermissions = (
const userPermissions: Array<UserPermission> = (
await this.getPermissionsForTenant(req)
).filter((permission) => {
console.log(permission.permission);
//FIX: Change "Project"
).filter((permission: UserPermission) => {
return (
permission.permission.toString() ===
Permission.ProjectOwner.toString() ||
@ -136,7 +134,7 @@ export default class UserAPI extends BaseAPI<
});
// refresh subscription status.
const subscriptionState =
const subscriptionState: string =
await BillingService.getSubscriptionStatus(
project.paymentProviderSubscriptionId as string
);

View File

@ -1,5 +1,5 @@
import BadDataException from 'Common/Types/Exception/BadDataException';
import Permission from 'Common/Types/Permission';
import Permission, { UserPermission } from 'Common/Types/Permission';
import BillingPaymentMethod from 'Model/Models/BillingPaymentMethod';
import Project from 'Model/Models/Project';
import { IsBillingEnabled } from '../Config';
@ -45,11 +45,9 @@ export default class UserAPI extends BaseAPI<
);
}
const userPermissions = (
const userPermissions: Array<UserPermission> = (
await this.getPermissionsForTenant(req)
).filter((permission) => {
console.log(permission.permission);
//FIX: Change "Project"
).filter((permission: UserPermission) => {
return (
permission.permission.toString() ===
Permission.ProjectOwner.toString() ||

View File

@ -5,7 +5,7 @@ import FindBy from '../Types/Database/FindBy';
import ProjectService from './ProjectService';
import BadDataException from 'Common/Types/Exception/BadDataException';
import Project from 'Model/Models/Project';
import BillingService from './BillingService';
import BillingService, { Invoice } from './BillingService';
import DeleteBy from '../Types/Database/DeleteBy';
import URL from 'Common/Types/API/URL';
@ -44,7 +44,7 @@ export class Service extends DatabaseService<Model> {
);
}
const invoices = await BillingService.getInvoices(
const invoices: Array<Invoice> = await BillingService.getInvoices(
project.paymentProviderCustomerId
);
@ -59,7 +59,7 @@ export class Service extends DatabaseService<Model> {
});
for (const invoice of invoices) {
const billingInvoice = new Model();
const billingInvoice: Model = new Model();
billingInvoice.projectId = project.id!;

View File

@ -5,7 +5,7 @@ import FindBy from '../Types/Database/FindBy';
import ProjectService from './ProjectService';
import BadDataException from 'Common/Types/Exception/BadDataException';
import Project from 'Model/Models/Project';
import BillingService from './BillingService';
import BillingService, { PaymentMethod } from './BillingService';
import DeleteBy from '../Types/Database/DeleteBy';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
@ -44,9 +44,10 @@ export class Service extends DatabaseService<Model> {
);
}
const paymentMethods = await BillingService.getPaymentMethods(
project.paymentProviderCustomerId
);
const paymentMethods: Array<PaymentMethod> =
await BillingService.getPaymentMethods(
project.paymentProviderCustomerId
);
await this.deleteBy({
query: {
@ -59,7 +60,7 @@ export class Service extends DatabaseService<Model> {
});
for (const paymentMethod of paymentMethods) {
const billingPaymentMethod = new Model();
const billingPaymentMethod: Model = new Model();
billingPaymentMethod.projectId = project.id!;
@ -85,7 +86,7 @@ export class Service extends DatabaseService<Model> {
protected override async onBeforeDelete(
deleteBy: DeleteBy<Model>
): Promise<OnDelete<Model>> {
const items = await this.findBy({
const items: Array<Model> = await this.findBy({
query: deleteBy.query,
select: {
_id: true,

View File

@ -133,15 +133,15 @@ export class BillingService {
);
}
const subscription = await this.stripe.subscriptions.retrieve(
subscriptionId
);
const subscription: Stripe.Response<Stripe.Subscription> =
await this.stripe.subscriptions.retrieve(subscriptionId);
if (!subscription) {
throw new BadDataException('Subscription not found');
}
const subscriptionItemId = subscription.items.data[0]?.id;
const subscriptionItemId: string | undefined =
subscription.items.data[0]?.id;
if (!subscriptionItemId) {
throw new BadDataException('Subscription Item not found');
@ -168,9 +168,8 @@ export class BillingService {
);
}
let subscription = await this.stripe.subscriptions.retrieve(
subscriptionId
);
let subscription: Stripe.Response<Stripe.Subscription> =
await this.stripe.subscriptions.retrieve(subscriptionId);
if (!subscription) {
throw new BadDataException('Subscription not found');
@ -200,7 +199,8 @@ export class BillingService {
: 'now',
});
const subscriptionItemId = subscription.items.data[0]?.id;
const subscriptionItemId: string | undefined =
subscription.items.data[0]?.id;
if (!subscriptionItemId) {
throw new BadDataException('Subscription Item not found');
@ -224,7 +224,9 @@ export class BillingService {
);
}
const paymenMethods = await this.getPaymentMethods(customerId);
const paymenMethods: Array<PaymentMethod> =
await this.getPaymentMethods(customerId);
if (paymenMethods.length === 1) {
throw new BadDataException(
"There's only one payment method associated with this account. It cannot be deleted. To delete this payment method please add more payment methods to your account."
@ -244,27 +246,31 @@ export class BillingService {
}
const paymenMethods: Array<PaymentMethod> = [];
const cardPaymentMethods = await this.stripe.paymentMethods.list({
customer: customerId,
type: 'card',
});
const cardPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> =
await this.stripe.paymentMethods.list({
customer: customerId,
type: 'card',
});
const sepaPaymentMethods = await this.stripe.paymentMethods.list({
customer: customerId,
type: 'sepa_debit',
});
const sepaPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> =
await this.stripe.paymentMethods.list({
customer: customerId,
type: 'sepa_debit',
});
const usBankPaymentMethods = await this.stripe.paymentMethods.list({
customer: customerId,
type: 'us_bank_account',
});
const usBankPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> =
await this.stripe.paymentMethods.list({
customer: customerId,
type: 'us_bank_account',
});
const bacsPaymentMethods = await this.stripe.paymentMethods.list({
customer: customerId,
type: 'bacs_debit',
});
const bacsPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> =
await this.stripe.paymentMethods.list({
customer: customerId,
type: 'bacs_debit',
});
cardPaymentMethods.data.forEach((item) => {
cardPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => {
paymenMethods.push({
type: item.card?.brand || 'Card',
last4Digits: item.card?.last4 || 'xxxx',
@ -273,7 +279,7 @@ export class BillingService {
});
});
bacsPaymentMethods.data.forEach((item) => {
bacsPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => {
paymenMethods.push({
type: 'UK Bank Account',
last4Digits: item.bacs_debit?.last4 || 'xxxx',
@ -282,7 +288,7 @@ export class BillingService {
});
});
usBankPaymentMethods.data.forEach((item) => {
usBankPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => {
paymenMethods.push({
type: 'US Bank Account',
last4Digits: item.us_bank_account?.last4 || 'xxxx',
@ -291,7 +297,7 @@ export class BillingService {
});
});
sepaPaymentMethods.data.forEach((item) => {
sepaPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => {
paymenMethods.push({
type: 'EU Bank Account',
last4Digits: item.sepa_debit?.last4 || 'xxxx',
@ -341,9 +347,8 @@ export class BillingService {
);
}
const subscription = await this.stripe.subscriptions.retrieve(
subscriptionId
);
const subscription: Stripe.Response<Stripe.Subscription> =
await this.stripe.subscriptions.retrieve(subscriptionId);
return subscription.status;
}
@ -351,12 +356,13 @@ export class BillingService {
public static async getInvoices(
customerId: string
): Promise<Array<Invoice>> {
const invoices = await this.stripe.invoices.list({
customer: customerId,
limit: 100,
});
const invoices: Stripe.ApiList<Stripe.Invoice> =
await this.stripe.invoices.list({
customer: customerId,
limit: 100,
});
return invoices.data.map((invoice) => {
return invoices.data.map((invoice: Stripe.Invoice) => {
return {
id: invoice.id!,
amount: invoice.amount_due,
@ -374,7 +380,8 @@ export class BillingService {
invoiceId: string
): Promise<Invoice> {
// after the invoice is paid, // please fetch subscription and check the status.
const paymentMethods = await this.getPaymentMethods(customerId);
const paymentMethods: Array<PaymentMethod> =
await this.getPaymentMethods(customerId);
if (paymentMethods.length === 0) {
throw new BadDataException(
@ -382,9 +389,12 @@ export class BillingService {
);
}
const invoice = await this.stripe.invoices.pay(invoiceId, {
payment_method: paymentMethods[0]?.id || '',
});
const invoice: Stripe.Invoice = await this.stripe.invoices.pay(
invoiceId,
{
payment_method: paymentMethods[0]?.id || '',
}
);
return {
id: invoice.id!,

View File

@ -518,9 +518,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
)) as TBaseModel;
try {
console.log('DATA CREATE');
createBy.data = await this.getRepository().save(createBy.data);
console.log('DATA CREATED');
if (!createBy.props.ignoreHooks) {
createBy.data = await this.onCreateSuccess(
@ -930,8 +928,6 @@ class DatabaseService<TBaseModel extends BaseModel> {
public async findOneById(
findOneById: FindOneByID<TBaseModel>
): Promise<TBaseModel | null> {
console.log('FINDONE BY');
console.log(findOneById);
return await this.findOneBy({
query: {
_id: findOneById.id.toString() as any,

View File

@ -114,7 +114,7 @@ export class Service extends DatabaseService<Model> {
if (IsBillingEnabled) {
if (updateBy.data.paymentProviderPlanId) {
// payment provider id changed.
const project = await this.findOneById({
const project: Model | null = await this.findOneById({
id: new ObjectID(updateBy.data._id! as string),
select: {
paymentProviderSubscriptionId: true,
@ -135,9 +135,10 @@ export class Service extends DatabaseService<Model> {
project.paymentProviderPlanId !==
updateBy.data.paymentProviderPlanId
) {
const plan = SubscriptionPlan.getSubscriptionPlanById(
updateBy.data.paymentProviderPlanId! as string
);
const plan: SubscriptionPlan | undefined =
SubscriptionPlan.getSubscriptionPlanById(
updateBy.data.paymentProviderPlanId! as string
);
if (!plan) {
throw new BadDataException('Invalid plan');
@ -601,7 +602,7 @@ export class Service extends DatabaseService<Model> {
return { plan: null, isSubscriptionUnpaid: false };
}
const project = await this.findOneById({
const project: Model | null = await this.findOneById({
id: projectId,
select: {
paymentProviderPlanId: true,

View File

@ -1,5 +1,4 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/TeamMember';
import DatabaseService, {
OnCreate,
OnDelete,
@ -30,15 +29,17 @@ import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import URL from 'Common/Types/API/URL';
import logger from '../Utils/Logger';
import BadDataException from 'Common/Types/Exception/BadDataException';
import PositiveNumber from 'Common/Types/PositiveNumber';
import TeamMember from 'Model/Models/TeamMember';
export class Service extends DatabaseService<Model> {
export class Service extends DatabaseService<TeamMember> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
super(TeamMember, postgresDatabase);
}
protected override async onBeforeCreate(
createBy: CreateBy<Model>
): Promise<OnCreate<Model>> {
createBy: CreateBy<TeamMember>
): Promise<OnCreate<TeamMember>> {
if (!createBy.data.hasAcceptedInvitation) {
createBy.data.hasAcceptedInvitation = false;
}
@ -60,7 +61,7 @@ export class Service extends DatabaseService<Model> {
createBy.data.userId = user.id!;
const project = await ProjectService.findOneById({
const project: Project | null = await ProjectService.findOneById({
id: createBy.data.projectId!,
select: {
name: true,
@ -84,7 +85,7 @@ export class Service extends DatabaseService<Model> {
homeUrl: new URL(HttpProtocol, Domain).toString(),
},
subject: 'You have been invited to ' + project.name,
}).catch((err) => {
}).catch((err: Error) => {
logger.error(err);
});
}
@ -107,9 +108,9 @@ export class Service extends DatabaseService<Model> {
}
protected override async onCreateSuccess(
onCreate: OnCreate<Model>,
createdItem: Model
): Promise<Model> {
onCreate: OnCreate<TeamMember>,
createdItem: TeamMember
): Promise<TeamMember> {
await this.refreshTokens(
onCreate.createBy.data.userId!,
onCreate.createBy.data.projectId!
@ -123,11 +124,11 @@ export class Service extends DatabaseService<Model> {
}
protected override async onUpdateSuccess(
onUpdate: OnUpdate<Model>,
onUpdate: OnUpdate<TeamMember>,
updatedItemIds: Array<ObjectID>
): Promise<OnUpdate<Model>> {
const updateBy: UpdateBy<Model> = onUpdate.updateBy;
const items: Array<Model> = await this.findBy({
): Promise<OnUpdate<TeamMember>> {
const updateBy: UpdateBy<TeamMember> = onUpdate.updateBy;
const items: Array<TeamMember> = await this.findBy({
query: {
_id: QueryHelper.in(updatedItemIds),
},
@ -151,9 +152,9 @@ export class Service extends DatabaseService<Model> {
}
protected override async onBeforeDelete(
deleteBy: DeleteBy<Model>
): Promise<OnDelete<Model>> {
const members: Array<Model> = await this.findBy({
deleteBy: DeleteBy<TeamMember>
): Promise<OnDelete<TeamMember>> {
const members: Array<TeamMember> = await this.findBy({
query: deleteBy.query,
select: {
userId: true,
@ -177,7 +178,7 @@ export class Service extends DatabaseService<Model> {
// check if there's one member in the team.
for (const member of members) {
if (member.team?.shouldHaveAtleastOneMember) {
const membersInTeam = await this.countBy({
const membersInTeam: PositiveNumber = await this.countBy({
query: {
_id: member.teamId?.toString() as string,
},
@ -203,9 +204,9 @@ export class Service extends DatabaseService<Model> {
}
protected override async onDeleteSuccess(
onDelete: OnDelete<Model>
): Promise<OnDelete<Model>> {
for (const item of onDelete.carryForward as Array<Model>) {
onDelete: OnDelete<TeamMember>
): Promise<OnDelete<TeamMember>> {
for (const item of onDelete.carryForward as Array<TeamMember>) {
await this.refreshTokens(item.userId!, item.projectId!);
await this.updateSubscriptionSeatsByUnqiqueTeamMembersInProject(
item.projectId!
@ -218,7 +219,7 @@ export class Service extends DatabaseService<Model> {
public async getUniqueTeamMemberCountInProject(
projectId: ObjectID
): Promise<number> {
const members = await this.findBy({
const members: Array<TeamMember> = await this.findBy({
query: {
projectId: projectId!,
},
@ -232,14 +233,15 @@ export class Service extends DatabaseService<Model> {
limit: LIMIT_MAX,
});
const emmberIds = members
.map((member) => {
const memberIds: Array<string | undefined> = members
.map((member: TeamMember) => {
return member.userId?.toString();
})
.filter((memberId) => {
.filter((memberId: string | undefined) => {
return Boolean(memberId);
});
return [...new Set(emmberIds)].length; //get unique member ids.
return [...new Set(memberIds)].length; //get unique member ids.
}
public async updateSubscriptionSeatsByUnqiqueTeamMembersInProject(

View File

@ -15,6 +15,7 @@ import TeamService from './TeamService';
import UpdateBy from '../Types/Database/UpdateBy';
import DeleteBy from '../Types/Database/DeleteBy';
import ObjectID from 'Common/Types/ObjectID';
import Team from 'Model/Models/Team';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
@ -31,7 +32,7 @@ export class Service extends DatabaseService<Model> {
}
// get team.
const team = await TeamService.findOneById({
const team: Team | null = await TeamService.findOneById({
id: createBy.data.teamId!,
select: {
isPermissionsEditable: true,
@ -91,7 +92,7 @@ export class Service extends DatabaseService<Model> {
protected override async onBeforeUpdate(
updateBy: UpdateBy<Model>
): Promise<OnUpdate<Model>> {
const teamPermissions = await this.findBy({
const teamPermissions: Array<Model> = await this.findBy({
query: updateBy.query,
select: {
_id: true,
@ -159,7 +160,7 @@ export class Service extends DatabaseService<Model> {
protected override async onBeforeDelete(
deleteBy: DeleteBy<Model>
): Promise<OnDelete<Model>> {
const teamPermissions = await this.findBy({
const teamPermissions: Array<Model> = await this.findBy({
query: deleteBy.query,
select: {
_id: true,

View File

@ -961,7 +961,7 @@ export default class ModelPermission {
/// Check billing permissions.
if (IsBillingEnabled && props.currentPlan) {
const model = new modelType();
const model: BaseModel = new modelType();
if (
props.isSubscriptionUnpaid &&

View File

@ -248,7 +248,6 @@ const BasicForm: Function = <T extends Object>(
{({ form }: any) => {
return (
<RadioButtons
tabIndex={index}
onChange={async (value: string) => {
setCurrentValue({
...currentValue,
@ -262,12 +261,6 @@ const BasicForm: Function = <T extends Object>(
true
);
}}
onBlur={async () => {
await form.setFieldTouched(
fieldName,
true
);
}}
options={field.radioButtonOptions || []}
initialValue={
initialValues &&

View File

@ -15,9 +15,6 @@ export interface RadioButton {
export interface ComponentProps {
onChange: (value: string) => void;
initialValue?: string | undefined;
onFocus?: () => void;
onBlur?: () => void;
tabIndex?: number | undefined;
options: Array<RadioButton>;
}

View File

@ -64,7 +64,7 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
BILLING_ENABLED
) {
setPaymentMethodCountLoading(true);
const paymentMethodsCount = await ModelAPI.count(
const paymentMethodsCount: number = await ModelAPI.count(
BillingPaymentMethod,
{ projectId: props.selectedProject?._id }
);

View File

@ -14,7 +14,6 @@ import Project from 'Model/Models/Project';
import React, {
FunctionComponent,
ReactElement,
useEffect,
useRef,
useState,
} from 'react';
@ -61,7 +60,6 @@ const Settings: FunctionComponent<ComponentProps> = (
setIsModalLoading(false);
}, []);
const fetchSetupIntent: Function = async (): Promise<void> => {
try {
setIsModalLoading(true);

View File

@ -12,7 +12,9 @@ export interface ComponentProps {
formRef: Ref<any>;
}
const CheckoutForm: FunctionComponent<ComponentProps> = (props: ComponentProps): ReactElement => {
const CheckoutForm: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const stripe: any = useStripe();
const elements: any = useElements();

View File

@ -31,7 +31,10 @@ const Settings: FunctionComponent<ComponentProps> = (
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const payInvoice: Function = async (customerId: string, invoiceId: string): Promise<void> => {
const payInvoice: Function = async (
customerId: string,
invoiceId: string
): Promise<void> => {
try {
setIsLoading(true);