From ded0e2638bd7b9bf972bd11e140be2063fa809d8 Mon Sep 17 00:00:00 2001 From: Simon Larsen Date: Fri, 9 Jun 2023 18:20:53 +0100 Subject: [PATCH] make manual recharge work --- CommonServer/API/NotificationAPI.ts | 116 ++++++++++++------ CommonServer/Services/NotificationService.ts | 50 ++++---- .../ModelDetail/CardModelDetail.tsx | 18 ++- Dashboard/src/Pages/Settings/CallSms.tsx | 44 ++++--- Notification/Services/SmsService.ts | 24 ++-- 5 files changed, 152 insertions(+), 100 deletions(-) diff --git a/CommonServer/API/NotificationAPI.ts b/CommonServer/API/NotificationAPI.ts index e5aee7b827..4f508a4601 100644 --- a/CommonServer/API/NotificationAPI.ts +++ b/CommonServer/API/NotificationAPI.ts @@ -15,45 +15,89 @@ import Exception from 'Common/Types/Exception/Exception'; const router: ExpressRouter = Express.getRouter(); -router.post('/notification/recharge', UserMiddleware.getUserMiddleware, async (req: ExpressRequest, res: ExpressResponse) => { +router.post( + '/notification/recharge', + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + try { + const amount: number = req.body.amount; + const projectId: ObjectID = JSONFunctions.deserializeValue( + req.body.projectId + ) as ObjectID; - try { - const amount: number = req.body.amount; - const projectId: ObjectID = JSONFunctions.deserializeValue(req.body.projectId) as ObjectID; + if (!amount || typeof amount !== 'number') { + return Response.sendErrorResponse( + req, + res, + new BadDataException('Invalid amount') + ); + } - if (!amount || typeof amount !== 'number') { - return Response.sendErrorResponse(req, res, new BadDataException('Invalid amount')); + if (amount > 1000) { + return Response.sendErrorResponse( + req, + res, + new BadDataException('Amount cannot be greater than 1000') + ); + } + + if (amount < 20) { + return Response.sendErrorResponse( + req, + res, + new BadDataException('Amount cannot be less than 20') + ); + } + + if (!projectId || !projectId.toString()) { + return Response.sendErrorResponse( + req, + res, + new BadDataException('Invalid projectId') + ); + } + + // get permissions. if user has permission to recharge, then recharge + + if ( + !(req as OneUptimeRequest).userTenantAccessPermission || + !(req as OneUptimeRequest).userTenantAccessPermission![ + projectId.toString() + ] + ) { + return Response.sendErrorResponse( + req, + res, + new BadDataException('Permission for this user not found') + ); + } + + const permissions: Array = ( + req as OneUptimeRequest + ).userTenantAccessPermission![ + projectId.toString() + ]!.permissions.map((permission: UserPermission) => { + return permission.permission; + }); + + if ( + permissions.includes(Permission.ProjectOwner) || + permissions.includes(Permission.CanManageProjectBilling) + ) { + await NotificationService.rechargeBalance(projectId, amount); + } else { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + 'User does not have permission to recharge. You need any one of these permissions - ProjectOwner, CanManageProjectBilling' + ) + ); + } + } catch (err: any) { + return Response.sendErrorResponse(req, res, err as Exception); } - - if(amount > 1000) { - return Response.sendErrorResponse(req, res, new BadDataException('Amount cannot be greater than 1000')); - } - - if(amount < 20){ - return Response.sendErrorResponse(req, res, new BadDataException('Amount cannot be less than 20')); - } - - if (!projectId || !projectId.toString()) { - return Response.sendErrorResponse(req, res, new BadDataException('Invalid projectId')); - } - - - // get permissions. if user has permission to recharge, then recharge - - if (!(req as OneUptimeRequest).userTenantAccessPermission || !(req as OneUptimeRequest).userTenantAccessPermission![projectId.toString()]) { - return Response.sendErrorResponse(req, res, new BadDataException('Permission for this user not found')); - } - - const permissions = (req as OneUptimeRequest).userTenantAccessPermission![projectId.toString()]!.permissions.map((permission: UserPermission) => permission.permission); - - if (permissions.includes(Permission.ProjectOwner) || permissions.includes(Permission.CanManageProjectBilling)) { - await NotificationService.rechargeBalance(projectId, amount) - } else { - return Response.sendErrorResponse(req, res, new BadDataException('User does not have permission to recharge. You need any one of these permissions - ProjectOwner, CanManageProjectBilling')); - } - } catch (err: any) { - return Response.sendErrorResponse(req, res, err as Exception); } -}); +); export default router; diff --git a/CommonServer/Services/NotificationService.ts b/CommonServer/Services/NotificationService.ts index 36bf1e5e90..f996c2087c 100644 --- a/CommonServer/Services/NotificationService.ts +++ b/CommonServer/Services/NotificationService.ts @@ -7,8 +7,10 @@ import logger from '../Utils/Logger'; import BadDataException from 'Common/Types/Exception/BadDataException'; export default class NotificationService { - - public static async rechargeBalance(projectId: ObjectID, amountInUSD: number): Promise { + public static async rechargeBalance( + projectId: ObjectID, + amountInUSD: number + ): Promise { const project: Project | null = await ProjectService.findOneById({ id: projectId, select: { @@ -52,8 +54,9 @@ export default class NotificationService { await ProjectService.sendEmailToProjectOwners( project.id!, 'ACTION REQUIRED: SMS and Call Recharge Failed for project - ' + - (project.name || ''), - `We have tried to recharge your SMS and Call balance for project - ${project.name || '' + (project.name || ''), + `We have tried to recharge your SMS and Call balance for project - ${ + project.name || '' } and failed. We could not find a payment method for the project. Please add a payment method in project settings.` ); } @@ -62,11 +65,10 @@ export default class NotificationService { ); } - // recharge balance const updatedAmount: number = Math.floor( (project.smsOrCallCurrentBalanceInUSDCents || 0) + - amountInUSD * 100 + amountInUSD * 100 ); // If the recharge is succcessful, then update the project balance. @@ -78,12 +80,10 @@ export default class NotificationService { await ProjectService.updateOneById({ data: { - smsOrCallCurrentBalanceInUSDCents: - updatedAmount, + smsOrCallCurrentBalanceInUSDCents: updatedAmount, failedCallAndSMSBalanceChargeNotificationSentToOwners: false, // reset this flag - lowCallAndSMSBalanceNotificationSentToOwners: - false, // reset this flag + lowCallAndSMSBalanceNotificationSentToOwners: false, // reset this flag notEnabledSmsNotificationSentToOwners: false, }, id: project.id!, @@ -95,21 +95,21 @@ export default class NotificationService { await ProjectService.sendEmailToProjectOwners( project.id!, 'SMS and Call Recharge Successful for project - ' + - (project.name || ''), - `We have successfully recharged your SMS and Call balance for project - ${project.name || '' - } by ${amountInUSD} USD. Your current balance is ${updatedAmount / 100 + (project.name || ''), + `We have successfully recharged your SMS and Call balance for project - ${ + project.name || '' + } by ${amountInUSD} USD. Your current balance is ${ + updatedAmount / 100 } USD.` ); - project.smsOrCallCurrentBalanceInUSDCents = - updatedAmount; + project.smsOrCallCurrentBalanceInUSDCents = updatedAmount; return updatedAmount; } catch (err) { await ProjectService.updateOneById({ data: { - failedCallAndSMSBalanceChargeNotificationSentToOwners: - true, + failedCallAndSMSBalanceChargeNotificationSentToOwners: true, }, id: project.id!, props: { @@ -119,8 +119,9 @@ export default class NotificationService { await ProjectService.sendEmailToProjectOwners( project.id!, 'ACTION REQUIRED: SMS and Call Recharge Failed for project - ' + - (project.name || ''), - `We have tried recharged your SMS and Call balance for project - ${project.name || '' + (project.name || ''), + `We have tried recharged your SMS and Call balance for project - ${ + project.name || '' } and failed. Please make sure your payment method is upto date and has sufficient balance. You can add new payment methods in project settings.` ); logger.error(err); @@ -166,7 +167,6 @@ export default class NotificationService { ? options.enableAutoRechargeSmsOrCallBalance : project?.enableAutoRechargeSmsOrCallBalance || false; - if (!project) { return 0; } @@ -180,11 +180,11 @@ export default class NotificationService { (project.smsOrCallCurrentBalanceInUSDCents || 0) / 100 < autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD ) { - - const updatedAmount: number = await this.rechargeBalance(projectId, autoRechargeSmsOrCallByBalanceInUSD); - project.smsOrCallCurrentBalanceInUSDCents = - updatedAmount; - + const updatedAmount: number = await this.rechargeBalance( + projectId, + autoRechargeSmsOrCallByBalanceInUSD + ); + project.smsOrCallCurrentBalanceInUSDCents = updatedAmount; } } } diff --git a/CommonUI/src/Components/ModelDetail/CardModelDetail.tsx b/CommonUI/src/Components/ModelDetail/CardModelDetail.tsx index 99ef0ea02f..3b62748764 100644 --- a/CommonUI/src/Components/ModelDetail/CardModelDetail.tsx +++ b/CommonUI/src/Components/ModelDetail/CardModelDetail.tsx @@ -61,19 +61,17 @@ const CardModelDetail: Function = ( let cardButtons: Array = []; if (props.isEditable && hasPermissionToEdit) { - cardButtons.push( - { - title: props.editButtonText || `Edit ${model.singularName}`, - buttonStyle: ButtonStyleType.NORMAL, - onClick: () => { - setShowModal(true); - }, - icon: IconProp.Edit, + cardButtons.push({ + title: props.editButtonText || `Edit ${model.singularName}`, + buttonStyle: ButtonStyleType.NORMAL, + onClick: () => { + setShowModal(true); }, - ); + icon: IconProp.Edit, + }); } - if(props.cardProps.buttons) { + if (props.cardProps.buttons) { cardButtons = cardButtons.concat(...props.cardProps.buttons); } diff --git a/Dashboard/src/Pages/Settings/CallSms.tsx b/Dashboard/src/Pages/Settings/CallSms.tsx index 17e3153b64..809d6c4b75 100644 --- a/Dashboard/src/Pages/Settings/CallSms.tsx +++ b/Dashboard/src/Pages/Settings/CallSms.tsx @@ -21,11 +21,13 @@ import Navigation from 'CommonUI/src/Utils/Navigation'; const Settings: FunctionComponent = ( _props: PageComponentProps ): ReactElement => { - - - const [showRechargeBalanceModal, setShowRechargeBalanceModal] = useState(false); - const [isRechargeBalanceLoading, setIsRechargeBalanceLoading] = useState(false); - const [rechargeBalanceError, setRechargeBalanceError] = useState(null); + const [showRechargeBalanceModal, setShowRechargeBalanceModal] = + useState(false); + const [isRechargeBalanceLoading, setIsRechargeBalanceLoading] = + useState(false); + const [rechargeBalanceError, setRechargeBalanceError] = useState< + string | null + >(null); return ( = ( icon: IconProp.Add, onClick: () => { setShowRechargeBalanceModal(true); - } - } - ] + }, + }, + ], }} isEditable={false} modelDetailProps={{ @@ -322,8 +324,7 @@ const Settings: FunctionComponent = ( <> )} - - {showRechargeBalanceModal ? + {showRechargeBalanceModal ? ( { @@ -335,10 +336,16 @@ const Settings: FunctionComponent = ( onSubmit={async (item: JSONObject) => { setIsRechargeBalanceLoading(true); try { - await API.post(URL.fromString(DASHBOARD_API_URL.toString()).addRoute("/notification/recharge"), { - amount: item['amount'], - projectId: DashboardNavigation.getProjectId()?.toString(), - }); + await API.post( + URL.fromString( + DASHBOARD_API_URL.toString() + ).addRoute('/notification/recharge'), + { + amount: item['amount'], + projectId: + DashboardNavigation.getProjectId()?.toString(), + } + ); setIsRechargeBalanceLoading(false); setShowRechargeBalanceModal(false); Navigation.reload(); @@ -346,10 +353,8 @@ const Settings: FunctionComponent = ( setRechargeBalanceError(API.getFriendlyMessage(e)); setIsRechargeBalanceLoading(false); } - }} formProps={{ - error: rechargeBalanceError || '', fields: [ { @@ -366,11 +371,12 @@ const Settings: FunctionComponent = ( }, fieldType: FormFieldSchemaType.PositveNumber, }, - ], }} - /> : <>} - + /> + ) : ( + <> + )} ); }; diff --git a/Notification/Services/SmsService.ts b/Notification/Services/SmsService.ts index 4fa56c4964..4a0b259702 100644 --- a/Notification/Services/SmsService.ts +++ b/Notification/Services/SmsService.ts @@ -106,7 +106,7 @@ export default class SmsService { await ProjectService.sendEmailToProjectOwners( project.id!, 'SMS notifications not enabled for ' + - (project.name || ''), + (project.name || ''), `We tried to send an SMS to ${to.toString()} with message:

${message}

This SMS was not sent because SMS notifications are not enabled for this project. Please enable SMS notifications in project settings.` ); } @@ -114,11 +114,13 @@ export default class SmsService { } // check if auto recharge is enabled and current balance is low. - let updatedBalance: number = project.smsOrCallCurrentBalanceInUSDCents!; + let updatedBalance: number = + project.smsOrCallCurrentBalanceInUSDCents!; try { - updatedBalance = await NotificationService.rechargeIfBalanceIsLow( - project.id! - ); + updatedBalance = + await NotificationService.rechargeIfBalanceIsLow( + project.id! + ); } catch (err) { logger.error(err); } @@ -149,8 +151,9 @@ export default class SmsService { await ProjectService.sendEmailToProjectOwners( project.id!, 'Low SMS and Call Balance for ' + - (project.name || ''), - `We tried to send an SMS to ${to.toString()} with message:

${message}
This SMS was not sent because project does not have enough balance to send SMS. Current balance is ${project.smsOrCallCurrentBalanceInUSDCents || 0 + (project.name || ''), + `We tried to send an SMS to ${to.toString()} with message:

${message}
This SMS was not sent because project does not have enough balance to send SMS. Current balance is ${ + project.smsOrCallCurrentBalanceInUSDCents || 0 } USD cents. Required balance to send this SMS should is ${SMSDefaultCostInCents} USD cents. Please enable auto recharge or recharge manually.` ); } @@ -183,8 +186,9 @@ export default class SmsService { await ProjectService.sendEmailToProjectOwners( project.id!, 'Low SMS and Call Balance for ' + - (project.name || ''), - `We tried to send an SMS to ${to.toString()} with message:

${message}

This SMS was not sent because project does not have enough balance to send SMS. Current balance is ${project.smsOrCallCurrentBalanceInUSDCents + (project.name || ''), + `We tried to send an SMS to ${to.toString()} with message:

${message}

This SMS was not sent because project does not have enough balance to send SMS. Current balance is ${ + project.smsOrCallCurrentBalanceInUSDCents } cents. Required balance is ${SMSDefaultCostInCents} cents to send this SMS. Please enable auto recharge or recharge manually.` ); } @@ -210,7 +214,7 @@ export default class SmsService { project.smsOrCallCurrentBalanceInUSDCents = Math.floor( project.smsOrCallCurrentBalanceInUSDCents! - - SMSDefaultCostInCents + SMSDefaultCostInCents ); await ProjectService.updateOneById({