chore: add notification service for all user action
This commit is contained in:
@@ -95,7 +95,7 @@ export class AuthService {
|
||||
|
||||
const tokens = this.generateAccessAndRefreshToken(user);
|
||||
|
||||
await this.notificationService.createLoginNotification(user.id);
|
||||
await this.notificationService.createLoginNotification(user.id, { userPhone: user.phone });
|
||||
|
||||
return {
|
||||
message: AuthMessage.PASSWORD_LOGIN_SUCCESS,
|
||||
@@ -176,7 +176,7 @@ export class AuthService {
|
||||
|
||||
const tokens = this.generateAccessAndRefreshToken(user);
|
||||
|
||||
await this.notificationService.createLoginNotification(user.id);
|
||||
await this.notificationService.createLoginNotification(user.id, { userPhone: user.phone });
|
||||
return {
|
||||
message: AuthMessage.LOGIN_SUCCESS,
|
||||
...tokens,
|
||||
|
||||
@@ -10,12 +10,19 @@ import { Discount } from "../discounts/entities/discount.entity";
|
||||
import { UsageDiscount } from "../discounts/entities/usage-discount.entity";
|
||||
import { DiscountRepository } from "../discounts/repositories/discount.repository";
|
||||
import { UsageDiscountRepository } from "../discounts/repositories/usage-discount.repository";
|
||||
import { NotificationModule } from "../notifications/notifications.module";
|
||||
import { UsersModule } from "../users/users.module";
|
||||
import { UtilsModule } from "../utils/utils.module";
|
||||
import { WalletsModule } from "../wallets/wallets.module";
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Invoice, InvoiceItem, Discount, UsageDiscount]), UsersModule, WalletsModule, UtilsModule],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Invoice, InvoiceItem, Discount, UsageDiscount]),
|
||||
UsersModule,
|
||||
WalletsModule,
|
||||
UtilsModule,
|
||||
NotificationModule,
|
||||
],
|
||||
providers: [InvoicesService, InvoicesRepository, DiscountRepository, UsageDiscountRepository],
|
||||
controllers: [InvoicesController],
|
||||
exports: [InvoicesService],
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AuthMessage, DiscountMessage, InvoiceMessage, WalletMessage } from "../
|
||||
import { VerifyOtpWithUserId } from "../../auth/DTO/verify-otp.dto";
|
||||
import { DiscountRepository } from "../../discounts/repositories/discount.repository";
|
||||
import { UsageDiscountRepository } from "../../discounts/repositories/usage-discount.repository";
|
||||
import { NotificationsService } from "../../notifications/providers/notifications.service";
|
||||
import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity";
|
||||
import { UsersService } from "../../users/providers/users.service";
|
||||
import { OTPService } from "../../utils/providers/otp.service";
|
||||
@@ -28,6 +29,7 @@ export class InvoicesService {
|
||||
private readonly logger = new Logger(InvoicesService.name);
|
||||
|
||||
constructor(
|
||||
private readonly notificationsService: NotificationsService,
|
||||
private readonly invoiceRepository: InvoicesRepository,
|
||||
private readonly discountRepository: DiscountRepository,
|
||||
private readonly usageDiscountRepository: UsageDiscountRepository,
|
||||
@@ -41,38 +43,63 @@ export class InvoicesService {
|
||||
///********************************** */
|
||||
|
||||
async createInvoiceAdmin(createDto: CreateInvoiceDto) {
|
||||
//
|
||||
const invoiceItems = createDto.items.map((item) => {
|
||||
return {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const invoiceItems = createDto.items.map((item) => ({
|
||||
name: item.name,
|
||||
count: item.count,
|
||||
unitPrice: item.unitPrice,
|
||||
discount: item.discount || 0,
|
||||
totalPrice: item.unitPrice * item.count - (item.unitPrice * item.count * item.discount) / 100,
|
||||
};
|
||||
});
|
||||
//
|
||||
const totalPrice = invoiceItems.reduce((sum, item) => new Decimal(item.totalPrice).add(sum), new Decimal(0));
|
||||
const tax = totalPrice.mul(0.1);
|
||||
//
|
||||
const dueDate = new Date();
|
||||
dueDate.setDate(dueDate.getDate() + 5);
|
||||
//
|
||||
const invoice = this.invoiceRepository.create({
|
||||
user: { id: createDto.userId },
|
||||
totalPrice: totalPrice.toNumber(),
|
||||
items: invoiceItems,
|
||||
tax: tax.toNumber(),
|
||||
dueDate,
|
||||
});
|
||||
//
|
||||
await this.invoiceRepository.save(invoice);
|
||||
//TODO: notify user
|
||||
}));
|
||||
|
||||
return {
|
||||
message: InvoiceMessage.CREATED,
|
||||
invoice,
|
||||
};
|
||||
const totalPrice = invoiceItems.reduce((sum, item) => new Decimal(item.totalPrice).add(sum), new Decimal(0));
|
||||
const tax = totalPrice.mul(0.1);
|
||||
|
||||
const dueDate = new Date();
|
||||
dueDate.setDate(dueDate.getDate() + 5);
|
||||
|
||||
const invoice = queryRunner.manager.create(Invoice, {
|
||||
user: { id: createDto.userId },
|
||||
totalPrice: totalPrice.toNumber(),
|
||||
items: invoiceItems,
|
||||
tax: tax.toNumber(),
|
||||
dueDate,
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(Invoice, invoice);
|
||||
|
||||
const user = await this.usersService.findOneByIdWithQueryRunner(createDto.userId, queryRunner);
|
||||
|
||||
await this.notificationsService.createInvoiceCreationNotification(
|
||||
createDto.userId,
|
||||
{
|
||||
invoiceId: invoice.numericId.toString(),
|
||||
dueDate: invoice.dueDate,
|
||||
price: new Decimal(invoice.totalPrice).toNumber(),
|
||||
userPhone: user.phone,
|
||||
userEmail: user.email,
|
||||
items: invoiceItems.map((item) => item.name).join(", "),
|
||||
},
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return {
|
||||
message: InvoiceMessage.CREATED,
|
||||
invoice,
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
//********************************** */
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsOptional } from "class-validator";
|
||||
import { IsIn, IsOptional } from "class-validator";
|
||||
|
||||
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||
|
||||
export class SearchNotificationQueryDto extends PaginationDto {
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsIn([0, 1])
|
||||
@ApiPropertyOptional({ description: "Notification status", example: 1 })
|
||||
isRead?: number;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export enum NotificationType {
|
||||
LOGIN = "LOGIN",
|
||||
PASSWORD_CAHNGED = "PASSWORD_CHANGED",
|
||||
PASSWORD_CHANGED = "PASSWORD_CHANGED",
|
||||
WALLET_CHARGED = "WALLER_CHARGED",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
export interface IBaseNotificationData {
|
||||
userPhone: string;
|
||||
userEmail?: string;
|
||||
}
|
||||
|
||||
export interface IInvoiceNotificationData extends IBaseNotificationData {
|
||||
price: number;
|
||||
items: string;
|
||||
dueDate: Date;
|
||||
invoiceId: string;
|
||||
}
|
||||
|
||||
export interface IServiceNotificationData extends IBaseNotificationData {
|
||||
serviceName: string;
|
||||
}
|
||||
|
||||
export interface ITicketNotificationData extends IBaseNotificationData {
|
||||
ticketId: string;
|
||||
subject: string;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export interface IWalletNotificationData extends IBaseNotificationData {
|
||||
amount: number;
|
||||
balance: number;
|
||||
date: Date;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface IGenericNotificationData extends IBaseNotificationData {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface IAnnouncementNotificationData extends IBaseNotificationData {
|
||||
title: string;
|
||||
description: string;
|
||||
date: Date;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { ParamDto } from "../../common/DTO/param.dto";
|
||||
|
||||
@Controller("notifications")
|
||||
@ApiTags("Notifications")
|
||||
@AuthGuards()
|
||||
export class NotificationController {
|
||||
constructor(private readonly notificationService: NotificationsService) {}
|
||||
|
||||
@@ -17,7 +18,6 @@ export class NotificationController {
|
||||
|
||||
@ApiOperation({ summary: "all notifications by user" })
|
||||
@Pagination()
|
||||
@AuthGuards()
|
||||
@Get()
|
||||
getAllNotifications(@UserDec("id") userId: string, @Query() queryDto: SearchNotificationQueryDto) {
|
||||
return this.notificationService.getAllNotifications(queryDto, userId);
|
||||
@@ -26,7 +26,6 @@ export class NotificationController {
|
||||
//********************* */
|
||||
|
||||
@ApiOperation({ summary: "mark user notification as read" })
|
||||
@AuthGuards()
|
||||
@Patch(":id/read")
|
||||
markAsRead(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
||||
return this.notificationService.markAsRead(paramDto.id, userId);
|
||||
@@ -35,7 +34,6 @@ export class NotificationController {
|
||||
//********************* */
|
||||
|
||||
@ApiOperation({ summary: "mark user all notification as read" })
|
||||
@AuthGuards()
|
||||
@Patch("read-all")
|
||||
markAllAsRead(@UserDec("id") userId: string) {
|
||||
return this.notificationService.markAllAsRead(userId);
|
||||
|
||||
@@ -5,11 +5,11 @@ import { Notification } from "./entities/notification.entity";
|
||||
import { NotificationController } from "./notifications.controller";
|
||||
import { NotificationsService } from "./providers/notifications.service";
|
||||
import { NotificationRepository } from "./repositories/notifications.repository";
|
||||
import { User } from "../users/entities/user.entity";
|
||||
import { UsersModule } from "../users/users.module";
|
||||
import { SettingModule } from "../settings/settings.module";
|
||||
import { UtilsModule } from "../utils/utils.module";
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Notification, User]), UsersModule],
|
||||
imports: [TypeOrmModule.forFeature([Notification]), UtilsModule, SettingModule],
|
||||
providers: [NotificationRepository, NotificationsService],
|
||||
controllers: [NotificationController],
|
||||
exports: [NotificationRepository, NotificationsService],
|
||||
|
||||
@@ -1,30 +1,73 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { InjectDataSource } from "@nestjs/typeorm";
|
||||
import { DataSource, QueryRunner } from "typeorm";
|
||||
|
||||
import { CommonMessage, NotificationMessage, UserMessage } from "../../../common/enums/message.enum";
|
||||
import { UsersService } from "../../users/providers/users.service";
|
||||
import { CommonMessage, NotificationMessage } from "../../../common/enums/message.enum";
|
||||
import { NotifType } from "../../settings/enums/notif-settings.enum";
|
||||
import { UserSettingsService } from "../../settings/providers/user-settings.service";
|
||||
import { dateFormat, numberFormat } from "../../utils/providers/international.utils";
|
||||
import { SmsService } from "../../utils/providers/sms.service";
|
||||
import { CreateNotificationDto } from "../DTO/create-notification.dto";
|
||||
import { SearchNotificationQueryDto } from "../DTO/search-notification-query.dto";
|
||||
import { Notification } from "../entities/notification.entity";
|
||||
import {
|
||||
IAnnouncementNotificationData,
|
||||
IBaseNotificationData,
|
||||
IInvoiceNotificationData,
|
||||
ITicketNotificationData,
|
||||
IWalletNotificationData,
|
||||
} from "../interfaces/ISendNotificationData";
|
||||
import { NotificationRepository } from "../repositories/notifications.repository";
|
||||
|
||||
@Injectable()
|
||||
export class NotificationsService {
|
||||
private readonly logger = new Logger(NotificationsService.name);
|
||||
constructor(
|
||||
private readonly notificationRepository: NotificationRepository,
|
||||
private readonly usersService: UsersService,
|
||||
private readonly smsService: SmsService,
|
||||
private readonly userSettingsService: UserSettingsService,
|
||||
@InjectDataSource() private readonly dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
//************************ */
|
||||
|
||||
async createNotification(createNotificationDto: CreateNotificationDto) {
|
||||
const { user } = await this.usersService.findOneById(createNotificationDto.recipientId);
|
||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||
const notification = this.notificationRepository.create({
|
||||
...createNotificationDto,
|
||||
recipient: user,
|
||||
});
|
||||
await this.notificationRepository.save(notification);
|
||||
async createNotification(createNotificationDto: CreateNotificationDto, queryRunner?: QueryRunner) {
|
||||
if (!queryRunner) {
|
||||
const localQueryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
return { notification };
|
||||
await localQueryRunner.connect();
|
||||
await localQueryRunner.startTransaction();
|
||||
try {
|
||||
//
|
||||
const notification = localQueryRunner.manager.create(Notification, {
|
||||
...createNotificationDto,
|
||||
recipient: { id: createNotificationDto.recipientId },
|
||||
});
|
||||
await localQueryRunner.manager.save(notification);
|
||||
|
||||
await localQueryRunner.commitTransaction();
|
||||
|
||||
return notification;
|
||||
//
|
||||
} catch (error) {
|
||||
this.logger.error("error in createNotification", error);
|
||||
await localQueryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
//
|
||||
} finally {
|
||||
await localQueryRunner.release();
|
||||
}
|
||||
//
|
||||
} else {
|
||||
//
|
||||
const notification = queryRunner.manager.create(Notification, {
|
||||
...createNotificationDto,
|
||||
recipient: { id: createNotificationDto.recipientId },
|
||||
});
|
||||
//
|
||||
await queryRunner.manager.save(notification);
|
||||
return notification;
|
||||
}
|
||||
}
|
||||
|
||||
//************************ */
|
||||
@@ -42,20 +85,13 @@ export class NotificationsService {
|
||||
|
||||
//************************ */
|
||||
|
||||
async createLoginNotification(recipientId: string) {
|
||||
const message = NotificationMessage.LOGIN_MESSAGE;
|
||||
return this.createNotification({ title: NotificationMessage.LOGIN, message, recipientId });
|
||||
}
|
||||
|
||||
//************************ */
|
||||
|
||||
async getAllNotifications(queryDto: SearchNotificationQueryDto, recipientId: string) {
|
||||
return await this.notificationRepository.getAllNotifications(queryDto, recipientId);
|
||||
}
|
||||
|
||||
//************************ */
|
||||
|
||||
async getOneNotification(id: string) {
|
||||
async getNotificationById(id: string) {
|
||||
const notification = await this.notificationRepository.findOneBy({ id });
|
||||
if (!notification) throw new BadRequestException(NotificationMessage.NOTIFICATION_NOT_FOUND_OR_ACCESS_DENIED);
|
||||
return { notification };
|
||||
@@ -64,18 +100,199 @@ export class NotificationsService {
|
||||
//************************ */
|
||||
|
||||
async markAllAsRead(userId: string) {
|
||||
await this.notificationRepository.update(
|
||||
{
|
||||
recipient: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
{
|
||||
isRead: true,
|
||||
},
|
||||
);
|
||||
await this.notificationRepository.update({ recipient: { id: userId } }, { isRead: true });
|
||||
return {
|
||||
message: CommonMessage.UPDATE_SUCCESS,
|
||||
};
|
||||
}
|
||||
|
||||
//************************ */
|
||||
|
||||
async createLoginNotification(recipientId: string, sendNotifData: IBaseNotificationData) {
|
||||
const loginDate = new Date().toLocaleString("fa-IR");
|
||||
const message = NotificationMessage.LOGIN_MESSAGE.replace("[loginDate]", loginDate);
|
||||
//sent notification based on the userSettings
|
||||
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.USER_LOGIN, recipientId);
|
||||
if (isActive) {
|
||||
await this.smsService.sendLoginSms(sendNotifData.userPhone, loginDate);
|
||||
//send email too
|
||||
}
|
||||
return this.createNotification({ title: NotificationMessage.LOGIN, message, recipientId });
|
||||
}
|
||||
|
||||
//************************ */
|
||||
async createInvoiceCreationNotification(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner) {
|
||||
const message = NotificationMessage.INVOICE_CREATION_MESSAGE.replace("[id]", data.invoiceId);
|
||||
//sent notification based on the userSettings
|
||||
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||
NotifType.CREATE_INVOICE,
|
||||
recipientId,
|
||||
queryRunner,
|
||||
);
|
||||
if (isActive) {
|
||||
await this.smsService.sendInvoiceCreationSms(data.userPhone, data.invoiceId, data.price, data.items, dateFormat(data.dueDate));
|
||||
//send email too
|
||||
}
|
||||
return this.createNotification({ title: NotificationMessage.INVOICE_CREATION, message, recipientId }, queryRunner);
|
||||
}
|
||||
//************************ */
|
||||
|
||||
async createAnnouncementNotification(recipientId: string, data: IAnnouncementNotificationData, queryRunner: QueryRunner) {
|
||||
const message = NotificationMessage.ANNOUNCEMENT_MESSAGE;
|
||||
//sent notification based on the userSettings
|
||||
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||
NotifType.ANNOUNCEMENT,
|
||||
recipientId,
|
||||
queryRunner,
|
||||
);
|
||||
if (isActive) {
|
||||
await this.smsService.sendAnnouncementSms(data.userPhone, data.title, data.description, dateFormat(data.date));
|
||||
//send email too
|
||||
}
|
||||
return this.createNotification({ title: NotificationMessage.ANNOUNCEMENT, message, recipientId });
|
||||
}
|
||||
|
||||
//************************ */
|
||||
|
||||
async createWalletChargeNotification(recipientId: string, data: IWalletNotificationData, queryRunner: QueryRunner) {
|
||||
const message = NotificationMessage.WALLET_CHARGE_MESSAGE.replace("[amount]", numberFormat(data.amount));
|
||||
//sent notification based on the userSettings
|
||||
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||
NotifType.WALLET_CHARGE,
|
||||
recipientId,
|
||||
queryRunner,
|
||||
);
|
||||
if (isActive) {
|
||||
await this.smsService.sendWalletChargeSms(
|
||||
data.userPhone,
|
||||
numberFormat(data.amount),
|
||||
numberFormat(data.balance),
|
||||
dateFormat(data.date),
|
||||
);
|
||||
//send email too
|
||||
}
|
||||
return this.createNotification({ title: NotificationMessage.WALLET_CHARGE, message, recipientId });
|
||||
}
|
||||
|
||||
//************************ */
|
||||
|
||||
async createWalletDeductionNotification(recipientId: string, data: IWalletNotificationData, queryRunner: QueryRunner) {
|
||||
const message = NotificationMessage.WALLET_DEDUCTION_MESSAGE.replace("[amount]", numberFormat(data.amount));
|
||||
//sent notification based on the userSettings
|
||||
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||
NotifType.WALLET_DEDUCTION,
|
||||
recipientId,
|
||||
queryRunner,
|
||||
);
|
||||
if (isActive) {
|
||||
await this.smsService.sendWalletDeductionSms(
|
||||
data.userPhone,
|
||||
numberFormat(data.amount),
|
||||
numberFormat(data.balance),
|
||||
data.reason,
|
||||
dateFormat(data.date),
|
||||
);
|
||||
//send email too
|
||||
}
|
||||
return this.createNotification({ title: NotificationMessage.WALLET_DEDUCTION, message, recipientId });
|
||||
}
|
||||
|
||||
// //************************ */
|
||||
|
||||
async createAnswerTicketNotification(recipientId: string, data: ITicketNotificationData, queryRunner: QueryRunner) {
|
||||
const message = NotificationMessage.ANSWER_TICKET_MESSAGE.replace("[ticketId]", data.ticketId);
|
||||
//sent notification based on the userSettings
|
||||
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||
NotifType.ANSWER_TICKET,
|
||||
recipientId,
|
||||
queryRunner,
|
||||
);
|
||||
if (isActive) {
|
||||
await this.smsService.sendAnswerTicketSms(data.userPhone, data.ticketId, data.subject);
|
||||
//send email too
|
||||
}
|
||||
return this.createNotification({ title: NotificationMessage.ANSWER_TICKET, message, recipientId });
|
||||
}
|
||||
|
||||
// //************************ */
|
||||
|
||||
async createTicketNotification(recipientId: string, data: ITicketNotificationData, queryRunner: QueryRunner) {
|
||||
const message = NotificationMessage.CREATE_TICKET_MESSAGE.replace("[ticketId]", data.ticketId);
|
||||
//sent notification based on the userSettings
|
||||
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||
NotifType.CREATE_TICKET,
|
||||
recipientId,
|
||||
queryRunner,
|
||||
);
|
||||
if (isActive) {
|
||||
await this.smsService.sendCreateTicketSms(data.userPhone, data.ticketId, data.subject, dateFormat(data.date));
|
||||
//send email too
|
||||
}
|
||||
return this.createNotification({ title: NotificationMessage.CREATE_TICKET, message, recipientId });
|
||||
}
|
||||
|
||||
//************************ */
|
||||
|
||||
// async createBillInvoiceReminderNotification(recipientId: string, sendNotifData: ISendNotificationData) {
|
||||
// const message = NotificationMessage.BILL_INVOICE_REMINDER_MESSAGE;
|
||||
// //sent notification based on the userSettings
|
||||
// const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BILL_INVOICE_REMINDER, recipientId);
|
||||
// if (isActive) {
|
||||
// await this.smsService.sendBillInvoiceReminderSms(sendNotifData.userPhone, sendNotifData.dueDate, sendNotifData.invoiceId);
|
||||
// //send email too
|
||||
// }
|
||||
// return this.createNotification({ title: NotificationMessage.BILL_INVOICE_REMINDER, message, recipientId });
|
||||
// }
|
||||
|
||||
//************************ */
|
||||
|
||||
// async createBillInvoiceNotification(recipientId: string, sendNotifData: ISendNotificationData) {
|
||||
// const message = NotificationMessage.BILL_INVOICE_MESSAGE;
|
||||
// //sent notification based on the userSettings
|
||||
// const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BILL_INVOICE, recipientId);
|
||||
// if (isActive) {
|
||||
// await this.smsService.sendBillInvoiceSms(sendNotifData.userPhone, sendNotifData.amount, sendNotifData.invoiceId);
|
||||
// //send email too
|
||||
// }
|
||||
// return this.createNotification({ title: NotificationMessage.BILL_INVOICE, message, recipientId });
|
||||
// }
|
||||
|
||||
// //************************ */
|
||||
|
||||
// async createServiceNotification(recipientId: string, sendNotifData: ISendNotificationData) {
|
||||
// const message = NotificationMessage.CREATE_SERVICE_MESSAGE.replace("[serviceName]", sendNotifData.serviceName);
|
||||
// //sent notification based on the userSettings
|
||||
// const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.CREATE_SERVICE, recipientId);
|
||||
// if (isActive) {
|
||||
// await this.smsService.sendCreateServiceSms(sendNotifData.userPhone, sendNotifData.serviceName);
|
||||
// //send email too
|
||||
// }
|
||||
// return this.createNotification({ title: NotificationMessage.CREATE_SERVICE, message, recipientId });
|
||||
// }
|
||||
|
||||
// //************************ */
|
||||
|
||||
// async createUnblockServiceNotification(recipientId: string, sendNotifData: ISendNotificationData) {
|
||||
// const message = NotificationMessage.UNBLOCK_SERVICE_MESSAGE.replace("[serviceName]", sendNotifData.serviceName);
|
||||
// //sent notification based on the userSettings
|
||||
// const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.UNBLOCK_SERVICE, recipientId);
|
||||
// if (isActive) {
|
||||
// await this.smsService.sendUnblockServiceSms(sendNotifData.userPhone, sendNotifData.serviceName);
|
||||
// //send email too
|
||||
// }
|
||||
// return this.createNotification({ title: NotificationMessage.UNBLOCK_SERVICE, message, recipientId });
|
||||
// }
|
||||
|
||||
// //************************ */
|
||||
|
||||
// async createBlockServiceNotification(recipientId: string, sendNotifData: ISendNotificationData) {
|
||||
// const message = NotificationMessage.BLOCK_SERVICE_MESSAGE.replace("[serviceName]", sendNotifData.serviceName);
|
||||
// //sent notification based on the userSettings
|
||||
// const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BLOCK_SERVICE, recipientId);
|
||||
// if (isActive) {
|
||||
// await this.smsService.sendBlockServiceSms(sendNotifData.userPhone, sendNotifData.serviceName);
|
||||
// //send email too
|
||||
// }
|
||||
// return this.createNotification({ title: NotificationMessage.BLOCK_SERVICE, message, recipientId });
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ import { DepositRequest } from "./entities/deposit-request.entity";
|
||||
import { PaymentGateway } from "./entities/payment-gateway.entity";
|
||||
import { DepositRequestsRepository } from "./repositories/deposit-requests.repository";
|
||||
import { PaymentGatewaysRepository } from "./repositories/payment-gateway.repository";
|
||||
import { NotificationModule } from "../notifications/notifications.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Payment, PaymentGateway, BankAccount, DepositRequest]),
|
||||
BullModule.registerQueue({ name: PAYMENT.PAYMENT_QUEUE_NAME }),
|
||||
WalletsModule,
|
||||
NotificationModule,
|
||||
],
|
||||
controllers: [PaymentsController],
|
||||
providers: [
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DataSource, Not, QueryRunner } from "typeorm";
|
||||
|
||||
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||
import { CommonMessage, PaymentMessage, UserMessage, WalletMessage } from "../../../common/enums/message.enum";
|
||||
import { NotificationsService } from "../../notifications/providers/notifications.service";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||
import { WalletsService } from "../../wallets/providers/wallets.service";
|
||||
@@ -37,6 +38,7 @@ import { GatewayType } from "../types/gateway.type";
|
||||
export class PaymentsService {
|
||||
constructor(
|
||||
@InjectQueue(PAYMENT.PAYMENT_QUEUE_NAME) private readonly paymentQueue: Queue,
|
||||
private readonly notificationsService: NotificationsService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly gatewayFactory: PaymentGatewayFactory,
|
||||
private readonly paymentGatewaysRepository: PaymentGatewaysRepository,
|
||||
@@ -159,12 +161,31 @@ export class PaymentsService {
|
||||
const verifyData = await paymentGateway.verifyPayment({ reference: queryDto.Authority, amount: payment.amount });
|
||||
|
||||
if (verifyData.code === 100) {
|
||||
await this.handleSuccessfulPayment(payment.id, payment.user.id, payment.amount, `${verifyData.ref_id}`, queryRunner);
|
||||
const { wallet } = await this.handleSuccessfulPayment(
|
||||
payment.id,
|
||||
payment.user.id,
|
||||
payment.amount,
|
||||
`${verifyData.ref_id}`,
|
||||
queryRunner,
|
||||
);
|
||||
await queryRunner.commitTransaction();
|
||||
frontUrl.searchParams.append("status", PaymentStatus.COMPLETED);
|
||||
frontUrl.searchParams.append("id", payment.id);
|
||||
frontUrl.searchParams.append("date", payment.createdAt.toISOString());
|
||||
frontUrl.searchParams.append("amount", payment.amount.toString());
|
||||
//
|
||||
await this.notificationsService.createWalletChargeNotification(
|
||||
payment.user.id,
|
||||
{
|
||||
amount: new Decimal(payment.amount).toNumber(),
|
||||
balance: new Decimal(wallet.balance).toNumber(),
|
||||
date: payment.createdAt,
|
||||
reason: WalletMessage.DEPOSIT_WALLET_IPG,
|
||||
userPhone: payment.user.phone,
|
||||
userEmail: payment.user.email,
|
||||
},
|
||||
queryRunner,
|
||||
);
|
||||
} else if (verifyData.code === 101) {
|
||||
await queryRunner.commitTransaction();
|
||||
frontUrl.searchParams.append("status", PaymentStatus.PENDING);
|
||||
@@ -229,8 +250,7 @@ export class PaymentsService {
|
||||
//*********************************** */
|
||||
async handleSuccessfulPayment(paymentId: string, userId: string, amount: Decimal, transactionId: string, queryRunner: QueryRunner) {
|
||||
await queryRunner.manager.update(Payment, { id: paymentId }, { status: PaymentStatus.COMPLETED, transactionId });
|
||||
const transaction = await this.walletsService.createPaymentTransaction(userId, amount, queryRunner);
|
||||
return transaction;
|
||||
return this.walletsService.createPaymentTransaction(userId, amount, queryRunner);
|
||||
}
|
||||
|
||||
//*********************************** */
|
||||
|
||||
@@ -60,6 +60,23 @@ export class UserSettingsService {
|
||||
}
|
||||
//*+*******************************************
|
||||
|
||||
async getUserNotificationSettingWithType(type: NotifType, userId: string, queryRunner?: QueryRunner) {
|
||||
const repository = queryRunner ? queryRunner.manager.getRepository(UserSetting) : this.userSettingsRepository;
|
||||
|
||||
const setting = await repository.findOne({
|
||||
where: { user: { id: userId }, notificationSetting: { type } },
|
||||
relations: {
|
||||
notificationSetting: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!setting) throw new BadRequestException(SettingMessageEnum.NOT_FOUND);
|
||||
|
||||
return setting;
|
||||
}
|
||||
|
||||
//*+*******************************************
|
||||
|
||||
async toggleUserNotificationSetting(settingId: string) {
|
||||
const setting = await this.userSettingsRepository.findOneBy({ id: settingId });
|
||||
|
||||
|
||||
@@ -23,4 +23,21 @@ export interface ISmsVerifyBody {
|
||||
TemplateId: string;
|
||||
}
|
||||
|
||||
export type TemplateParams = "VERIFICATIONCODE" | "code" | "invoiceId" | "price" | "items";
|
||||
export type TemplateParams =
|
||||
| "VERIFICATIONCODE"
|
||||
| "code"
|
||||
| "invoiceId"
|
||||
| "price"
|
||||
| "items"
|
||||
| "loginDate"
|
||||
| "invoiceDate"
|
||||
| "title"
|
||||
| "description"
|
||||
| "ticketId"
|
||||
| "serviceName"
|
||||
| "date"
|
||||
| "amount"
|
||||
| "newBalance"
|
||||
| "reason"
|
||||
| "subject"
|
||||
| "message";
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function numberFormat(number: number, locale: string = "fa-IR") {
|
||||
return Intl.NumberFormat(locale).format(number);
|
||||
}
|
||||
|
||||
export function dateFormat(date: Date, locale: string = "fa-IR") {
|
||||
return new Intl.DateTimeFormat(locale).format(date);
|
||||
}
|
||||
@@ -84,6 +84,254 @@ export class SmsService {
|
||||
throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendLoginSms(mobile: string, loginDate: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [{ name: "loginDate", value: loginDate }],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_LOGIN,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending verify sms", err);
|
||||
throw new InternalServerErrorException("error in sending verify sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
// throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendInvoiceCreationSms(mobile: string, invoiceId: string, price: number, items: string, invoiceDate: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "invoiceId", value: invoiceId },
|
||||
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
|
||||
{ name: "items", value: items },
|
||||
{ name: "invoiceDate", value: invoiceDate },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_CREATED,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending invoice created sms", err);
|
||||
throw new InternalServerErrorException("error in sending invoice created sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
async sendAnnouncementSms(mobile: string, title: string, description: string, date: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "title", value: title },
|
||||
{ name: "description", value: description },
|
||||
{ name: "date", value: date },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_ANNOUNCEMENT,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending announcement sms", err);
|
||||
throw new InternalServerErrorException("error in sending announcement sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendWalletChargeSms(mobile: string, amount: string, newBalance: string, date: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "amount", value: amount },
|
||||
{ name: "newBalance", value: newBalance },
|
||||
{ name: "date", value: date },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_WALLET_CHARGE,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending wallet charge sms", err);
|
||||
throw new InternalServerErrorException("error in sending wallet charge sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendWalletDeductionSms(mobile: string, amount: string, newBalance: string, reason: string, date: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "amount", value: amount },
|
||||
{ name: "newBalance", value: newBalance },
|
||||
{ name: "reason", value: reason },
|
||||
{ name: "date", value: date },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_WALLET_DEDUCTION,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending wallet deduction sms", err);
|
||||
throw new InternalServerErrorException("error in sending wallet deduction sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
|
||||
async sendCreateTicketSms(mobile: string, ticketId: string, subject: string, date: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "ticketId", value: ticketId },
|
||||
{ name: "subject", value: subject },
|
||||
{ name: "date", value: date },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_TICKET_CREATED,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending ticket created sms", err);
|
||||
throw new InternalServerErrorException("error in sending ticket created sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendAnswerTicketSms(mobile: string, ticketId: string, subject: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "ticketId", value: ticketId },
|
||||
{ name: "subject", value: subject },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_TICKET_ANSWERED,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending ticket answered sms", err);
|
||||
throw new InternalServerErrorException("error in sending ticket answered sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
|
||||
// sendBillInvoiceReminderSms(userPhone: string, dueDate: any, invoiceId: any) {
|
||||
// throw new Error("Method not implemented.");
|
||||
// }
|
||||
// //************************************************* */
|
||||
|
||||
// sendBillInvoiceSms(userPhone: string, amount: any, invoiceId: any) {
|
||||
// throw new Error("Method not implemented.");
|
||||
// }
|
||||
// //************************************************* */
|
||||
|
||||
// sendCreateServiceSms(userPhone: string, serviceName: any) {
|
||||
// throw new Error("Method not implemented.");
|
||||
// }
|
||||
// //************************************************* */
|
||||
|
||||
// sendUnblockServiceSms(userPhone: string, serviceName: any) {
|
||||
// throw new Error("Method not implemented.");
|
||||
// }
|
||||
// //************************************************* */
|
||||
|
||||
// sendBlockServiceSms(userPhone: string, serviceName: any) {
|
||||
// throw new Error("Method not implemented.");
|
||||
// }
|
||||
// //************************************************* */
|
||||
|
||||
//************************************************* */
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ export class WalletsService {
|
||||
await queryRunner.manager.save(Wallet, wallet);
|
||||
await queryRunner.manager.save(WalletTransaction, transaction);
|
||||
|
||||
return transaction;
|
||||
return { transaction, wallet };
|
||||
}
|
||||
//*********************************** */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user