implement emails for erorr states

This commit is contained in:
Simon Larsen
2023-06-09 13:39:20 +01:00
parent 7baafd0db6
commit 1ed51a6dc2
8 changed files with 204 additions and 10 deletions

View File

@@ -23,7 +23,7 @@ export default class Hostname extends DatabaseProperty {
if (Hostname.isValid(value)) {
this._route = value;
} else {
throw new BadDataException('Hostname is not in valid format.');
throw new BadDataException('Hostname '+value+' is not in valid format.');
}
}

View File

@@ -31,6 +31,7 @@ enum EmailTemplateType {
StatusPageOwnerResourceCreated = 'StatusPageOwnerResourceCreated.hbs',
StatusPageOwnerAdded = 'StatusPageOwnerAdded.hbs',
StatusPageOwnerAnnouncementPosted = 'StatusPageOwnerAnnouncementPosted.hbs',
SimpleMessage = 'SimpleMessage.hbs',
}
export default EmailTemplateType;

View File

@@ -531,7 +531,21 @@ export class BillingService {
await this.stripe.invoices.finalizeInvoice(invoice.id!);
await this.payInvoice(customerId, invoice.id!);
try {
await this.payInvoice(customerId, invoice.id!);
} catch (err) {
// mark invoice as failed and do not collect payment.
await this.voidInvoice(invoice.id!);
throw err;
}
}
public static async voidInvoice(invoiceId: string): Promise<Stripe.Invoice> {
const invoice = await this.stripe.invoices.voidInvoice(
invoiceId
);
return invoice;
}
public static async payInvoice(

View File

@@ -28,6 +28,8 @@ export default class NotificationService {
autoRechargeSmsOrCallByBalanceInUSD: true,
autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: true,
paymentProviderCustomerId: true,
name: true,
failedCallAndSMSBalanceChargeNotificationSentToOwners: true
},
props: {
isRoot: true,
@@ -56,6 +58,19 @@ export default class NotificationService {
project.paymentProviderCustomerId!
))
) {
if (!project.failedCallAndSMSBalanceChargeNotificationSentToOwners) {
await ProjectService.updateOneById({
data: {
failedCallAndSMSBalanceChargeNotificationSentToOwners:
true,
},
id: project.id!,
props: {
isRoot: true,
},
});
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 || ''} and failed. We could not find a payment method for the project. Please add a payment method in project settings.`);
}
throw new BadDataException(
'No payment methods found for the project. Please add a payment method in project settings to continue.'
);
@@ -74,7 +89,7 @@ export default class NotificationService {
// recharge balance
const updatedAmount: number = Math.floor(
(project.smsOrCallCurrentBalanceInUSDCents || 0) +
autoRechargeSmsOrCallByBalanceInUSD * 100
autoRechargeSmsOrCallByBalanceInUSD * 100
);
// If the recharge is succcessful, then update the project balance.
@@ -88,6 +103,9 @@ export default class NotificationService {
data: {
smsOrCallCurrentBalanceInUSDCents:
updatedAmount,
failedCallAndSMSBalanceChargeNotificationSentToOwners: false, // reset this flag
lowCallAndSMSBalanceNotificationSentToOwners: false, // reset this flag
notEnabledSmsNotificationSentToOwners: false
},
id: project.id!,
props: {
@@ -95,12 +113,24 @@ 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 ${autoRechargeSmsOrCallByBalanceInUSD} USD. Your current balance is ${updatedAmount / 100} USD.`);
project.smsOrCallCurrentBalanceInUSDCents =
updatedAmount;
// TODO: Send an email on successful recharge.
} catch (err) {
// TODO: if the recharge fails, then send email to the user.
await ProjectService.updateOneById({
data: {
failedCallAndSMSBalanceChargeNotificationSentToOwners:
true,
},
id: project.id!,
props: {
isRoot: true,
},
});
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 || ''} 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);
}
}

View File

@@ -43,6 +43,10 @@ import AccessTokenService from './AccessTokenService';
import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus';
import User from 'Model/Models/User';
import NotificationService from './NotificationService';
import MailService from './MailService';
import logger from '../Utils/Logger';
import Email from 'Common/Types/Email';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -185,7 +189,7 @@ export class Service extends DatabaseService<Model> {
plan,
project.paymentProviderSubscriptionSeats as number,
plan.getYearlyPlanId() ===
updateBy.data.paymentProviderPlanId,
updateBy.data.paymentProviderPlanId,
project.trialEndsAt
);
@@ -724,10 +728,37 @@ export class Service extends DatabaseService<Model> {
plan === PlanSelect.Free
? false
: SubscriptionPlan.isUnpaid(
project.paymentProviderSubscriptionStatus ||
SubscriptionStatus.Active
),
project.paymentProviderSubscriptionStatus ||
SubscriptionStatus.Active
),
};
}
public async sendEmailToProjectOwners(projectId: ObjectID, subject: string, message: string): Promise<void> {
const owners: Array<User> = await this.getOwners(projectId);
if (owners.length === 0) {
return;
}
const emails: Array<Email> = owners.map((owner: User) => {
return owner.email!
});
for(const email of emails) {
MailService.sendMail({
toEmail: email,
templateType: EmailTemplateType.SimpleMessage,
vars: {
subject: subject,
message: message,
},
subject: subject,
}).catch((err: Error) => {
logger.error(err);
});
}
}
}
export default new Service();

