From 1f658546edabdffaef1d2c8fa5d3578b0a091bb8 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 20 May 2025 14:49:57 +0300 Subject: [PATCH 1/5] PM-1201 - sync payments with legacy --- src/api/admin/admin.controller.ts | 12 +-- src/api/admin/admin.module.ts | 9 +- src/api/admin/admin.service.ts | 125 +++++++++++++++------- src/api/user/user.controller.ts | 4 +- src/api/wallet/wallet.controller.ts | 4 +- src/api/winnings/winnings.controller.ts | 11 +- src/api/withdrawal/withdrawal.module.ts | 3 +- src/api/withdrawal/withdrawal.service.ts | 35 ++++++ src/shared/topcoder/challenges.service.ts | 93 ++++++++++++++++ src/shared/topcoder/members.service.ts | 18 ++-- src/shared/topcoder/topcoder.module.ts | 9 +- 11 files changed, 255 insertions(+), 68 deletions(-) create mode 100644 src/shared/topcoder/challenges.service.ts diff --git a/src/api/admin/admin.controller.ts b/src/api/admin/admin.controller.ts index 284a63e..8b9ca26 100644 --- a/src/api/admin/admin.controller.ts +++ b/src/api/admin/admin.controller.ts @@ -171,12 +171,12 @@ export class AdminController { } const result = await this.adminService.updateWinnings(body, user.id); + + result.status = ResponseStatusType.SUCCESS; if (result.error) { result.status = ResponseStatusType.ERROR; } - result.status = ResponseStatusType.SUCCESS; - return result; } @@ -200,12 +200,12 @@ export class AdminController { @Param('winningID') winningId: string, ): Promise> { const result = await this.adminService.getWinningAudit(winningId); + + result.status = ResponseStatusType.SUCCESS; if (result.error) { result.status = ResponseStatusType.ERROR; } - result.status = ResponseStatusType.SUCCESS; - return result; } @@ -230,12 +230,12 @@ export class AdminController { @Param('winningID') winningId: string, ): Promise> { const result = await this.adminService.getWinningAuditPayout(winningId); + + result.status = ResponseStatusType.SUCCESS; if (result.error) { result.status = ResponseStatusType.ERROR; } - result.status = ResponseStatusType.SUCCESS; - return result; } } diff --git a/src/api/admin/admin.module.ts b/src/api/admin/admin.module.ts index 7ceb127..2439f5c 100644 --- a/src/api/admin/admin.module.ts +++ b/src/api/admin/admin.module.ts @@ -1,8 +1,6 @@ import { Module } from '@nestjs/common'; import { AdminController } from './admin.controller'; import { AdminService } from './admin.service'; -import { TaxFormRepository } from '../repository/taxForm.repo'; -import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; import { WinningsRepository } from '../repository/winnings.repo'; import { TopcoderModule } from 'src/shared/topcoder/topcoder.module'; import { PaymentsModule } from 'src/shared/payments'; @@ -10,11 +8,6 @@ import { PaymentsModule } from 'src/shared/payments'; @Module({ imports: [TopcoderModule, PaymentsModule], controllers: [AdminController], - providers: [ - AdminService, - TaxFormRepository, - PaymentMethodRepository, - WinningsRepository, - ], + providers: [AdminService, WinningsRepository], }) export class AdminModule {} diff --git a/src/api/admin/admin.service.ts b/src/api/admin/admin.service.ts index 3e6fdab..efa568d 100644 --- a/src/api/admin/admin.service.ts +++ b/src/api/admin/admin.service.ts @@ -6,16 +6,18 @@ import { Logger, } from '@nestjs/common'; -import { PrismaPromise } from '@prisma/client'; +import { Prisma } from '@prisma/client'; import { PrismaService } from 'src/shared/global/prisma.service'; import { PaymentsService } from 'src/shared/payments'; -import { TaxFormRepository } from '../repository/taxForm.repo'; -import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; import { ResponseDto } from 'src/dto/api-response.dto'; import { PaymentStatus } from 'src/dto/payment.dto'; import { WinningAuditDto, AuditPayoutDto } from './dto/audit.dto'; import { WinningUpdateRequestDto } from './dto/winnings.dto'; +import { + AdminPaymentUpdateData, + TopcoderCallengesService, +} from 'src/shared/topcoder/challenges.service'; /** * The admin winning service. @@ -30,11 +32,14 @@ export class AdminService { */ constructor( private readonly prisma: PrismaService, - private readonly taxFormRepo: TaxFormRepository, - private readonly paymentMethodRepo: PaymentMethodRepository, private readonly paymentsService: PaymentsService, + private readonly tcChallengesService: TopcoderCallengesService, ) {} + private getWinningById(winningId: string) { + return this.prisma.winnings.findFirst({ where: { winning_id: winningId } }); + } + private getPaymentsByWinningsId(winningsId: string, paymentId?: string) { return this.prisma.payment.findMany({ where: { @@ -79,7 +84,9 @@ export class AdminService { releaseDate = await this.getPaymentReleaseDateByWinningsId(winningsId); } - const transactions: PrismaPromise[] = []; + const transactions: (( + tx: Prisma.TransactionClient, + ) => Promise)[] = []; const now = new Date().getTime(); payments.forEach((payment) => { if ( @@ -108,8 +115,8 @@ export class AdminService { if (sinceRelease < 12) { errMessage = `Cannot put a processing payment back to owed, unless it's been processing for at least 12 hours. Currently it's only been ${sinceRelease.toFixed(1)} hours`; } else { - transactions.push( - this.markPaymentReleaseAsFailedByWinningsId(winningsId), + transactions.push((tx) => + this.markPaymentReleaseAsFailedByWinningsId(winningsId, tx), ); } } else { @@ -137,17 +144,18 @@ export class AdminService { throw new BadRequestException(errMessage); } - transactions.push( + transactions.push((tx) => this.updatePaymentStatus( userId, winningsId, payment.payment_id, payment.payment_status, body.paymentStatus, - version, + version++, + tx, ), ); - version += 1; + paymentStatus = body.paymentStatus as PaymentStatus; if (body.paymentStatus === PaymentStatus.OWED) { @@ -155,12 +163,13 @@ export class AdminService { } if (payment.installment_number === 1) { - transactions.push( + transactions.push((tx) => this.addAudit( userId, winningsId, `Modified payment status from ${payment.payment_status} to ${body.paymentStatus}`, body.auditNote, + tx, ), ); } @@ -186,24 +195,25 @@ export class AdminService { ); } - transactions.push( + transactions.push((tx) => this.updateReleaseDate( userId, winningsId, payment.payment_id, newReleaseDate, - version, + version++, + tx, ), ); - version += 1; if (payment.installment_number === 1) { - transactions.push( + transactions.push((tx) => this.addAudit( userId, winningsId, `Modified release date from ${payment.release_date?.toISOString()} to ${newReleaseDate.toISOString()}`, body.auditNote, + tx, ), ); } @@ -218,7 +228,7 @@ export class AdminService { ) { // ideally we should be maintaining the original split of the payment amount between installments - but we aren't really using splits anymore if (payment.installment_number === 1) { - transactions.push( + transactions.push((tx) => this.updatePaymentAmount( userId, winningsId, @@ -227,20 +237,22 @@ export class AdminService { body.paymentAmount, body.paymentAmount, version, + tx, ), ); - transactions.push( + transactions.push((tx) => this.addAudit( userId, winningsId, // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Modified payment amount from ${payment.total_amount} to ${body.paymentAmount.toFixed(2)}`, body.auditNote, + tx, ), ); } else { - transactions.push( + transactions.push((tx) => this.updatePaymentAmount( userId, winningsId, @@ -249,15 +261,43 @@ export class AdminService { 0, body.paymentAmount, version, + tx, ), ); } } }); - if (transactions.length > 0) { - await this.prisma.$transaction(transactions); - } + transactions.push(async () => { + const winning = await this.getWinningById(winningsId); + if (!winning) { + this.logger.error( + `Error updating legacy system for winning ${winningsId}. Winning not found!`, + ); + throw new Error( + `Error updating legacy system for winning ${winningsId}. Winning not found!`, + ); + } + + const payoutData: AdminPaymentUpdateData = { + userId: +userId, + status: body.paymentStatus, + amount: body.paymentAmount, + releaseDate: body.releaseDate, + }; + + await this.tcChallengesService.updateLegacyPayments( + winning.external_id as string, + payoutData, + ); + }); + + // Run all transaction tasks in a single prisma transaction + await this.prisma.$transaction(async (tx) => { + for (const transaction of transactions) { + await transaction(tx); + } + }); if (needsReconciliation) { const winning = await this.prisma.winnings.findFirst({ @@ -320,8 +360,11 @@ export class AdminService { return paymentReleases?.release_date; } - private markPaymentReleaseAsFailedByWinningsId(winningsId: string) { - return this.prisma.payment_releases.updateMany({ + private markPaymentReleaseAsFailedByWinningsId( + winningsId: string, + tx?: Prisma.TransactionClient, + ) { + return (tx ?? this.prisma).payment_releases.updateMany({ where: { payment_release_associations: { some: { @@ -346,19 +389,22 @@ export class AdminService { oldPaymentStatus: string | null, newPaymentStatus: PaymentStatus, currentVersion: number, + tx?: Prisma.TransactionClient, ) { let setDatePaidNull = false; - if ([ - PaymentStatus.PAID, - PaymentStatus.PROCESSING, - PaymentStatus.RETURNED, - PaymentStatus.FAILED, - ].includes(oldPaymentStatus as PaymentStatus) && + if ( + [ + PaymentStatus.PAID, + PaymentStatus.PROCESSING, + PaymentStatus.RETURNED, + PaymentStatus.FAILED, + ].includes(oldPaymentStatus as PaymentStatus) && newPaymentStatus === PaymentStatus.OWED ) { setDatePaidNull = true; } - return this.prisma.payment.update({ + + return (tx ?? this.prisma).payment.update({ where: { payment_id: paymentId, winnings_id: winningsId, @@ -379,8 +425,9 @@ export class AdminService { winningsId: string, action: string, auditNote?: string, + tx?: Prisma.TransactionClient, ) { - return this.prisma.audit.create({ + return (tx ?? this.prisma).audit.create({ data: { user_id: userId, winnings_id: winningsId, @@ -396,8 +443,9 @@ export class AdminService { paymentId: string, newReleaseDate: Date, currentVersion: number, + tx?: Prisma.TransactionClient, ) { - return this.prisma.payment.update({ + return (tx ?? this.prisma).payment.update({ where: { payment_id: paymentId, winnings_id: winningsId, @@ -427,8 +475,9 @@ export class AdminService { grossAmount: number, totalAmount: number, currentVersion: number, + tx?: Prisma.TransactionClient, ) { - return this.prisma.payment.update({ + return (tx ?? this.prisma).payment.update({ where: { payment_id: paymentId, winnings_id: winningsId, @@ -461,11 +510,12 @@ export class AdminService { */ async getWinningAudit( winningId: string, + tx?: Prisma.TransactionClient, ): Promise> { const result = new ResponseDto(); try { - const audits = await this.prisma.audit.findMany({ + const audits = await (tx ?? this.prisma).audit.findMany({ where: { winnings_id: { equals: winningId, @@ -501,11 +551,14 @@ export class AdminService { */ async getWinningAuditPayout( winningId: string, + tx?: Prisma.TransactionClient, ): Promise> { const result = new ResponseDto(); try { - const paymentReleases = await this.prisma.payment_releases.findMany({ + const paymentReleases = await ( + tx ?? this.prisma + ).payment_releases.findMany({ where: { payment_release_associations: { some: { diff --git a/src/api/user/user.controller.ts b/src/api/user/user.controller.ts index f4fa123..c31991b 100644 --- a/src/api/user/user.controller.ts +++ b/src/api/user/user.controller.ts @@ -62,12 +62,12 @@ export class UserController { const result = await this.winningsRepo.searchWinnings( body as WinningRequestDto, ); + + result.status = ResponseStatusType.SUCCESS; if (result.error) { result.status = ResponseStatusType.ERROR; } - result.status = ResponseStatusType.SUCCESS; - return result; } } diff --git a/src/api/wallet/wallet.controller.ts b/src/api/wallet/wallet.controller.ts index c17b9fd..34269ee 100644 --- a/src/api/wallet/wallet.controller.ts +++ b/src/api/wallet/wallet.controller.ts @@ -43,12 +43,12 @@ export class WalletController { @User() user: UserInfo, ): Promise> { const result = await this.walletService.getWalletDetails(user.id); + + result.status = ResponseStatusType.SUCCESS; if (result.error) { result.status = ResponseStatusType.ERROR; } - result.status = ResponseStatusType.SUCCESS; - return result; } } diff --git a/src/api/winnings/winnings.controller.ts b/src/api/winnings/winnings.controller.ts index 15a373b..81a64ff 100644 --- a/src/api/winnings/winnings.controller.ts +++ b/src/api/winnings/winnings.controller.ts @@ -53,12 +53,12 @@ export class WinningsController { body, user.id, ); + + result.status = ResponseStatusType.SUCCESS; if (result.error) { result.status = ResponseStatusType.ERROR; } - result.status = ResponseStatusType.SUCCESS; - return result; } @@ -84,9 +84,10 @@ export class WinningsController { ): Promise> { const result = await this.winningsRepo.searchWinnings(body); - result.status = result.error - ? ResponseStatusType.ERROR - : ResponseStatusType.SUCCESS; + result.status = ResponseStatusType.SUCCESS; + if (result.error) { + result.status = ResponseStatusType.ERROR; + } return { ...result, diff --git a/src/api/withdrawal/withdrawal.module.ts b/src/api/withdrawal/withdrawal.module.ts index 3821d3c..075f4ce 100644 --- a/src/api/withdrawal/withdrawal.module.ts +++ b/src/api/withdrawal/withdrawal.module.ts @@ -4,9 +4,10 @@ import { WithdrawalController } from './withdrawal.controller'; import { WithdrawalService } from './withdrawal.service'; import { TaxFormRepository } from '../repository/taxForm.repo'; import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; +import { TopcoderModule } from 'src/shared/topcoder/topcoder.module'; @Module({ - imports: [PaymentsModule], + imports: [PaymentsModule, TopcoderModule], controllers: [WithdrawalController], providers: [WithdrawalService, TaxFormRepository, PaymentMethodRepository], }) diff --git a/src/api/withdrawal/withdrawal.service.ts b/src/api/withdrawal/withdrawal.service.ts index 4117969..412a733 100644 --- a/src/api/withdrawal/withdrawal.service.ts +++ b/src/api/withdrawal/withdrawal.service.ts @@ -6,6 +6,10 @@ import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; import { payment_releases, payment_status, Prisma } from '@prisma/client'; import { TrolleyService } from 'src/shared/global/trolley.service'; import { PaymentsService } from 'src/shared/payments'; +import { + TopcoderCallengesService, + WithdrawUpdateData, +} from 'src/shared/topcoder/challenges.service'; const TROLLEY_MINIMUM_PAYMENT_AMOUNT = ENV_CONFIG.TROLLEY_MINIMUM_PAYMENT_AMOUNT; @@ -21,6 +25,16 @@ interface ReleasableWinningRow { datePaid: Date; } +function formatDate(date = new Date()) { + const pad = (n, z = 2) => String(n).padStart(z, '0'); + + return ( + `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` + + `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.` + + `${pad(date.getMilliseconds(), 3)}` + ); +} + @Injectable() export class WithdrawalService { private readonly logger = new Logger(WithdrawalService.name); @@ -31,6 +45,7 @@ export class WithdrawalService { private readonly paymentsService: PaymentsService, private readonly paymentMethodRepo: PaymentMethodRepository, private readonly trolleyService: TrolleyService, + private readonly tcChallengesService: TopcoderCallengesService, ) {} getTrolleyRecipientByUserId(userId: string) { @@ -236,6 +251,26 @@ export class WithdrawalService { this.logger.error(errorMsg, error); throw new Error(errorMsg); } + + try { + for (const winning of winnings) { + const payoutData: WithdrawUpdateData = { + userId: +userId, + status: 'Paid', + datePaid: formatDate(new Date()), + }; + + await this.tcChallengesService.updateLegacyPayments( + winning.externalId as string, + payoutData, + ); + } + } catch (error) { + this.logger.error( + `Failed to udpate legacy payment while withdrawing for challenge ${error?.message ?? error}`, + error, + ); + } }); } catch (error) { if (error.code === 'P2010' && error.meta?.code === '55P03') { diff --git a/src/shared/topcoder/challenges.service.ts b/src/shared/topcoder/challenges.service.ts new file mode 100644 index 0000000..d18be27 --- /dev/null +++ b/src/shared/topcoder/challenges.service.ts @@ -0,0 +1,93 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { TopcoderM2MService } from './topcoder-m2m.service'; +import { ENV_CONFIG } from 'src/config'; +import { payment_status } from '@prisma/client'; + +const { TOPCODER_API_BASE_URL } = ENV_CONFIG; + +export interface WithdrawUpdateData { + userId: number; + status: string; + datePaid: string; +} + +export interface AdminPaymentUpdateData { + userId: number; + status: string; + amount: number; + releaseDate: string; +} + +const mapStatus = (payoutData: WithdrawUpdateData | AdminPaymentUpdateData) => { + return { + ...payoutData, + status: { + [payment_status.CANCELLED]: 'Cancelled', + [payment_status.FAILED]: 'Failed', + [payment_status.ON_HOLD]: 'OnHold', + [payment_status.ON_HOLD_ADMIN]: 'OnHoldAdmin', + [payment_status.OWED]: 'Owed', + [payment_status.PAID]: 'Paid', + [payment_status.PROCESSING]: 'Processing', + [payment_status.RETURNED]: 'Returned', + }[payoutData.status], + }; +}; + +@Injectable() +export class TopcoderCallengesService { + private readonly logger = new Logger(TopcoderCallengesService.name); + + constructor(private readonly m2MService: TopcoderM2MService) {} + + async updateLegacyPayments( + challengeId: string, + payoutData: WithdrawUpdateData | AdminPaymentUpdateData, + ) { + const requestData = mapStatus(payoutData); + + let m2mToken: string | undefined; + try { + m2mToken = await this.m2MService.getToken(); + } catch (e) { + this.logger.error( + 'Failed to fetch m2m token for fetching member details!', + e.message ?? e, + ); + } + const requestUrl = `${TOPCODER_API_BASE_URL}/challenges/${challengeId}/legacy-payment`; + + this.logger.debug( + `Updating legacy payment for challenge ${challengeId} with data: ${JSON.stringify(requestData, null, 2)}`, + ); + + try { + const response = await fetch(requestUrl, { + method: 'PATCH', + body: JSON.stringify(requestData), + headers: { + Authorization: `Bearer ${m2mToken}`, + 'Content-Type': 'application/json', + }, + }); + + const jsonResponse: { [key: string]: string } = await response.json(); + + if (response.status > 299) { + throw new Error(jsonResponse.message ?? JSON.stringify(jsonResponse)); + } + + this.logger.debug( + `Response from updating legacy payment for challenge ${challengeId}: ${JSON.stringify(jsonResponse, null, 2)}`, + ); + + return jsonResponse; + } catch (e) { + this.logger.error( + `Failed to update legacy payment for challenge ${challengeId}! Error: ${e?.message ?? e}`, + e, + ); + throw e; + } + } +} diff --git a/src/shared/topcoder/members.service.ts b/src/shared/topcoder/members.service.ts index 35acc68..f11d909 100644 --- a/src/shared/topcoder/members.service.ts +++ b/src/shared/topcoder/members.service.ts @@ -79,17 +79,23 @@ export class TopcoderMembersService { const requestUrl = `${TOPCODER_API_BASE_URL}/members/${handle}${fields ? `?fields=${fields.join(',')}` : ''}`; try { - const response: { [key: string]: string } = await fetch(requestUrl, { + const response = await fetch(requestUrl, { headers: { Authorization: `Bearer ${m2mToken}` }, - }).then((r) => r.json()); - return response; + }); + + const jsonResponse: { [key: string]: string } = await response.json(); + + if (response.status > 299) { + throw new Error(jsonResponse.message ?? JSON.stringify(jsonResponse)); + } + + return jsonResponse; } catch (e) { this.logger.error( - `Failed to fetch tc member info for user '${handle}'! Error: `, - e?.message ?? e, + `Failed to fetch tc member info for user '${handle}'! Error: ${e?.message ?? e}`, e, ); - return {}; + throw e; } } } diff --git a/src/shared/topcoder/topcoder.module.ts b/src/shared/topcoder/topcoder.module.ts index bd90bb0..5bd4db8 100644 --- a/src/shared/topcoder/topcoder.module.ts +++ b/src/shared/topcoder/topcoder.module.ts @@ -1,9 +1,14 @@ import { Module } from '@nestjs/common'; import { TopcoderMembersService } from './members.service'; import { TopcoderM2MService } from './topcoder-m2m.service'; +import { TopcoderCallengesService } from './challenges.service'; @Module({ - providers: [TopcoderMembersService, TopcoderM2MService], - exports: [TopcoderMembersService], + providers: [ + TopcoderCallengesService, + TopcoderMembersService, + TopcoderM2MService, + ], + exports: [TopcoderCallengesService, TopcoderMembersService], }) export class TopcoderModule {} From 87cd6392091acba75bee771cc7043a8594692180 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 20 May 2025 15:23:16 +0300 Subject: [PATCH 2/5] fix typo --- src/api/admin/admin.service.ts | 4 ++-- src/api/withdrawal/withdrawal.service.ts | 6 +++--- src/shared/topcoder/challenges.service.ts | 4 ++-- src/shared/topcoder/topcoder.module.ts | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/api/admin/admin.service.ts b/src/api/admin/admin.service.ts index efa568d..e6aa633 100644 --- a/src/api/admin/admin.service.ts +++ b/src/api/admin/admin.service.ts @@ -16,7 +16,7 @@ import { WinningAuditDto, AuditPayoutDto } from './dto/audit.dto'; import { WinningUpdateRequestDto } from './dto/winnings.dto'; import { AdminPaymentUpdateData, - TopcoderCallengesService, + TopcoderChallengesService, } from 'src/shared/topcoder/challenges.service'; /** @@ -33,7 +33,7 @@ export class AdminService { constructor( private readonly prisma: PrismaService, private readonly paymentsService: PaymentsService, - private readonly tcChallengesService: TopcoderCallengesService, + private readonly tcChallengesService: TopcoderChallengesService, ) {} private getWinningById(winningId: string) { diff --git a/src/api/withdrawal/withdrawal.service.ts b/src/api/withdrawal/withdrawal.service.ts index 412a733..67c9722 100644 --- a/src/api/withdrawal/withdrawal.service.ts +++ b/src/api/withdrawal/withdrawal.service.ts @@ -7,7 +7,7 @@ import { payment_releases, payment_status, Prisma } from '@prisma/client'; import { TrolleyService } from 'src/shared/global/trolley.service'; import { PaymentsService } from 'src/shared/payments'; import { - TopcoderCallengesService, + TopcoderChallengesService, WithdrawUpdateData, } from 'src/shared/topcoder/challenges.service'; @@ -45,7 +45,7 @@ export class WithdrawalService { private readonly paymentsService: PaymentsService, private readonly paymentMethodRepo: PaymentMethodRepository, private readonly trolleyService: TrolleyService, - private readonly tcChallengesService: TopcoderCallengesService, + private readonly tcChallengesService: TopcoderChallengesService, ) {} getTrolleyRecipientByUserId(userId: string) { @@ -267,7 +267,7 @@ export class WithdrawalService { } } catch (error) { this.logger.error( - `Failed to udpate legacy payment while withdrawing for challenge ${error?.message ?? error}`, + `Failed to update legacy payment while withdrawing for challenge ${error?.message ?? error}`, error, ); } diff --git a/src/shared/topcoder/challenges.service.ts b/src/shared/topcoder/challenges.service.ts index d18be27..23e2e8b 100644 --- a/src/shared/topcoder/challenges.service.ts +++ b/src/shared/topcoder/challenges.service.ts @@ -35,8 +35,8 @@ const mapStatus = (payoutData: WithdrawUpdateData | AdminPaymentUpdateData) => { }; @Injectable() -export class TopcoderCallengesService { - private readonly logger = new Logger(TopcoderCallengesService.name); +export class TopcoderChallengesService { + private readonly logger = new Logger(TopcoderChallengesService.name); constructor(private readonly m2MService: TopcoderM2MService) {} diff --git a/src/shared/topcoder/topcoder.module.ts b/src/shared/topcoder/topcoder.module.ts index 5bd4db8..86ad64d 100644 --- a/src/shared/topcoder/topcoder.module.ts +++ b/src/shared/topcoder/topcoder.module.ts @@ -1,14 +1,14 @@ import { Module } from '@nestjs/common'; import { TopcoderMembersService } from './members.service'; import { TopcoderM2MService } from './topcoder-m2m.service'; -import { TopcoderCallengesService } from './challenges.service'; +import { TopcoderChallengesService } from './challenges.service'; @Module({ providers: [ - TopcoderCallengesService, + TopcoderChallengesService, TopcoderMembersService, TopcoderM2MService, ], - exports: [TopcoderCallengesService, TopcoderMembersService], + exports: [TopcoderChallengesService, TopcoderMembersService], }) export class TopcoderModule {} From f4097ff3032f6ecda758ea2ed8e4e82f8260586c Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 21 May 2025 10:56:46 +0300 Subject: [PATCH 3/5] Check for wipro emails and deny withdrawal --- src/api/withdrawal/withdrawal.service.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/api/withdrawal/withdrawal.service.ts b/src/api/withdrawal/withdrawal.service.ts index 67c9722..1b1de58 100644 --- a/src/api/withdrawal/withdrawal.service.ts +++ b/src/api/withdrawal/withdrawal.service.ts @@ -10,6 +10,8 @@ import { TopcoderChallengesService, WithdrawUpdateData, } from 'src/shared/topcoder/challenges.service'; +import { TopcoderMembersService } from 'src/shared/topcoder/members.service'; +import { MEMBER_FIELDS } from 'src/shared/topcoder'; const TROLLEY_MINIMUM_PAYMENT_AMOUNT = ENV_CONFIG.TROLLEY_MINIMUM_PAYMENT_AMOUNT; @@ -46,6 +48,7 @@ export class WithdrawalService { private readonly paymentMethodRepo: PaymentMethodRepository, private readonly trolleyService: TrolleyService, private readonly tcChallengesService: TopcoderChallengesService, + private readonly tcMembersService: TopcoderMembersService, ) {} getTrolleyRecipientByUserId(userId: string) { @@ -190,6 +193,23 @@ export class WithdrawalService { ); } + let userInfo: { email: string }; + this.logger.debug(`Getting user details for user ${userHandle}(${userId})`); + try { + userInfo = (await this.tcMembersService.getMemberInfoByUserHandle( + userHandle, + { fields: [MEMBER_FIELDS.email] }, + )) as { email: string }; + } catch { + throw new Error('Failed to fetch UserInfo for withdrawal!'); + } + + if (userInfo.email.toLocaleLowerCase().indexOf('wipro.com') > -1) { + throw new Error( + 'Please contact Topgear support to process your withdrawal.', + ); + } + try { await this.prisma.$transaction(async (tx) => { const winnings = await this.getReleasableWinningsForUserId( From 5f5a74a1c77f91921b40ea90099f86b3f7452ead Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 21 May 2025 10:58:36 +0300 Subject: [PATCH 4/5] typo --- src/api/withdrawal/withdrawal.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/withdrawal/withdrawal.service.ts b/src/api/withdrawal/withdrawal.service.ts index 1b1de58..02ba47b 100644 --- a/src/api/withdrawal/withdrawal.service.ts +++ b/src/api/withdrawal/withdrawal.service.ts @@ -204,7 +204,7 @@ export class WithdrawalService { throw new Error('Failed to fetch UserInfo for withdrawal!'); } - if (userInfo.email.toLocaleLowerCase().indexOf('wipro.com') > -1) { + if (userInfo.email.toLowerCase().indexOf('wipro.com') > -1) { throw new Error( 'Please contact Topgear support to process your withdrawal.', ); From 1c6ff07605b7786a989cc5eb34741d25f735eda9 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 21 May 2025 11:34:30 +0300 Subject: [PATCH 5/5] fix legacy sync --- src/api/admin/admin.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/admin/admin.service.ts b/src/api/admin/admin.service.ts index e6aa633..5fd749c 100644 --- a/src/api/admin/admin.service.ts +++ b/src/api/admin/admin.service.ts @@ -280,7 +280,7 @@ export class AdminService { } const payoutData: AdminPaymentUpdateData = { - userId: +userId, + userId: +winning.winner_id, status: body.paymentStatus, amount: body.paymentAmount, releaseDate: body.releaseDate,