Skip to content

Commit 8202966

Browse files
Merge pull request #103 from Amruth-Vamshi/feature/WA-provider
Enabled gupshup whatsapp provided for international numbers
2 parents 23c009d + b9c0e89 commit 8202966

File tree

4 files changed

+177
-5
lines changed

4 files changed

+177
-5
lines changed

src/api/api.controller.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { ConfigService } from '@nestjs/config';
3939
import { v4 as uuidv4 } from 'uuid';
4040
import { VerifyJWTDto } from './dto/verify-jwt.dto';
4141
import { Request } from 'express';
42+
import { GupshupWhatsappService } from './sms/gupshupWhatsapp/gupshupWhatsapp.service';
4243
// eslint-disable-next-line @typescript-eslint/no-var-requires
4344
const CryptoJS = require('crypto-js');
4445

@@ -53,6 +54,7 @@ export class ApiController {
5354
private readonly otpService: OtpService,
5455
private readonly apiService: ApiService,
5556
private readonly configResolverService: ConfigResolverService,
57+
private readonly gupshupWhatsappService: GupshupWhatsappService
5658
) {}
5759

5860
@Get()
@@ -91,8 +93,21 @@ export class ApiController {
9193
);
9294
}
9395
}
94-
const status: SMSResponse = await this.otpService.sendOTP(params.phone);
95-
return { status };
96+
// Check if phone number contains country code (e.g. 91-1234567890)
97+
if (params.phone.includes('-')) {
98+
const [countryCode, number] = params.phone.split('-');
99+
params.phone = number;
100+
const status: any = await this.gupshupWhatsappService.sendWhatsappOTP({
101+
phone: number,
102+
template: null,
103+
type: null,
104+
params: null
105+
});
106+
return { status };
107+
} else {
108+
const status: any = await this.otpService.sendOTP(params.phone);
109+
return { status };
110+
}
96111
}
97112

98113
@Get('verifyOTP')

src/api/api.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { SmsService } from './sms/sms.service';
1212
import got from 'got/dist/source';
1313
import { CdacService } from './sms/cdac/cdac.service';
1414
import { RajaiOtpService } from '../user/sms/rajaiOtpService/rajaiOtpService.service';
15+
import { GupshupWhatsappService } from './sms/gupshupWhatsapp/gupshupWhatsapp.service';
1516