View File

@@ -636,4 +636,66 @@ export default class Model extends TenantModel {
type: ColumnType.Boolean,
})
public enableAutoRechargeSmsOrCallBalance?: boolean = undefined;
@ColumnAccessControl({
create: [],
read: [],
update: [],
})
@TableColumn({
required: true,
isDefaultValueColumn: true,
type: TableColumnType.Boolean,
title: 'Low Call and SMS Balance Notification Sent to Owners',
description:
'Low Call and SMS Balance Notification Sent to Owners',
})
@Column({
nullable: false,
default: false,
type: ColumnType.Boolean,
})
public lowCallAndSMSBalanceNotificationSentToOwners?: boolean = undefined;
@ColumnAccessControl({
create: [],
read: [],
update: [],
})
@TableColumn({
required: true,
isDefaultValueColumn: true,
type: TableColumnType.Boolean,
title: 'Failed Call and SMS Balance Charge Notification Sent to Owners',
description:
'Failed Call and SMS Balance Charge Notification Sent to Owners',
})
@Column({
nullable: false,
default: false,
type: ColumnType.Boolean,
})
public failedCallAndSMSBalanceChargeNotificationSentToOwners?: boolean = undefined;
@ColumnAccessControl({
create: [],
read: [],
update: [],
})
@TableColumn({
required: true,
isDefaultValueColumn: true,
type: TableColumnType.Boolean,
title: 'Failed Call and SMS Balance Charge Notification Sent to Owners',
description:
'Failed Call and SMS Balance Charge Notification Sent to Owners',
})
@Column({
nullable: false,
default: false,
type: ColumnType.Boolean,
})
public notEnabledSmsNotificationSentToOwners?: boolean = undefined;
}

View File

@@ -61,6 +61,9 @@ export default class SmsService {
select: {
smsOrCallCurrentBalanceInUSDCents: true,
enableSmsNotifications: true,
lowCallAndSMSBalanceNotificationSentToOwners: true,
name: true,
notEnabledSmsNotificationSentToOwners: true
},
props: {
isRoot: true,
@@ -82,12 +85,26 @@ export default class SmsService {
if (!project.enableSmsNotifications) {
smsLog.status = SmsStatus.Error;
smsLog.statusMessage = `SMS notifications are not enabled for this project. Please enable SMS notifications in project settings.`;
await SmsLogService.create({
data: smsLog,
props: {
isRoot: true,
},
});
if (!project.notEnabledSmsNotificationSentToOwners) {
await ProjectService.updateOneById({
data: {
notEnabledSmsNotificationSentToOwners:
true,
},
id: project.id!,
props: {
isRoot: true,
},
});
await ProjectService.sendEmailToProjectOwners(project.id!, "SMS notifications not enabled 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 SMS notifications are not enabled for this project. Please enable SMS notifications in project settings.`);
}
return;
}
@@ -107,6 +124,20 @@ export default class SmsService {
isRoot: true,
},
});
if (!project.lowCallAndSMSBalanceNotificationSentToOwners) {
await ProjectService.updateOneById({
data: {
lowCallAndSMSBalanceNotificationSentToOwners:
true,
},
id: project.id!,
props: {
isRoot: true,
},
});
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} USD cents. Required balance to send this SMS should is ${SMSDefaultCostInCents} USD cents. Please enable auto recharge or recharge manually.`);
}
return;
}
@@ -122,6 +153,19 @@ export default class SmsService {
isRoot: true,
},
});
if (!project.lowCallAndSMSBalanceNotificationSentToOwners) {
await ProjectService.updateOneById({
data: {
lowCallAndSMSBalanceNotificationSentToOwners:
true,
},
id: project.id!,
props: {
isRoot: true,
},
});
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} cents. Required balance is ${SMSDefaultCostInCents} cents to send this SMS. Please enable auto recharge or recharge manually.`);
}
return;
}
}
@@ -144,13 +188,14 @@ export default class SmsService {
project.smsOrCallCurrentBalanceInUSDCents = Math.floor(
project.smsOrCallCurrentBalanceInUSDCents! -
SMSDefaultCostInCents
SMSDefaultCostInCents
);
await ProjectService.updateOneById({
data: {
smsOrCallCurrentBalanceInUSDCents:
project.smsOrCallCurrentBalanceInUSDCents,
notEnabledSmsNotificationSentToOwners: false, // reset this flag
},
id: project.id!,
props: {

View File

@@ -0,0 +1,11 @@
{{> Start this}}
{{> Logo this}}
{{> EmailTitle title=subject }}
{{> InfoBlock info=message}}
{{> Footer this}}
{{> End}}