make manual recharge work

This commit is contained in:
Simon Larsen
2023-06-09 18:20:53 +01:00
parent bb609e2ccc
commit ded0e2638b
5 changed files with 152 additions and 100 deletions

View File

@@ -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<Permission> = (
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;

View File

@@ -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<number> {
public static async rechargeBalance(
projectId: ObjectID,
amountInUSD: number
): Promise<number> {
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;
}
}
}

View File

@@ -61,19 +61,17 @@ const CardModelDetail: Function = <TBaseModel extends BaseModel>(
let cardButtons: Array<CardButtonSchema> = [];
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);
}

View File

@@ -21,11 +21,13 @@ import Navigation from 'CommonUI/src/Utils/Navigation';
const Settings: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
const [showRechargeBalanceModal, setShowRechargeBalanceModal] = useState<boolean>(false);
const [isRechargeBalanceLoading, setIsRechargeBalanceLoading] = useState<boolean>(false);
const [rechargeBalanceError, setRechargeBalanceError] = useState<string | null>(null);
const [showRechargeBalanceModal, setShowRechargeBalanceModal] =
useState<boolean>(false);
const [isRechargeBalanceLoading, setIsRechargeBalanceLoading] =
useState<boolean>(false);
const [rechargeBalanceError, setRechargeBalanceError] = useState<
string | null
>(null);
return (
<Page
@@ -67,9 +69,9 @@ const Settings: FunctionComponent<PageComponentProps> = (
icon: IconProp.Add,
onClick: () => {
setShowRechargeBalanceModal(true);
}
}
]
},
},
],
}}
isEditable={false}
modelDetailProps={{
@@ -322,8 +324,7 @@ const Settings: FunctionComponent<PageComponentProps> = (
<></>
)}
{showRechargeBalanceModal ?
{showRechargeBalanceModal ? (
<BasicFormModal
title={'Recharge Balance'}
onClose={() => {
@@ -335,10 +336,16 @@ const Settings: FunctionComponent<PageComponentProps> = (
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<PageComponentProps> = (
setRechargeBalanceError(API.getFriendlyMessage(e));
setIsRechargeBalanceLoading(false);
}
}}
formProps={{
error: rechargeBalanceError || '',
fields: [
{
@@ -366,11 +371,12 @@ const Settings: FunctionComponent<PageComponentProps> = (
},
fieldType: FormFieldSchemaType.PositveNumber,
},
],
}}
/> : <></>}
/>
) : (
<></>
)}
</Page>
);
};

View File

@@ -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: <br/> <br/> ${message} <br/> <br/> 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: <br/> <br/> ${message} <br/>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: <br/> <br/> ${message} <br/>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: <br/> <br/> ${message} <br/> <br/> 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: <br/> <br/> ${message} <br/> <br/> 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({