Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabled gupshup whatsapp provided for international numbers #103

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
}
Loading