From bc41bba51199ce01796fdb9cbc51afbfdf2f0249 Mon Sep 17 00:00:00 2001 From: Simon Larsen Date: Thu, 17 Nov 2022 22:02:37 +0000 Subject: [PATCH] fix payment methods. --- Common/Types/API/URL.ts | 29 ++++++- .../DatabaseCommonInteractionProps.ts | 1 + CommonServer/API/BaseAPI.ts | 52 ++++++++++-- .../Services/BillingPaymentMethodService.ts | 34 ++++++-- CommonServer/Services/BillingService.ts | 2 +- CommonServer/Services/DatabaseService.ts | 80 +++++++++++-------- .../Settings/BillingPaymentMethodForm.tsx | 4 +- Model/Models/BillingPaymentMethod.ts | 15 ++++ 8 files changed, 163 insertions(+), 54 deletions(-) diff --git a/Common/Types/API/URL.ts b/Common/Types/API/URL.ts index be16546051..8939d0c840 100644 --- a/Common/Types/API/URL.ts +++ b/Common/Types/API/URL.ts @@ -42,7 +42,8 @@ export default class URL extends DatabaseProperty { public constructor( protocol: Protocol, hostname: Hostname | string, - route?: Route + route?: Route, + queryString?: string ) { super(); if (hostname instanceof Hostname) { @@ -56,6 +57,20 @@ export default class URL extends DatabaseProperty { if (route) { this.route = route; } + + if (queryString) { + const keyValues = queryString.split("&"); + for (const keyValue of keyValues) { + if (keyValue.split("=")[0] && keyValue.split("=")[1]) { + const key = keyValue.split("=")[0]; + const value = keyValue.split("=")[1]; + if (key && value) { + this._params[key] = value; + } + } + } + } + } public isHttps(): boolean { @@ -118,14 +133,22 @@ export default class URL extends DatabaseProperty { const hostname: Hostname = new Hostname(url.split('/')[0] || ''); let route: Route | undefined; + let queryString: string | undefined; if (url.split('/').length > 1) { const paths: Array = url.split('/'); paths.shift(); - route = new Route(paths.join('/')); + route = new Route(paths.join('/').split("?")[0]); + } - return new URL(protocol, hostname, route); + queryString = url.split("?")[1] || ''; + + return new URL(protocol, hostname, route, queryString); + } + + public removeQueryString(): URL { + return URL.fromString(this.toString().split("?")[0] || ''); } public addRoute(route: Route | string): URL { diff --git a/Common/Types/Database/DatabaseCommonInteractionProps.ts b/Common/Types/Database/DatabaseCommonInteractionProps.ts index e1fa81c1bd..e7a36ec6cd 100644 --- a/Common/Types/Database/DatabaseCommonInteractionProps.ts +++ b/Common/Types/Database/DatabaseCommonInteractionProps.ts @@ -16,4 +16,5 @@ export default interface DatabaseCommonInteractionProps { tenantId?: ObjectID | undefined; isRoot?: boolean | undefined; isMultiTenantRequest?: boolean | undefined; + ignoreHooks?: boolean | undefined; } diff --git a/CommonServer/API/BaseAPI.ts b/CommonServer/API/BaseAPI.ts index 6dd7d6f5fc..4ac512f674 100644 --- a/CommonServer/API/BaseAPI.ts +++ b/CommonServer/API/BaseAPI.ts @@ -27,12 +27,12 @@ export default class BaseAPI< TBaseModel extends BaseModel, TBaseService extends DatabaseService > { - public entityType: { new (): TBaseModel }; + public entityType: { new(): TBaseModel }; public router: ExpressRouter; public service: TBaseService; - public constructor(type: { new (): TBaseModel }, service: TBaseService) { + public constructor(type: { new(): TBaseModel }, service: TBaseService) { this.entityType = type; const router: ExpressRouter = Express.getRouter(); @@ -144,8 +144,8 @@ export default class BaseAPI< this.service = service; } - public getPermissionsForTenant(req:ExpressRequest): Array { - + public getPermissionsForTenant(req: ExpressRequest): Array { + const permissions: Array = []; const props = this.getDatabaseCommonInteractionProps(req); @@ -156,7 +156,7 @@ export default class BaseAPI< return props.userTenantAccessPermission[props.tenantId?.toString() || '']?.permissions || []; } - return permissions; + return permissions; } public getDatabaseCommonInteractionProps( @@ -206,7 +206,6 @@ export default class BaseAPI< res: ExpressResponse ): Promise { - console.log("LIST") await this.onBeforeList(req, res); const skip: PositiveNumber = req.query['skip'] @@ -281,6 +280,8 @@ export default class BaseAPI< ): Promise { let query: Query = {}; + await this.onBeforeCount(req, res); + if (req.body) { query = JSONFunctions.deserialize( req.body['query'] @@ -305,7 +306,7 @@ export default class BaseAPI< res: ExpressResponse ): Promise { const objectId: ObjectID = new ObjectID(req.params['id'] as string); - + await this.onBeforeGet(req, res); let select: Select = {}; let populate: Populate = {}; @@ -335,6 +336,7 @@ export default class BaseAPI< req: ExpressRequest, res: ExpressResponse ): Promise { + await this.onBeforeDelete(req, res); const objectId: ObjectID = new ObjectID(req.params['id'] as string); await this.service.deleteBy({ @@ -351,6 +353,7 @@ export default class BaseAPI< req: ExpressRequest, res: ExpressResponse ): Promise { + await this.onBeforeUpdate(req, res); const objectId: ObjectID = new ObjectID(req.params['id'] as string); const objectIdString: string = objectId.toString(); const body: JSONObject = req.body; @@ -378,6 +381,7 @@ export default class BaseAPI< req: ExpressRequest, res: ExpressResponse ): Promise { + await this.onBeforeCreate(req, res); const body: JSONObject = req.body; const item: TBaseModel = BaseModel.fromJSON( @@ -418,4 +422,38 @@ export default class BaseAPI< ): Promise { return Promise.resolve(true); } + + + protected async onBeforeCreate( + _req: ExpressRequest, _res: ExpressResponse + ): Promise { + return Promise.resolve(true); + } + + protected async onBeforeGet( + _req: ExpressRequest, _res: ExpressResponse + ): Promise { + return Promise.resolve(true); + } + + protected async onBeforeUpdate( + _req: ExpressRequest, _res: ExpressResponse + ): Promise { + return Promise.resolve(true); + } + + + protected async onBeforeDelete( + _req: ExpressRequest, _res: ExpressResponse + ): Promise { + return Promise.resolve(true); + } + + protected async onBeforeCount( + _req: ExpressRequest, _res: ExpressResponse + ): Promise { + return Promise.resolve(true); + } + + } diff --git a/CommonServer/Services/BillingPaymentMethodService.ts b/CommonServer/Services/BillingPaymentMethodService.ts index f9daddcfd0..f0d4fd3651 100644 --- a/CommonServer/Services/BillingPaymentMethodService.ts +++ b/CommonServer/Services/BillingPaymentMethodService.ts @@ -7,6 +7,7 @@ import BadDataException from 'Common/Types/Exception/BadDataException'; import Project from 'Model/Models/Project'; import BillingService from './BillingService'; import DeleteBy from '../Types/Database/DeleteBy'; +import LIMIT_MAX from 'Common/Types/Database/LimitMax'; export class Service extends DatabaseService { public constructor(postgresDatabase?: PostgresDatabase) { @@ -15,7 +16,6 @@ export class Service extends DatabaseService { protected override async onBeforeFind(findBy: FindBy): Promise> { - console.log(findBy.props); if (!findBy.props.tenantId) { throw new BadDataException("ProjectID not found.") @@ -25,7 +25,8 @@ export class Service extends DatabaseService { id: findBy.props.tenantId!, props: { ...findBy.props, - isRoot: true + isRoot: true, + ignoreHooks: true }, select: { _id: true, @@ -62,7 +63,8 @@ export class Service extends DatabaseService { billingPaymentMethod.last4Digits = paymentMethod.last4Digits; billingPaymentMethod.isDefault = paymentMethod.isDefault; billingPaymentMethod.paymentProviderPaymentMethodId = paymentMethod.id; - + billingPaymentMethod.paymentProviderCustomerId = project.paymentProviderCustomerId; + await this.create({ data: billingPaymentMethod, props: { @@ -75,11 +77,29 @@ export class Service extends DatabaseService { return { findBy, carryForward: paymentMethods }; } - protected override onBeforeDelete(deleteBy: DeleteBy): Promise> { + protected override async onBeforeDelete(deleteBy: DeleteBy): Promise> { const items = await this.findBy({ - query: deleteBy.query, - - }) + query: deleteBy.query, + select: { + _id: true, + paymentProviderPaymentMethodId: true, + paymentProviderCustomerId: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + ignoreHooks: true + } + }); + + for (const item of items) { + if (item.paymentProviderPaymentMethodId && item.paymentProviderCustomerId) { + await BillingService.deletePaymentMethod(item.paymentProviderCustomerId, item.paymentProviderPaymentMethodId); + } + } + + return { deleteBy, carryForward: null }; } } diff --git a/CommonServer/Services/BillingService.ts b/CommonServer/Services/BillingService.ts index 8ff32ee6c3..3ebcf66e4f 100644 --- a/CommonServer/Services/BillingService.ts +++ b/CommonServer/Services/BillingService.ts @@ -206,7 +206,7 @@ export class BillingService { const paymenMethods = await this.getPaymentMethods(customerId); if (paymenMethods.length === 1) { - throw new BadDataException("There's only one poayment method associated with this account. It cannot be deleted. To delete this payment method please add more payment methods to your account."); + 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."); } await this.stripe.paymentMethods.detach(paymentMethodId); diff --git a/CommonServer/Services/DatabaseService.ts b/CommonServer/Services/DatabaseService.ts index ec9b274cd7..d8fe368905 100644 --- a/CommonServer/Services/DatabaseService.ts +++ b/CommonServer/Services/DatabaseService.ts @@ -465,7 +465,7 @@ class DatabaseService { } public async create(createBy: CreateBy): Promise { - const onCreate: OnCreate = await this._onBeforeCreate( + const onCreate: OnCreate = createBy.props.ignoreHooks ? { createBy, carryForward: [] } : await this._onBeforeCreate( createBy ); @@ -519,13 +519,15 @@ class DatabaseService { try { createBy.data = await this.getRepository().save(createBy.data); - createBy.data = await this.onCreateSuccess( - { - createBy, - carryForward, - }, - createBy.data - ); + if (!createBy.props.ignoreHooks) { + createBy.data = await this.onCreateSuccess( + { + createBy, + carryForward, + }, + createBy.data + ); + } return createBy.data; } catch (error) { await this.onCreateError(error as Exception); @@ -653,7 +655,7 @@ class DatabaseService { ); findBy.query = checkReadPermissionType.query; - let count: number = 0; + let count: number = 0; if (distinctOn) { const queryBuilder: SelectQueryBuilder = this.getQueryBuilder(this.modelName) @@ -668,14 +670,14 @@ class DatabaseService { count = await queryBuilder.getCount(); } else { - count = await this.getRepository().count({ + count = await this.getRepository().count({ where: findBy.query as any, skip: (findBy.skip as PositiveNumber).toNumber(), take: (findBy.limit as PositiveNumber).toNumber(), }); } - + let countPositive: PositiveNumber = new PositiveNumber(count); countPositive = await this.onCountSuccess(countPositive); return countPositive; @@ -697,7 +699,7 @@ class DatabaseService { private async _deleteBy(deleteBy: DeleteBy): Promise { try { - const onDelete: OnDelete = await this.onBeforeDelete( + const onDelete: OnDelete = deleteBy.props.ignoreHooks ? { deleteBy, carryForward: [] } : await this.onBeforeDelete( deleteBy ); const beforeDeleteBy: DeleteBy = onDelete.deleteBy; @@ -716,7 +718,7 @@ class DatabaseService { limit: LIMIT_MAX, populate: {}, select: {}, - props: beforeDeleteBy.props, + props: { ...beforeDeleteBy.props, ignoreHooks: true }, }); await this._updateBy({ @@ -726,19 +728,24 @@ class DatabaseService { } as any, props: { isRoot: true, + ignoreHooks: true }, }); const numberOfDocsAffected: number = (await this.getRepository().delete(beforeDeleteBy.query as any)) .affected || 0; + + + if (!deleteBy.props.ignoreHooks) { - await this.onDeleteSuccess( - { deleteBy, carryForward }, - items.map((i: TBaseModel) => { - return new ObjectID(i._id!); - }) - ); + await this.onDeleteSuccess( + { deleteBy, carryForward }, + items.map((i: TBaseModel) => { + return new ObjectID(i._id!); + }) + ); + } return numberOfDocsAffected; } catch (error) { @@ -762,7 +769,7 @@ class DatabaseService { createdAt: SortOrder.Descending, }; } - const onFind: OnFind = await this.onBeforeFind(findBy); + const onFind: OnFind = findBy.props.ignoreHooks ? { findBy, carryForward: [] } : await this.onBeforeFind(findBy); const onBeforeFind: FindBy = onFind.findBy; const carryForward: any = onFind.carryForward; @@ -824,13 +831,14 @@ class DatabaseService { decryptedItems, onBeforeFind ); - - decryptedItems = await ( - await this.onFindSuccess( - { findBy, carryForward }, - decryptedItems - ) - ).carryForward; + if (!findBy.props.ignoreHooks) { + decryptedItems = await ( + await this.onFindSuccess( + { findBy, carryForward }, + decryptedItems + ) + ).carryForward; + } return decryptedItems; } catch (error) { @@ -934,7 +942,7 @@ class DatabaseService { private async _updateBy(updateBy: UpdateBy): Promise { try { - const onUpdate: OnUpdate = await this.onBeforeUpdate( + const onUpdate: OnUpdate = updateBy.props.ignoreHooks ? { updateBy, carryForward: [] } : await this.onBeforeUpdate( updateBy ); @@ -961,7 +969,7 @@ class DatabaseService { limit: LIMIT_MAX, populate: {}, select: {}, - props: beforeUpdateBy.props, + props: { ...beforeUpdateBy.props, ignoreHooks: true }, }); for (let item of items) { @@ -984,12 +992,14 @@ class DatabaseService { // ) // ).affected || 0; - await this.onUpdateSuccess( - { updateBy, carryForward }, - items.map((i: TBaseModel) => { - return new ObjectID(i._id!); - }) - ); + if (!updateBy.props.ignoreHooks) { + await this.onUpdateSuccess( + { updateBy, carryForward }, + items.map((i: TBaseModel) => { + return new ObjectID(i._id!); + }) + ); + } return items.length; } catch (error) { diff --git a/Dashboard/src/Pages/Settings/BillingPaymentMethodForm.tsx b/Dashboard/src/Pages/Settings/BillingPaymentMethodForm.tsx index 505d328f31..31e49402e8 100644 --- a/Dashboard/src/Pages/Settings/BillingPaymentMethodForm.tsx +++ b/Dashboard/src/Pages/Settings/BillingPaymentMethodForm.tsx @@ -24,12 +24,14 @@ const CheckoutForm = (props: ComponentProps) => { // Make sure to disable form submission until Stripe.js has loaded. return; } + + console.log(Navigation.getCurrentURL().removeQueryString().toString()); const { error } = await stripe.confirmSetup({ //`Elements` instance that was used to create the Payment Element elements, confirmParams: { - return_url: Navigation.getCurrentURL().toString(), + return_url: Navigation.getCurrentURL().removeQueryString().toString(), }, }); diff --git a/Model/Models/BillingPaymentMethod.ts b/Model/Models/BillingPaymentMethod.ts index 18fb0b1f2b..ac89cf028b 100644 --- a/Model/Models/BillingPaymentMethod.ts +++ b/Model/Models/BillingPaymentMethod.ts @@ -175,6 +175,21 @@ export default class BillingPaymentMethod extends BaseModel { public paymentProviderPaymentMethodId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.ProjectOwner], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: false, + }) + public paymentProviderCustomerId?: string = undefined; + + @ColumnAccessControl({ create: [Permission.ProjectOwner], read: [Permission.ProjectOwner],