1617
const otpServiceFactory = {
1718
provide: OtpService,
@@ -68,6 +69,7 @@ const otpServiceFactory = {
6869
otpServiceFactory,
6970
QueryGeneratorService,
7071
ConfigResolverService,
72+
GupshupWhatsappService
7173
],
7274
})
7375
export class ApiModule {

src/api/api.service.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { InjectRedis } from '@nestjs-modules/ioredis';
3535
import Redis from 'ioredis';
3636
const jwksClient = require('jwks-rsa');
3737
import * as jwt from 'jsonwebtoken';
38+
import { GupshupWhatsappService } from './sms/gupshupWhatsapp/gupshupWhatsapp.service';
3839

3940
CryptoJS.lib.WordArray.words;
4041

@@ -49,7 +50,8 @@ export class ApiService {
4950
private readonly fusionAuthService: FusionauthService,
5051
private readonly otpService: OtpService,
5152
private readonly configResolverService: ConfigResolverService,
52-
@InjectRedis() private readonly redis: Redis
53+
@InjectRedis() private readonly redis: Redis,
54+
private readonly gupshupWhatsappService: GupshupWhatsappService
5355
) {
5456
this.client = jwksClient({
5557
jwksUri: this.configService.get("JWKS_URI"),
@@ -561,6 +563,7 @@ export class ApiService {
561563
4. Send login response with the token
562564
*/
563565
let otp = loginDto.password;
566+
let phone = loginDto.loginId;
564567
const salt = this.configResolverService.getSalt(loginDto.applicationId);
565568
let verifyOTPResult;
566569
if(
@@ -572,8 +575,16 @@ export class ApiService {
572575
verifyOTPResult = {status: SMSResponseStatus.success}
573576
else
574577
verifyOTPResult = {status: SMSResponseStatus.failure}
575-
}
576-
else {
578+
} else if (phone.includes('-')) {
579+
const [countryCode, number] = phone.split('-');
580+
loginDto.loginId = number;
581+
const status: any = await this.gupshupWhatsappService.verifyWhatsappOTP(loginDto.loginId, loginDto.password);
582+
if(status.status == 'success') {
583+
verifyOTPResult = {status: SMSResponseStatus.success}
584+
} else {
585+
verifyOTPResult = {status: SMSResponseStatus.failure}
586+
}
587+
} else {
577588
verifyOTPResult = await this.otpService.verifyOTP({
578589
phone: loginDto.loginId,
579590
otp: loginDto.password, // existing OTP
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { SMSData, SMSProvider, SMSResponse, SMSResponseStatus } from "../sms.interface";
2+
import { InjectRedis } from '@nestjs-modules/ioredis';
3+
import Redis from 'ioredis';
4+
import { Injectable } from "@nestjs/common";
5+
6+
7+
@Injectable()
8+
export class GupshupWhatsappService {
9+
10+
constructor(
11+
@InjectRedis() private readonly redis: Redis
12+
) {
13+
}
14+
15+
async sendWhatsappOTP(smsData: SMSData): Promise<SMSResponse> {
16+
const status: SMSResponse = {
17+
providerResponseCode: null,
18+
status: SMSResponseStatus.failure,
19+
messageID: null,
20+
error: null,
21+
providerSuccessResponse: null,
22+
phone: smsData.phone,
23+
networkResponseCode: null,
24+
provider: SMSProvider.gupshup
25+
};
26+
27+
// Generate 4 digit OTP
28+
const otp = Math.floor(1000 + Math.random() * 9000);
29+
30+
try {
31+
// First opt-in the user
32+
const optInParams = new URLSearchParams();
33+
optInParams.append("method", "OPT_IN");
34+
optInParams.append("format", "text");
35+
optInParams.append("userid", process.env.GUPSHUP_WHATSAPP_USERID);
36+
optInParams.append("password", process.env.GUPSHUP_WHATSAPP_PASSWORD);
37+
optInParams.append("phone_number", `91${smsData.phone}`);
38+
optInParams.append("v", "1.1");
39+
optInParams.append("auth_scheme", "plain");
40+
optInParams.append("channel", "WHATSAPP");
41+
42+
let optinURL = process.env.GUPSHUP_WHATSAPP_BASEURL + '?' + optInParams.toString();
43+
44+
await fetch(optinURL, {
45+
method: 'GET',
46+
headers: {
47+
'Content-Type': 'application/json'
48+
}
49+
});
50+
51+
// Then send OTP message
52+
const otpMessage = `${otp} is your verification code. For your security, do not share this code.`;
53+
54+
const sendOtpParams = new URLSearchParams();
55+
sendOtpParams.append("method", "SENDMESSAGE");
56+
sendOtpParams.append("userid", process.env.GUPSHUP_WHATSAPP_USERID);
57+
sendOtpParams.append("password", process.env.GUPSHUP_WHATSAPP_PASSWORD);
58+
sendOtpParams.append("send_to", smsData.phone);
59+
sendOtpParams.append("v", "1.1");
60+
sendOtpParams.append("format", "json");
61+
sendOtpParams.append("msg_type", "TEXT");
62+
sendOtpParams.append("msg", otpMessage);
63+
sendOtpParams.append("isTemplate", "true");
64+
sendOtpParams.append("footer", "This code expires in 30 minute.");
65+
66+
let sendOtpURL = process.env.GUPSHUP_WHATSAPP_BASEURL + '?' + sendOtpParams.toString();
67+
68+
const response = await fetch(sendOtpURL, {
69+
method: 'GET',
70+
headers: {
71+
'Content-Type': 'application/json'
72+
}
73+
});
74+
75+
if (response.status === 200) {
76+
// Store OTP in Redis with 30 minute expiry
77+
await this.redis.set(`whatsapp_otp:${smsData.phone}`, otp.toString(), 'EX', 1800);
78+
79+
status.providerSuccessResponse = await response.text();
80+
status.status = SMSResponseStatus.success;
81+
status.messageID = otp.toString();
82+
}
83+
84+
return status;
85+
86+
} catch (error) {
87+
status.error = {
88+
errorCode: error.code || 'WHATSAPP_ERROR',
89+
errorText: error.message
90+
};
91+
return status;
92+
}
93+
}
94+
95+
async verifyWhatsappOTP(phone: string, otp: string): Promise<SMSResponse> {
96+
const status: SMSResponse = {
97+
providerResponseCode: null,
98+
status: SMSResponseStatus.failure,
99+
messageID: null,
100+
error: null,
101+
providerSuccessResponse: null,
102+
phone: phone,
103+
networkResponseCode: null,
104+
provider: SMSProvider.gupshup
105+
};
106+
107+
try {
108+
// Get stored OTP from Redis
109+
const storedOTP = await this.redis.get(`whatsapp_otp:${phone}`);
110+
console.log("storedOTP",storedOTP)
111+
112+
if (!storedOTP) {
113+
status.error = {
114+
errorCode: 'OTP_EXPIRED',
115+
errorText: 'OTP has expired or does not exist'
116+
};
117+
return status;
118+
}
119+
120+
if (storedOTP === otp) {
121+
// OTP matches
122+
status.status = SMSResponseStatus.success;
123+
status.providerSuccessResponse = 'OTP verified successfully';
124+
125+
// Delete the OTP from Redis after successful verification
126+
await this.redis.del(`whatsapp_otp:${phone}`);
127+
} else {
128+
status.error = {
129+
errorCode: 'INVALID_OTP',
130+
errorText: 'Invalid OTP provided'
131+
};
132+
}
133+
134+
return status;
135+
136+
} catch (error) {
137+
status.error = {
138+
errorCode: error.code || 'VERIFICATION_ERROR',
139+
errorText: error.message
140+
};
141+
return status;
142+
}
143+
}
144+
}

0 commit comments

Comments
 (0)