From b9c0e89906b2133c74e00d1b4935fb737a96ff27 Mon Sep 17 00:00:00 2001 From: AmruthVamshi Date: Mon, 9 Dec 2024 13:55:54 +0530 Subject: [PATCH] Enabled gupshup whatsapp provided for international numbers --- src/api/api.controller.ts | 19 ++- src/api/api.module.ts | 2 + src/api/api.service.ts | 17 ++- .../gupshupWhatsapp.service.ts | 144 ++++++++++++++++++ 4 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 src/api/sms/gupshupWhatsapp/gupshupWhatsapp.service.ts diff --git a/src/api/api.controller.ts b/src/api/api.controller.ts index e56049a..95b5354 100644 --- a/src/api/api.controller.ts +++ b/src/api/api.controller.ts @@ -39,6 +39,7 @@ import { ConfigService } from '@nestjs/config'; import { v4 as uuidv4 } from 'uuid'; import { VerifyJWTDto } from './dto/verify-jwt.dto'; import { Request } from 'express'; +import { GupshupWhatsappService } from './sms/gupshupWhatsapp/gupshupWhatsapp.service'; // eslint-disable-next-line @typescript-eslint/no-var-requires const CryptoJS = require('crypto-js'); @@ -53,6 +54,7 @@ export class ApiController { private readonly otpService: OtpService, private readonly apiService: ApiService, private readonly configResolverService: ConfigResolverService, + private readonly gupshupWhatsappService: GupshupWhatsappService ) {} @Get() @@ -91,8 +93,21 @@ export class ApiController { ); } } - const status: SMSResponse = await this.otpService.sendOTP(params.phone); - return { status }; + // Check if phone number contains country code (e.g. 91-1234567890) + if (params.phone.includes('-')) { + const [countryCode, number] = params.phone.split('-'); + params.phone = number; + const status: any = await this.gupshupWhatsappService.sendWhatsappOTP({ + phone: number, + template: null, + type: null, + params: null + }); + return { status }; + } else { + const status: any = await this.otpService.sendOTP(params.phone); + return { status }; + } } @Get('verifyOTP') diff --git a/src/api/api.module.ts b/src/api/api.module.ts index 445cb51..9b92e28 100644 --- a/src/api/api.module.ts +++ b/src/api/api.module.ts @@ -12,6 +12,7 @@ import { SmsService } from './sms/sms.service'; import got from 'got/dist/source'; import { CdacService } from './sms/cdac/cdac.service'; import { RajaiOtpService } from '../user/sms/rajaiOtpService/rajaiOtpService.service'; +import { GupshupWhatsappService } from './sms/gupshupWhatsapp/gupshupWhatsapp.service'; const otpServiceFactory = { provide: OtpService, @@ -68,6 +69,7 @@ const otpServiceFactory = { otpServiceFactory, QueryGeneratorService, ConfigResolverService, + GupshupWhatsappService ], }) export class ApiModule { diff --git a/src/api/api.service.ts b/src/api/api.service.ts index 54edf13..87525ba 100644 --- a/src/api/api.service.ts +++ b/src/api/api.service.ts @@ -35,6 +35,7 @@ import { InjectRedis } from '@nestjs-modules/ioredis'; import Redis from 'ioredis'; const jwksClient = require('jwks-rsa'); import * as jwt from 'jsonwebtoken'; +import { GupshupWhatsappService } from './sms/gupshupWhatsapp/gupshupWhatsapp.service'; CryptoJS.lib.WordArray.words; @@ -49,7 +50,8 @@ export class ApiService { private readonly fusionAuthService: FusionauthService, private readonly otpService: OtpService, private readonly configResolverService: ConfigResolverService, - @InjectRedis() private readonly redis: Redis + @InjectRedis() private readonly redis: Redis, + private readonly gupshupWhatsappService: GupshupWhatsappService ) { this.client = jwksClient({ jwksUri: this.configService.get("JWKS_URI"), @@ -561,6 +563,7 @@ export class ApiService { 4. Send login response with the token */ let otp = loginDto.password; + let phone = loginDto.loginId; const salt = this.configResolverService.getSalt(loginDto.applicationId); let verifyOTPResult; if( @@ -572,8 +575,16 @@ export class ApiService { verifyOTPResult = {status: SMSResponseStatus.success} else verifyOTPResult = {status: SMSResponseStatus.failure} - } - else { + } else if (phone.includes('-')) { + const [countryCode, number] = phone.split('-'); + loginDto.loginId = number; + const status: any = await this.gupshupWhatsappService.verifyWhatsappOTP(loginDto.loginId, loginDto.password); + if(status.status == 'success') { + verifyOTPResult = {status: SMSResponseStatus.success} + } else { + verifyOTPResult = {status: SMSResponseStatus.failure} + } + } else { verifyOTPResult = await this.otpService.verifyOTP({ phone: loginDto.loginId, otp: loginDto.password, // existing OTP diff --git a/src/api/sms/gupshupWhatsapp/gupshupWhatsapp.service.ts b/src/api/sms/gupshupWhatsapp/gupshupWhatsapp.service.ts new file mode 100644 index 0000000..0c15830 --- /dev/null +++ b/src/api/sms/gupshupWhatsapp/gupshupWhatsapp.service.ts @@ -0,0 +1,144 @@ +import { SMSData, SMSProvider, SMSResponse, SMSResponseStatus } from "../sms.interface"; +import { InjectRedis } from '@nestjs-modules/ioredis'; +import Redis from 'ioredis'; +import { Injectable } from "@nestjs/common"; + + +@Injectable() +export class GupshupWhatsappService { + + constructor( + @InjectRedis() private readonly redis: Redis + ) { + } + + async sendWhatsappOTP(smsData: SMSData): Promise { + const status: SMSResponse = { + providerResponseCode: null, + status: SMSResponseStatus.failure, + messageID: null, + error: null, + providerSuccessResponse: null, + phone: smsData.phone, + networkResponseCode: null, + provider: SMSProvider.gupshup + }; + + // Generate 4 digit OTP + const otp = Math.floor(1000 + Math.random() * 9000); + + try { + // First opt-in the user + const optInParams = new URLSearchParams(); + optInParams.append("method", "OPT_IN"); + optInParams.append("format", "text"); + optInParams.append("userid", process.env.GUPSHUP_WHATSAPP_USERID); + optInParams.append("password", process.env.GUPSHUP_WHATSAPP_PASSWORD); + optInParams.append("phone_number", `91${smsData.phone}`); + optInParams.append("v", "1.1"); + optInParams.append("auth_scheme", "plain"); + optInParams.append("channel", "WHATSAPP"); + + let optinURL = process.env.GUPSHUP_WHATSAPP_BASEURL + '?' + optInParams.toString(); + + await fetch(optinURL, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }); + + // Then send OTP message + const otpMessage = `${otp} is your verification code. For your security, do not share this code.`; + + const sendOtpParams = new URLSearchParams(); + sendOtpParams.append("method", "SENDMESSAGE"); + sendOtpParams.append("userid", process.env.GUPSHUP_WHATSAPP_USERID); + sendOtpParams.append("password", process.env.GUPSHUP_WHATSAPP_PASSWORD); + sendOtpParams.append("send_to", smsData.phone); + sendOtpParams.append("v", "1.1"); + sendOtpParams.append("format", "json"); + sendOtpParams.append("msg_type", "TEXT"); + sendOtpParams.append("msg", otpMessage); + sendOtpParams.append("isTemplate", "true"); + sendOtpParams.append("footer", "This code expires in 30 minute."); + + let sendOtpURL = process.env.GUPSHUP_WHATSAPP_BASEURL + '?' + sendOtpParams.toString(); + + const response = await fetch(sendOtpURL, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }); + + if (response.status === 200) { + // Store OTP in Redis with 30 minute expiry + await this.redis.set(`whatsapp_otp:${smsData.phone}`, otp.toString(), 'EX', 1800); + + status.providerSuccessResponse = await response.text(); + status.status = SMSResponseStatus.success; + status.messageID = otp.toString(); + } + + return status; + + } catch (error) { + status.error = { + errorCode: error.code || 'WHATSAPP_ERROR', + errorText: error.message + }; + return status; + } + } + + async verifyWhatsappOTP(phone: string, otp: string): Promise { + const status: SMSResponse = { + providerResponseCode: null, + status: SMSResponseStatus.failure, + messageID: null, + error: null, + providerSuccessResponse: null, + phone: phone, + networkResponseCode: null, + provider: SMSProvider.gupshup + }; + + try { + // Get stored OTP from Redis + const storedOTP = await this.redis.get(`whatsapp_otp:${phone}`); + console.log("storedOTP",storedOTP) + + if (!storedOTP) { + status.error = { + errorCode: 'OTP_EXPIRED', + errorText: 'OTP has expired or does not exist' + }; + return status; + } + + if (storedOTP === otp) { + // OTP matches + status.status = SMSResponseStatus.success; + status.providerSuccessResponse = 'OTP verified successfully'; + + // Delete the OTP from Redis after successful verification + await this.redis.del(`whatsapp_otp:${phone}`); + } else { + status.error = { + errorCode: 'INVALID_OTP', + errorText: 'Invalid OTP provided' + }; + } + + return status; + + } catch (error) { + status.error = { + errorCode: error.code || 'VERIFICATION_ERROR', + errorText: error.message + }; + return status; + } + } +} \ No newline at end of file