diff --git a/src/modules/booking-online/authentication/data/services/verification.service.ts b/src/modules/booking-online/authentication/data/services/verification.service.ts index 85ce140..acbc186 100644 --- a/src/modules/booking-online/authentication/data/services/verification.service.ts +++ b/src/modules/booking-online/authentication/data/services/verification.service.ts @@ -4,6 +4,7 @@ import { VerificationModel } from '../models/verification.model'; import { BookingVerification } from '../../domain/entities/booking-verification.entity'; import { UnprocessableEntityException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; +import { WhatsappService } from 'src/services/whatsapp/whatsapp.service'; export class VerificationService { constructor( @InjectRepository(VerificationModel) @@ -25,21 +26,25 @@ export class VerificationService { async register(data: BookingVerification) { const currentTime = Math.floor(Date.now()); // current time in seconds - if ( - data.created_at && - currentTime - data.created_at > this.expiredTimeRegister - ) { - throw new UnprocessableEntityException('Please try again in 1 minute'); - } + // Generate a 4 digit OTP code - data.code = Math.floor(1000 + Math.random() * 9000).toString(); - data.tried = 0; - data.updated_at = currentTime; + const otpCode = Math.floor(1000 + Math.random() * 9000).toString(); let verification = await this.verificationRepository.findOne({ where: { phone_number: data.phone_number }, }); + if ( + verification.updated_at && + currentTime - verification.updated_at < this.expiredTimeRegister + ) { + throw new UnprocessableEntityException('Please try again in 1 minute'); + } + + data.code = otpCode; + data.tried = 0; + data.updated_at = currentTime; + if (verification) { // Update existing record verification = this.verificationRepository.merge(verification, data); @@ -47,7 +52,15 @@ export class VerificationService { // Create new record verification = this.verificationRepository.create(data); } - return this.verificationRepository.save(verification); + const payload = await this.verificationRepository.save(verification); + + const notificationService = new WhatsappService(); + notificationService.sendOtpNotification({ + phone: data.phone_number, + code: otpCode, + }); + + return payload; } async findByPhoneNumber(phoneNumber: string) { diff --git a/src/modules/booking-online/order/infrastructure/order.controller.ts b/src/modules/booking-online/order/infrastructure/order.controller.ts index 9498070..9a31e01 100644 --- a/src/modules/booking-online/order/infrastructure/order.controller.ts +++ b/src/modules/booking-online/order/infrastructure/order.controller.ts @@ -50,6 +50,7 @@ export class BookingOrderController { @Get(':id') async get(@Param('id') transactionId: string) { const data = await this.serviceData.getOneByOptions({ + relations: ['items'], where: { id: transactionId }, }); @@ -60,15 +61,49 @@ export class BookingOrderController { invoice_code, status, id, + items, } = data; + const usageItems = items.map((item) => { + const { + id, + item_id, + item_name, + item_price, + item_category_name, + total_price, + total_net_price, + qty, + qty_remaining, + } = item; + return { + id, + item_id, + item_name, + item_price, + item_category_name, + total_price, + total_net_price, + qty, + qty_remaining, + }; + }); + + // Mask customer_phone with * and keep last 4 numbers + let maskedCustomerPhone = customer_phone; + if (typeof customer_phone === 'string' && customer_phone.length > 4) { + const last4 = customer_phone.slice(-4); + maskedCustomerPhone = '*'.repeat(customer_phone.length - 4) + last4; + } + return { customer_name, - customer_phone, + customer_phone: maskedCustomerPhone, booking_date, invoice_code, status, id, + items: usageItems, }; } } diff --git a/src/services/whatsapp/whatsapp.constant.ts b/src/services/whatsapp/whatsapp.constant.ts index 0f568ca..cc04926 100644 --- a/src/services/whatsapp/whatsapp.constant.ts +++ b/src/services/whatsapp/whatsapp.constant.ts @@ -2,7 +2,7 @@ export const WHATSAPP_BUSINESS_API_URL = process.env.WHATSAPP_BUSINESS_API_URL ?? 'https://graph.facebook.com/'; export const WHATSAPP_BUSINESS_VERSION = - process.env.WHATSAPP_BUSINESS_VERSION ?? 'v21.0'; + process.env.WHATSAPP_BUSINESS_VERSION ?? 'v22.0'; export const WHATSAPP_BUSINESS_QUEUE_URL = process.env.WHATSAPP_BUSINESS_QUEUE_URL ?? 'auth/login'; diff --git a/src/services/whatsapp/whatsapp.service.ts b/src/services/whatsapp/whatsapp.service.ts index efd855f..8d22282 100644 --- a/src/services/whatsapp/whatsapp.service.ts +++ b/src/services/whatsapp/whatsapp.service.ts @@ -30,6 +30,19 @@ export class WhatsappService { const response = await axios(config); return response.data; } catch (error) { + if (axios.isAxiosError(error)) { + if (error.response) { + console.error('Axios error response:', { + status: error.response.status, + data: error.response.data, + headers: error.response.headers, + }); + } else if (error.request) { + console.error('Axios error request:', error.request); + } else { + console.error('Axios error message:', error.message); + } + } Logger.error(error); apm?.captureError(error); return null; @@ -105,6 +118,50 @@ export class WhatsappService { ); } + async sendOtpNotification(data: { phone: string; code: string }) { + // Compose the WhatsApp message payload for OTP using Facebook WhatsApp API + const payload = { + messaging_product: 'whatsapp', + to: data.phone, // recipient's phone number in international format + type: 'template', + template: { + name: 'booking_otp', // Make sure this template is approved in WhatsApp Business Manager + language: { + code: 'id', // or 'en' if you want English + }, + components: [ + { + type: 'body', + parameters: [ + { + type: 'text', + text: parseInt(data.code), // OTP code + }, + ], + }, + { + type: 'button', + sub_type: 'url', + index: '0', + parameters: [ + { + type: 'text', + text: `${data.code}`, + }, + ], + }, + ], + }, + }; + + const response = await this.sendMessage(payload); + if (response) { + Logger.log( + `OTP notification for code ${data.code} sent to ${data.phone}`, + ); + } + } + async queueProcess(data: WhatsappQueue) { const queueUrl = `${WHATSAPP_BUSINESS_QUEUE_URL}?id=${data.id}`; const payload = {