Skip to content

PM-1201 - sync payments with legacy #60

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

Merged
merged 5 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
12 changes: 6 additions & 6 deletions src/api/admin/admin.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -200,12 +200,12 @@ export class AdminController {
@Param('winningID') winningId: string,
): Promise<ResponseDto<WinningAuditDto[]>> {
const result = await this.adminService.getWinningAudit(winningId);

result.status = ResponseStatusType.SUCCESS;
if (result.error) {
result.status = ResponseStatusType.ERROR;
}

result.status = ResponseStatusType.SUCCESS;

return result;
}

Expand All @@ -230,12 +230,12 @@ export class AdminController {
@Param('winningID') winningId: string,
): Promise<ResponseDto<AuditPayoutDto[]>> {
const result = await this.adminService.getWinningAuditPayout(winningId);

result.status = ResponseStatusType.SUCCESS;
if (result.error) {
result.status = ResponseStatusType.ERROR;
}

result.status = ResponseStatusType.SUCCESS;

return result;
}
}
9 changes: 1 addition & 8 deletions src/api/admin/admin.module.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
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';

@Module({
imports: [TopcoderModule, PaymentsModule],
controllers: [AdminController],
providers: [
AdminService,
TaxFormRepository,
PaymentMethodRepository,
WinningsRepository,
],
providers: [AdminService, WinningsRepository],
})
export class AdminModule {}
125 changes: 89 additions & 36 deletions src/api/admin/admin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
TopcoderChallengesService,
} from 'src/shared/topcoder/challenges.service';

/**
* The admin winning service.
Expand All @@ -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: TopcoderChallengesService,
) {}

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: {
Expand Down Expand Up @@ -79,7 +84,9 @@ export class AdminService {
releaseDate = await this.getPaymentReleaseDateByWinningsId(winningsId);
}

const transactions: PrismaPromise<any>[] = [];
const transactions: ((
tx: Prisma.TransactionClient,
) => Promise<unknown>)[] = [];
const now = new Date().getTime();
payments.forEach((payment) => {
if (
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -137,30 +144,32 @@ 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) {
needsReconciliation = true;
}

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,
),
);
}
Expand All @@ -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,
),
);
}
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -249,15 +261,43 @@ export class AdminService {
0,
body.paymentAmount,
version,
tx,
),
);
}
}
});

if (transactions.length > 0) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI - needed to update the way the DBtransaction is handled from batch transactions to one big transaction in order to be able to fail the transaction if the legacy update failed (this is happening only for admin updates only, for withdraw, we're just logging the error).

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({
Expand Down Expand Up @@ -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: {
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -461,11 +510,12 @@ export class AdminService {
*/
async getWinningAudit(
winningId: string,
tx?: Prisma.TransactionClient,
): Promise<ResponseDto<WinningAuditDto[]>> {
const result = new ResponseDto<WinningAuditDto[]>();

try {
const audits = await this.prisma.audit.findMany({
const audits = await (tx ?? this.prisma).audit.findMany({
where: {
winnings_id: {
equals: winningId,
Expand Down Expand Up @@ -501,11 +551,14 @@ export class AdminService {
*/
async getWinningAuditPayout(
winningId: string,
tx?: Prisma.TransactionClient,
): Promise<ResponseDto<AuditPayoutDto[]>> {
const result = new ResponseDto<AuditPayoutDto[]>();

try {
const paymentReleases = await this.prisma.payment_releases.findMany({
const paymentReleases = await (
tx ?? this.prisma
).payment_releases.findMany({
where: {
payment_release_associations: {
some: {
Expand Down
4 changes: 2 additions & 2 deletions src/api/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Loading