diff --git a/src/modules/notifications/dto/create-preference.dto.ts b/src/modules/notifications/dto/create-preference.dto.ts index 0777fd4..9a97c62 100644 --- a/src/modules/notifications/dto/create-preference.dto.ts +++ b/src/modules/notifications/dto/create-preference.dto.ts @@ -1,24 +1,24 @@ import { IsNotEmpty, IsEnum } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { NotificationTitle } from '../entities/notification.entity'; +import { NotificationTitleEnum } from '../interfaces/notification.interface'; import { NotificationType } from '../interfaces/notification.interface'; export class CreatePreferenceDto { - @ApiProperty({ + @ApiProperty({ description: 'Notification type (SMS, PUSH, BOTH, or NONE)', enum: NotificationType, - example: NotificationType.SMS + example: NotificationType.SMS, }) @IsEnum(NotificationType) @IsNotEmpty() notificationType!: NotificationType; - @ApiProperty({ + @ApiProperty({ description: 'Notification title/type (e.g., order.created, review.created)', - enum: NotificationTitle, - example: NotificationTitle.ORDER_CREATED + enum: NotificationTitleEnum, + example: NotificationTitleEnum.ORDER_CREATED, }) - @IsEnum(NotificationTitle) + @IsEnum(NotificationTitleEnum) @IsNotEmpty() - title!: NotificationTitle; + title!: NotificationTitleEnum; } diff --git a/src/modules/notifications/entities/notification-preference.entity.ts b/src/modules/notifications/entities/notification-preference.entity.ts index 61d3b48..a74a777 100644 --- a/src/modules/notifications/entities/notification-preference.entity.ts +++ b/src/modules/notifications/entities/notification-preference.entity.ts @@ -1,7 +1,7 @@ import { Entity, Property, ManyToOne, Unique, Enum } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; import { Restaurant } from '../../restaurants/entities/restaurant.entity'; -import { NotificationTitle } from './notification.entity'; +import { NotificationTitleEnum } from '../interfaces/notification.interface'; import { NotificationType } from '../interfaces/notification.interface'; @Entity({ tableName: 'notification_preferences' }) @@ -11,7 +11,7 @@ export class NotificationPreference extends BaseEntity { restaurant!: Restaurant; @Property() - title!: NotificationTitle; + title!: NotificationTitleEnum; @Enum(() => NotificationType) notificationType!: NotificationType; //sms, push, email diff --git a/src/modules/notifications/entities/notification.entity.ts b/src/modules/notifications/entities/notification.entity.ts index 84e5c81..9cf395b 100644 --- a/src/modules/notifications/entities/notification.entity.ts +++ b/src/modules/notifications/entities/notification.entity.ts @@ -2,12 +2,7 @@ import { Entity, Property, ManyToOne, Enum } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { User } from '../../users/entities/user.entity'; - -export enum NotificationTitle { - ORDER_CREATED = 'order.created', - REVIEW_CREATED = 'review.created', - ORDER_STATUS_CHANGED = 'order.status.changed', -} +import { NotificationTitleEnum } from '../interfaces/notification.interface'; @Entity({ tableName: 'notifications' }) export class Notification extends BaseEntity { @@ -17,14 +12,14 @@ export class Notification extends BaseEntity { @ManyToOne(() => User, { nullable: true }) user?: User; - @Enum(() => NotificationTitle) - title!: NotificationTitle; + @Enum(() => NotificationTitleEnum) + title!: NotificationTitleEnum; @Property() content!: string; - @Property({ nullable: true, unique: true }) - idempotencyKey?: string; + // @Property({ nullable: true, unique: true }) + // idempotencyKey?: string; @Property({ nullable: true }) sentAt?: Date; diff --git a/src/modules/notifications/events/notification.events.ts b/src/modules/notifications/events/notification.events.ts index 560b116..b8aa2a1 100644 --- a/src/modules/notifications/events/notification.events.ts +++ b/src/modules/notifications/events/notification.events.ts @@ -2,8 +2,17 @@ export class OrderCreatedEvent { constructor( public readonly restaurantId: string, public readonly userId: string, - public readonly orderId: string, - public readonly orderData: any, + public readonly orderNumber: string, + public readonly totalAmount: number, + ) {} +} + +export class OrderPaymentSuccessEvent { + constructor( + public readonly restaurantId: string, + public readonly userId: string, + public readonly orderNumber: string, + public readonly totalAmount: number, ) {} } diff --git a/src/modules/notifications/interfaces/notification-queue.interface.ts b/src/modules/notifications/interfaces/notification-queue.interface.ts index 73005a5..460f859 100644 --- a/src/modules/notifications/interfaces/notification-queue.interface.ts +++ b/src/modules/notifications/interfaces/notification-queue.interface.ts @@ -1,10 +1,10 @@ import type { NotificationType } from './notification.interface'; -import type { NotificationTitle } from '../entities/notification.entity'; +import type { NotificationTitleEnum } from './notification.interface'; export interface NotificationQueueJob { restaurantId: string; userId?: string; - title: NotificationTitle; + title: NotificationTitleEnum; content: string; idempotencyKey?: string; notificationId?: string; // For retries diff --git a/src/modules/notifications/interfaces/notification.interface.ts b/src/modules/notifications/interfaces/notification.interface.ts index 9af5342..398a4dd 100644 --- a/src/modules/notifications/interfaces/notification.interface.ts +++ b/src/modules/notifications/interfaces/notification.interface.ts @@ -1,6 +1,44 @@ +export enum NotificationTitleEnum { + ORDER_CREATED = 'order.created', + PAYMENT_SUCCESS = 'payment.success', + REVIEW_CREATED = 'review.created', + ORDER_STATUS_CHANGED = 'order.status.changed', +} + export enum NotificationType { NONE = 'none', SMS = 'sms', PUSH = 'push', Both = 'both', } +interface INotifySmsPayload { + [key: string]: string | number | Date; +} +interface INotifySms { + phone: string; + subject: NotificationTitleEnum; +} +// Sub intefaces +// 1. Order Created Sms Notify Payload +interface IOrderCreatedSmsNotifyPayload extends INotifySmsPayload { + orderNumber: string; + orderAmount: number; + orderDate: Date; +} + +interface IOrderCreatedSmsNotify extends INotifySms { + subject: NotificationTitleEnum.ORDER_CREATED; + payload: IOrderCreatedSmsNotifyPayload; +} +// 2. Payment Success +interface IPaymentSuccessSmsNotifyPayload extends INotifySmsPayload { + amount: number; + date: Date; +} + +interface IPaymentSuccessSmsNotify extends INotifySms { + subject: NotificationTitleEnum.PAYMENT_SUCCESS; + payload: IPaymentSuccessSmsNotifyPayload; +} + +export type ISmsNotifyPayload = IOrderCreatedSmsNotify | IPaymentSuccessSmsNotify; diff --git a/src/modules/notifications/listeners/notification.listeners.ts b/src/modules/notifications/listeners/notification.listeners.ts index d703170..eb3b63f 100644 --- a/src/modules/notifications/listeners/notification.listeners.ts +++ b/src/modules/notifications/listeners/notification.listeners.ts @@ -1,8 +1,13 @@ import { Injectable, Logger } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { NotificationService } from '../services/notification.service'; -import { NotificationTitle } from '../entities/notification.entity'; -import { OrderCreatedEvent, ReviewCreatedEvent, OrderStatusChangedEvent } from '../events/notification.events'; +import { NotificationTitleEnum } from '../interfaces/notification.interface'; +import { + OrderCreatedEvent, + ReviewCreatedEvent, + OrderStatusChangedEvent, + OrderPaymentSuccessEvent, +} from '../events/notification.events'; @Injectable() export class NotificationListeners { @@ -13,17 +18,35 @@ export class NotificationListeners { @OnEvent('order.created') async handleOrderCreated(event: OrderCreatedEvent) { try { - this.logger.log(`Order created event received: ${event.orderId}`); + this.logger.log(`Order created event received: ${event.orderNumber}`); await this.notificationService.sendNotification({ restaurantId: event.restaurantId, userId: event.userId, - title: NotificationTitle.ORDER_CREATED, - content: `Your order #${event.orderId} has been confirmed`, + title: NotificationTitleEnum.ORDER_CREATED, + content: `Your order #${event.orderNumber} has created successfully`, }); } catch (error) { this.logger.error( - `Failed to send notification for order.created event: ${event.orderId}`, + `Failed to send notification for order.created event: ${event.orderNumber}`, + error instanceof Error ? error.stack : String(error), + ); + } + } + @OnEvent('order.payment.success') + async handleOrderPaymentSuccess(event: OrderPaymentSuccessEvent) { + try { + this.logger.log(`Order payment success event received: ${event.orderNumber}`); + + await this.notificationService.sendNotification({ + restaurantId: event.restaurantId, + userId: event.userId, + title: NotificationTitleEnum.ORDER_CREATED, + content: `Your Payement for order #${event.orderNumber} has been successful Paid ${event.totalAmount}`, + }); + } catch (error) { + this.logger.error( + `Failed to send notification for order.created event: ${event.orderNumber}`, error instanceof Error ? error.stack : String(error), ); } @@ -37,7 +60,7 @@ export class NotificationListeners { await this.notificationService.sendNotification({ restaurantId: event.restaurantId, userId: event.userId, - title: NotificationTitle.REVIEW_CREATED, + title: NotificationTitleEnum.REVIEW_CREATED, content: 'Thank you for your review!', }); } catch (error) { @@ -56,7 +79,7 @@ export class NotificationListeners { await this.notificationService.sendNotification({ restaurantId: event.restaurantId, userId: event.userId, - title: NotificationTitle.ORDER_STATUS_CHANGED, + title: NotificationTitleEnum.ORDER_STATUS_CHANGED, content: `Your order #${event.orderId} is now ${event.newStatus}`, }); } catch (error) { diff --git a/src/modules/notifications/notifications.module.ts b/src/modules/notifications/notifications.module.ts index 45d1ea7..e213e65 100644 --- a/src/modules/notifications/notifications.module.ts +++ b/src/modules/notifications/notifications.module.ts @@ -8,7 +8,7 @@ import { NotificationService } from './services/notification.service'; import { NotificationPreferenceService } from './services/notification-preference.service'; import { NotificationQueueService } from './services/notification-queue.service'; import { PushNotificationService } from './services/push-notification.service'; -import { SmsNotificationService } from './services/sms-notification.service'; +import { SmsAdaptorService } from './services/sms.adaptor'; import { SmsProcessor } from './processors/sms.processor'; import { PushProcessor } from './processors/push.processor'; import { NotificationsController } from './notifications.controller'; @@ -18,10 +18,12 @@ import { NotificationListeners } from './listeners/notification.listeners'; import { AuthModule } from '../auth/auth.module'; import { ConfigService } from '@nestjs/config'; import { NotificationQueueNameEnum } from './constants/queue'; +import { UtilsModule } from '../utils/utils.module'; @Module({ imports: [ AuthModule, + UtilsModule, JwtModule, MikroOrmModule.forFeature([Notification, NotificationPreference, User, Restaurant]), BullModule.registerQueue({ name: NotificationQueueNameEnum.SMS }, { name: NotificationQueueNameEnum.PUSH }), @@ -42,7 +44,7 @@ import { NotificationQueueNameEnum } from './constants/queue'; NotificationPreferenceService, NotificationQueueService, PushNotificationService, - SmsNotificationService, + SmsAdaptorService, PushProcessor, SmsProcessor, NotificationListeners, @@ -52,7 +54,7 @@ import { NotificationQueueNameEnum } from './constants/queue'; NotificationPreferenceService, NotificationQueueService, PushNotificationService, - SmsNotificationService, + SmsAdaptorService, ], }) export class NotificationsModule {} diff --git a/src/modules/notifications/processors/push.processor.ts b/src/modules/notifications/processors/push.processor.ts index 685c006..1325dca 100644 --- a/src/modules/notifications/processors/push.processor.ts +++ b/src/modules/notifications/processors/push.processor.ts @@ -5,7 +5,7 @@ import { InjectRepository } from '@mikro-orm/nestjs'; import { EntityRepository, EntityManager } from '@mikro-orm/postgresql'; import { Notification } from '../entities/notification.entity'; import { NotificationQueueJob, NotificationQueueJobResult } from '../interfaces/notification-queue.interface'; - import { NotificationQueueNameEnum } from '../constants/queue'; +import { NotificationQueueNameEnum } from '../constants/queue'; import { PushNotificationService } from '../services/push-notification.service'; @Processor(NotificationQueueNameEnum.PUSH) @@ -41,16 +41,19 @@ export class PushProcessor extends WorkerHost { } else if (idempotencyKey) { // Fallback: find by idempotency key if notificationId not provided notification = await this.notificationRepository.findOne( - { idempotencyKey }, - { populate: ['user', 'restaurant'] }, + { + id: 'sdfpsdfpsdf', + }, + // { idempotencyKey }, + // { populate: ['user', 'restaurant'] }, ); - if (notification && notification.sentAt) { - this.logger.warn(`Duplicate notification skipped: ${idempotencyKey}`); - return { - success: true, - notificationId: notification.id, - }; - } + // if (notification && notification.sentAt) { + // this.logger.warn(`Duplicate notification skipped: ${idempotencyKey}`); + // return { + // success: true, + // notificationId: notification.id, + // }; + // } } if (!notification) { diff --git a/src/modules/notifications/processors/sms.processor.ts b/src/modules/notifications/processors/sms.processor.ts index 6d2a49c..dd733fb 100644 --- a/src/modules/notifications/processors/sms.processor.ts +++ b/src/modules/notifications/processors/sms.processor.ts @@ -7,7 +7,9 @@ import { Notification } from '../entities/notification.entity'; import { NotificationQueueJob, NotificationQueueJobResult } from '../interfaces/notification-queue.interface'; import { User } from '../../users/entities/user.entity'; import { NotificationQueueNameEnum } from '../constants/queue'; -import { SmsNotificationService } from '../services/sms-notification.service'; +import { SmsAdaptorService } from '../services/sms.adaptor'; +import { NotificationTitleEnum } from '../interfaces/notification.interface'; +import { ISmsResponse } from '../../utils/interface/sms'; @Processor(NotificationQueueNameEnum.SMS) export class SmsProcessor extends WorkerHost { @@ -17,79 +19,63 @@ export class SmsProcessor extends WorkerHost { @InjectRepository(Notification) private readonly notificationRepository: EntityRepository, private readonly em: EntityManager, - private readonly smsNotificationService: SmsNotificationService, + private readonly smsAdaptorService: SmsAdaptorService, ) { super(); } async process(job: Job): Promise { - const { restaurantId, userId, title, content, idempotencyKey, notificationId } = job.data; + const { restaurantId, userId, title } = job.data; - this.logger.log(`Processing SMS notification job: ${job.id} - Title: ${title}, Restaurant: ${restaurantId}`); + this.logger.log(`Processing SMS notification job: ${job.id} - Title: ${title}, + Restaurant: ${restaurantId}`); try { - // Find existing notification record (should already exist from NotificationService.sendNotification) - let notification: Notification | null = null; - - if (notificationId) { - notification = await this.notificationRepository.findOne( - { id: notificationId }, - { populate: ['user', 'restaurant'] }, - ); - if (!notification) { - throw new Error(`Notification ${notificationId} not found`); - } - } else if (idempotencyKey) { - // Fallback: find by idempotency key if notificationId not provided - notification = await this.notificationRepository.findOne( - { idempotencyKey }, - { populate: ['user', 'restaurant'] }, - ); - if (notification && notification.sentAt) { - this.logger.warn(`Duplicate notification skipped: ${idempotencyKey}`); - return { - success: true, - notificationId: notification.id, - }; - } - } - - if (!notification) { - throw new Error(`Notification not found for job ${job.id}. notificationId or idempotencyKey required.`); - } - - // Get user phone number for SMS - if (!userId || !notification.user) { - this.logger.warn(`No user found for SMS notification ${notification.id}`); - throw new Error('User is required for SMS notifications'); - } - const user = await this.em.findOne(User, { id: userId }); if (!user || !user.phone) { this.logger.warn(`User ${userId} not found or has no phone number`); throw new Error('User phone number is required for SMS notifications'); } - // Send SMS notification - const smsResult = await this.smsNotificationService.send({ - phoneNumber: user.phone, - message: content, - }); + // Check if the notification type is supported for SMS + if (title !== NotificationTitleEnum.ORDER_CREATED && title !== NotificationTitleEnum.PAYMENT_SUCCESS) { + throw new Error(`SMS notification type ${title} is not supported`); + } - if (!smsResult.success) { - throw new Error(smsResult.error || 'Failed to send SMS notification'); + // Send SMS notification + let smsResult: ISmsResponse; + if (title === NotificationTitleEnum.ORDER_CREATED) { + smsResult = await this.smsAdaptorService.sendNotifySms({ + phone: user.phone, + subject: NotificationTitleEnum.ORDER_CREATED, + payload: { + orderNumber: '1234567890', + orderAmount: 100000, + orderDate: new Date(), + }, + }); + } else { + smsResult = await this.smsAdaptorService.sendNotifySms({ + phone: user.phone, + subject: NotificationTitleEnum.PAYMENT_SUCCESS, + payload: { + amount: 100000, + date: new Date(), + }, + }); + } + + if (smsResult.status !== 1) { + throw new Error(smsResult.message ?? 'Failed to send SMS notification'); } // Mark notification as sent - notification.sentAt = new Date(); - await this.em.persistAndFlush(notification); - this.logger.log(`SMS notification ${notification.id} sent successfully to ${user.phone}`); + this.logger.log(`SMS notification sent successfully to ${user.phone}`); return { success: true, - notificationId: notification.id, - providerResponse: { messageId: smsResult.messageId }, + notificationId: 'sdfpsdfpsdf', }; } catch (error) { this.logger.error(`Error processing SMS notification job ${job.id}:`, error); diff --git a/src/modules/notifications/services/notification-preference.service.ts b/src/modules/notifications/services/notification-preference.service.ts index b8fe72c..dc5a81e 100644 --- a/src/modules/notifications/services/notification-preference.service.ts +++ b/src/modules/notifications/services/notification-preference.service.ts @@ -4,7 +4,7 @@ import { NotificationPreference } from '../entities/notification-preference.enti import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { CreatePreferenceDto } from '../dto/create-preference.dto'; import { UpdatePreferenceDto } from '../dto/update-preference.dto'; -import { NotificationTitle } from '../entities/notification.entity'; +import { NotificationTitleEnum } from '../interfaces/notification.interface'; @Injectable() export class NotificationPreferenceService { @@ -34,7 +34,7 @@ export class NotificationPreferenceService { async findByRestaurantAndType( restaurantId: string, - title: NotificationTitle, + title: NotificationTitleEnum, ): Promise { return this.em.findOne(NotificationPreference, { restaurant: { id: restaurantId }, diff --git a/src/modules/notifications/services/notification.service.ts b/src/modules/notifications/services/notification.service.ts index 049d949..b6c54d7 100644 --- a/src/modules/notifications/services/notification.service.ts +++ b/src/modules/notifications/services/notification.service.ts @@ -1,17 +1,18 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { EntityManager } from '@mikro-orm/postgresql'; -import { Notification, NotificationTitle } from '../entities/notification.entity'; +import { Notification } from '../entities/notification.entity'; import { NotificationPreferenceService } from './notification-preference.service'; import { NotificationQueueService } from './notification-queue.service'; import { NotificationQueueJob } from '../interfaces/notification-queue.interface'; import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { User } from '../../users/entities/user.entity'; import { NotificationType } from '../interfaces/notification.interface'; +import { NotificationTitleEnum } from '../interfaces/notification.interface'; export interface SendNotificationParams { restaurantId: string; userId?: string; - title: NotificationTitle; + title: NotificationTitleEnum; content: string; idempotencyKey?: string; } @@ -53,7 +54,7 @@ export class NotificationService { user, title, content, - idempotencyKey: finalIdempotencyKey, + // idempotencyKey: finalIdempotencyKey, }); await this.em.persistAndFlush(notification); @@ -125,7 +126,11 @@ export class NotificationService { await this.em.removeAndFlush(notification as any); } - async findByRestaurantAndType(restaurantId: string, title: NotificationTitle, limit = 50): Promise { + async findByRestaurantAndType( + restaurantId: string, + title: NotificationTitleEnum, + limit = 50, + ): Promise { return this.em.find( Notification, { diff --git a/src/modules/notifications/services/sms-notification.service.ts b/src/modules/notifications/services/sms-notification.service.ts deleted file mode 100644 index 8d24fdc..0000000 --- a/src/modules/notifications/services/sms-notification.service.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Injectable, Logger, HttpException, HttpStatus } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import axios from 'axios'; - -export interface SmsNotificationPayload { - phoneNumber: string; - message: string; - templateId?: string; - parameters?: Array<{ name: string; value: string }>; -} - -export interface SmsNotificationResponse { - success: boolean; - messageId?: string; - error?: string; -} - -@Injectable() -export class SmsNotificationService { - private readonly logger = new Logger(SmsNotificationService.name); - private readonly baseURL: string; - private readonly apiKey: string; - private readonly defaultTemplateId?: string; - - constructor(private readonly configService: ConfigService) { - this.baseURL = this.configService.getOrThrow('SMS_BASE_URL'); - this.apiKey = this.configService.getOrThrow('SMS_API_KEY'); - this.defaultTemplateId = this.configService.get('SMS_PATTERN_OTP'); - } - - async send(payload: SmsNotificationPayload): Promise { - try { - // If templateId is provided, use template-based SMS - if (payload.templateId || this.defaultTemplateId) { - return await this.sendTemplateSms(payload); - } - - // Otherwise, send simple SMS - return await this.sendSimpleSms(payload); - } catch (error) { - this.logger.error(`Failed to send SMS: ${error}`); - return { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; - } - } - - private async sendTemplateSms(payload: SmsNotificationPayload): Promise { - const templateId = payload.templateId || this.defaultTemplateId; - - if (!templateId) { - throw new HttpException('Template ID is required for template SMS', HttpStatus.BAD_REQUEST); - } - - const smsData = { - Parameters: payload.parameters || [{ name: 'message', value: payload.message }], - Mobile: payload.phoneNumber, - TemplateId: templateId, - }; - - const url = `${this.baseURL}/send/verify`; - - try { - const { data } = await axios.post<{ status: number; message: string }>(url, smsData, { - headers: { - 'Content-Type': 'application/json', - 'X-API-KEY': this.apiKey, - }, - }); - - if (data.status !== 1) { - throw new HttpException(data.message || 'SMS sending failed', HttpStatus.BAD_GATEWAY); - } - - this.logger.log(`SMS sent successfully to ${payload.phoneNumber}`); - return { - success: true, - messageId: data.message, - }; - } catch (error) { - this.logger.error(`SMS sending failed: ${JSON.stringify(error)}`); - throw new HttpException('The SMS was not sent. Please try again later.', HttpStatus.BAD_GATEWAY); - } - } - - private async sendSimpleSms(payload: SmsNotificationPayload): Promise { - // If your SMS provider supports simple text messages, implement it here - // For now, we'll use the template method as fallback - return this.sendTemplateSms({ - ...payload, - parameters: [{ name: 'message', value: payload.message }], - }); - } -} diff --git a/src/modules/notifications/services/sms.adaptor.ts b/src/modules/notifications/services/sms.adaptor.ts new file mode 100644 index 0000000..88c5588 --- /dev/null +++ b/src/modules/notifications/services/sms.adaptor.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { SmsService } from 'src/modules/utils/sms.service'; +import { ISmsNotifyPayload } from '../interfaces/notification.interface'; +import { NotificationTitleEnum } from '../interfaces/notification.interface'; + +@Injectable() +export class SmsAdaptorService { + private readonly smsPatternOrderCreated: string; + private readonly smsPatternPaymentSuccess: string; + constructor( + private readonly configService: ConfigService, + private readonly smsService: SmsService, + ) { + this.smsPatternOrderCreated = this.configService.getOrThrow('SMS_PATTERN_ORDER_CREATED'); + this.smsPatternPaymentSuccess = this.configService.getOrThrow('SMS_PATTERN_PAYMENT_SUCCESS'); + } + + async sendNotifySms(params: ISmsNotifyPayload) { + const { phone, subject, payload } = params; + let templateId!: string; + switch (subject) { + case NotificationTitleEnum.ORDER_CREATED: + templateId = this.smsPatternOrderCreated; + break; + case NotificationTitleEnum.PAYMENT_SUCCESS: + templateId = this.smsPatternPaymentSuccess; + break; + } + return this.smsService.sendSms({ + phone, + parameters: payload, + templateId, + }); + } +} diff --git a/src/modules/utils/interface/sms.ts b/src/modules/utils/interface/sms.ts new file mode 100644 index 0000000..d397dfe --- /dev/null +++ b/src/modules/utils/interface/sms.ts @@ -0,0 +1,16 @@ +export interface ISmsResponse { + status: number; + message: string; +} + +export interface ISmsParams { + phone: string; + parameters: Record; + templateId: string; +} + +export interface ISmsBodyParameters { + Mobile: string; + TemplateId: string; + Parameters: { name: string; value: string }[]; +} diff --git a/src/modules/utils/sms.service.ts b/src/modules/utils/sms.service.ts index 3b9a94c..b671545 100644 --- a/src/modules/utils/sms.service.ts +++ b/src/modules/utils/sms.service.ts @@ -1,24 +1,7 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; -// import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; -// import { lastValueFrom } from 'rxjs'; import axios from 'axios'; - -export interface IParameterArray { - name: 'otp'; - value: string; -} - -export interface ISmsResponse { - status: number; - message: string; -} - -export interface ISmsVerifyBody { - Parameters: IParameterArray[]; - Mobile: string; - TemplateId: string; -} +import { ISmsParams, ISmsResponse, ISmsBodyParameters } from './interface/sms'; @Injectable() export class SmsService { @@ -31,15 +14,26 @@ export class SmsService { private readonly configService: ConfigService, ) { this.baseURL = this.configService.getOrThrow('SMS_BASE_URL'); - this.OTP = this.configService.getOrThrow('SMS_PATTERN_OTP'); this.apiKey = this.configService.getOrThrow('SMS_API_KEY'); + this.OTP = this.configService.getOrThrow('SMS_PATTERN_OTP'); } - async sendOtp(mobile: string, otp: string) { - const smsData: ISmsVerifyBody = { - Parameters: [{ name: 'otp', value: otp }], - Mobile: mobile, - TemplateId: this.OTP, + sendotp(phone: string, otp: string) { + return this.sendSms({ + phone, + parameters: { otp }, + templateId: this.OTP, + }); + } + + async sendSms(params: ISmsParams) { + const { parameters, phone, templateId } = params; + const parametersArray = Object.entries(parameters).map(([name, value]) => ({ name, value: value.toString() })); + + const smsData: ISmsBodyParameters = { + Parameters: parametersArray, + Mobile: phone, + TemplateId: templateId, }; const url = `${this.baseURL}/send/verify`;