Skip to content

Commit

Permalink
Merge pull request #103 from Amruth-Vamshi/feature/WA-provider
Browse files Browse the repository at this point in the history
Enabled gupshup whatsapp provided for international numbers
  • Loading branch information
ChakshuGautam authored Dec 9, 2024
2 parents 23c009d + b9c0e89 commit 8202966
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 5 deletions.
19 changes: 17 additions & 2 deletions src/api/api.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -53,6 +54,7 @@ export class ApiController {
private readonly otpService: OtpService,
private readonly apiService: ApiService,
private readonly configResolverService: ConfigResolverService,
private readonly gupshupWhatsappService: GupshupWhatsappService
) {}

@Get()
Expand Down Expand Up @@ -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')
Expand Down
2 changes: 2 additions & 0 deletions src/api/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -68,6 +69,7 @@ const otpServiceFactory = {
otpServiceFactory,
QueryGeneratorService,
ConfigResolverService,
GupshupWhatsappService
],
})
export class ApiModule {
Expand Down
17 changes: 14 additions & 3 deletions src/api/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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"),
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down
144 changes: 144 additions & 0 deletions src/api/sms/gupshupWhatsapp/gupshupWhatsapp.service.ts
Original file line number Diff line number Diff line change
@@ -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<SMSResponse> {
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<SMSResponse> {
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;
}
}
}

0 comments on commit 8202966

Please sign in to comment.