Skip to content

Commit bf2fd8d

Browse files
committed
lock otp db row
1 parent 42749c6 commit bf2fd8d

File tree

2 files changed

+80
-62
lines changed

2 files changed

+80
-62
lines changed

src/api/withdrawal/withdrawal.service.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,14 +252,29 @@ export class WithdrawalService {
252252
);
253253
return { error: otpError };
254254
} else {
255-
const otpResponse = await this.otpService.verifyOtpCode(
256-
otpCode,
257-
userInfo,
258-
reference_type.WITHDRAW_PAYMENT,
259-
);
255+
try {
256+
const otpResponse = await this.otpService.verifyOtpCode(
257+
otpCode,
258+
userInfo,
259+
reference_type.WITHDRAW_PAYMENT,
260+
);
260261

261-
if (!otpResponse || otpResponse.code !== 'success') {
262-
return { error: otpResponse };
262+
if (!otpResponse || otpResponse.code !== 'success') {
263+
return { error: otpResponse };
264+
}
265+
} catch (error) {
266+
if (error.code === 'P2010' && error.meta?.code === '55P03') {
267+
this.logger.error(
268+
'Payment request denied because payment row was locked previously!',
269+
error,
270+
);
271+
272+
throw new Error(
273+
'Some or all of the winnings you requested to process are either processing, on hold or already paid.',
274+
);
275+
} else {
276+
throw error;
277+
}
263278
}
264279
}
265280

src/shared/global/otp.service.ts

Lines changed: 58 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Injectable, Logger } from '@nestjs/common';
22
import { PrismaService } from './prisma.service';
33
import crypto from 'crypto';
4-
import { reference_type } from '@prisma/client';
4+
import { otp, reference_type } from '@prisma/client';
55
import { ENV_CONFIG } from 'src/config';
66
import { TopcoderEmailService } from '../topcoder/tc-email.service';
77
import { BasicMemberInfo } from '../topcoder';
@@ -104,62 +104,65 @@ export class OtpService {
104104
userInfo: BasicMemberInfo,
105105
actionType: reference_type,
106106
) {
107-
const record = await this.prisma.otp.findFirst({
108-
where: {
109-
otp_hash: hashOtp(otpCode),
110-
},
111-
orderBy: {
112-
expiration_time: 'desc',
113-
},
114-
});
115-
116-
if (!record) {
117-
this.logger.warn(`No OTP record found for the provided code.`);
118-
return { code: 'otp_invalid', message: `Invalid OTP code.` };
119-
}
120-
121-
if (record.email !== userInfo.email) {
122-
this.logger.warn(`Email mismatch for OTP verification.`);
123-
return {
124-
code: 'otp_email_mismatch',
125-
message: `Email mismatch for OTP verification.`,
126-
};
127-
}
128-
129-
if (record.action_type !== actionType) {
130-
this.logger.warn(`Action type mismatch for OTP verification.`);
131-
return {
132-
code: 'otp_action_type_mismatch',
133-
message: `Action type mismatch for OTP verification.`,
134-
};
135-
}
136-
137-
if (record.expiration_time && record.expiration_time < new Date()) {
138-
this.logger.warn(`OTP code has expired.`);
139-
return { code: 'otp_expired', message: `OTP code has expired.` };
140-
}
141-
142-
if (record.verified_at !== null) {
143-
this.logger.warn(`OTP code has already been verified.`);
144-
return {
145-
code: 'otp_already_verified',
146-
message: `OTP code has already been verified.`,
147-
};
148-
}
107+
return await this.prisma.$transaction(async (tx) => {
108+
const records = await tx.$queryRaw<otp>`
109+
SELECT id, email, otp_hash, expiration_time, action_type, created_at, updated_at, verified_at
110+
FROM otp
111+
WHERE otp_hash=${hashOtp(otpCode)}
112+
ORDER BY expiration_time DESC
113+
LIMIT 1
114+
FOR UPDATE NOWAIT;
115+
`;
116+
const record = records[0];
117+
118+
if (!record) {
119+
this.logger.warn(`No OTP record found for the provided code.`);
120+
return { code: 'otp_invalid', message: `Invalid OTP code.` };
121+
}
122+
123+
if (record.email !== userInfo.email) {
124+
this.logger.warn(`Email mismatch for OTP verification.`);
125+
return {
126+
code: 'otp_email_mismatch',
127+
message: `Email mismatch for OTP verification.`,
128+
};
129+
}
130+
131+
if (record.action_type !== actionType) {
132+
this.logger.warn(`Action type mismatch for OTP verification.`);
133+
return {
134+
code: 'otp_action_type_mismatch',
135+
message: `Action type mismatch for OTP verification.`,
136+
};
137+
}
138+
139+
if (record.expiration_time && record.expiration_time < new Date()) {
140+
this.logger.warn(`OTP code has expired.`);
141+
return { code: 'otp_expired', message: `OTP code has expired.` };
142+
}
143+
144+
if (record.verified_at !== null) {
145+
this.logger.warn(`OTP code has already been verified.`);
146+
return {
147+
code: 'otp_already_verified',
148+
message: `OTP code has already been verified.`,
149+
};
150+
}
151+
152+
this.logger.log(
153+
`OTP code ${otpCode} verified successfully for action ${actionType}`,
154+
);
149155

150-
this.logger.log(
151-
`OTP code ${otpCode} verified successfully for action ${actionType}`,
152-
);
156+
await tx.otp.update({
157+
where: {
158+
id: record.id,
159+
},
160+
data: {
161+
verified_at: new Date(),
162+
},
163+
});
153164

154-
await this.prisma.otp.update({
155-
where: {
156-
id: record.id,
157-
},
158-
data: {
159-
verified_at: new Date(),
160-
},
165+
return { code: 'success' };
161166
});
162-
163-
return { code: 'success' };
164167
}
165168
}

0 commit comments

Comments
 (0)