chore: add logic for remove completed payment
This commit is contained in:
@@ -10,9 +10,8 @@ export function bullMqConfig(): SharedBullAsyncConfiguration {
|
|||||||
},
|
},
|
||||||
prefix: "danak-console",
|
prefix: "danak-console",
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
removeOnComplete: false,
|
removeOnComplete: true,
|
||||||
removeOnFail: false,
|
removeOnFail: false,
|
||||||
attempts: 5,
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,13 +38,6 @@ export class CreateAnnouncementDto {
|
|||||||
@ApiProperty({ description: "Is this announcement important?", example: false })
|
@ApiProperty({ description: "Is this announcement important?", example: false })
|
||||||
isImportant: boolean;
|
isImportant: boolean;
|
||||||
|
|
||||||
// @IsOptional()
|
|
||||||
// @IsNotEmpty({ message: AnnouncementMessage.SERVICE_MUST_BE_ID })
|
|
||||||
// @ArrayMinSize(1)
|
|
||||||
// @IsUUID("4", { message: AnnouncementMessage.SERVICE_MUST_BE_UUID, each: true })
|
|
||||||
// @ApiProperty({ type: [String], description: "Service IDs", example: ["d290f1ee-6c54-4b01-90e6-d701748f0851"] })
|
|
||||||
// serviceIds: string[];
|
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNotEmpty({ message: AnnouncementMessage.SERVICE_MUST_BE_ID })
|
@IsNotEmpty({ message: AnnouncementMessage.SERVICE_MUST_BE_ID })
|
||||||
@IsUUID("4", { message: AnnouncementMessage.SERVICE_MUST_BE_UUID })
|
@IsUUID("4", { message: AnnouncementMessage.SERVICE_MUST_BE_UUID })
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ import { ReferralsModule } from "../referrals/referrals.module";
|
|||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([Payment, PaymentGateway, BankAccount, DepositRequest]),
|
TypeOrmModule.forFeature([Payment, PaymentGateway, BankAccount, DepositRequest]),
|
||||||
BullModule.registerQueue({ name: PAYMENT.QUEUE_NAME }),
|
BullModule.registerQueue({
|
||||||
|
name: PAYMENT.QUEUE_NAME,
|
||||||
|
defaultJobOptions: {
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
WalletsModule,
|
WalletsModule,
|
||||||
NotificationModule,
|
NotificationModule,
|
||||||
ReferralsModule,
|
ReferralsModule,
|
||||||
|
|||||||
@@ -176,10 +176,19 @@ export class PaymentsService {
|
|||||||
if (verifyData.code === 100) {
|
if (verifyData.code === 100) {
|
||||||
return this.processSuccessfulVerification(payment, verifyData.ref_id, qryRnr);
|
return this.processSuccessfulVerification(payment, verifyData.ref_id, qryRnr);
|
||||||
} else if (verifyData.code === 101) {
|
} else if (verifyData.code === 101) {
|
||||||
return { status: PaymentStatus.PENDING, payment };
|
const freshPayment = await qryRnr.manager.findOne(Payment, { where: { id: payment.id } });
|
||||||
|
//
|
||||||
|
if (freshPayment && freshPayment.status === PaymentStatus.COMPLETED) {
|
||||||
|
this.logger.log(`Payment ${payment.id} already marked as COMPLETED in DB (code 101)`);
|
||||||
|
return { status: payment.status, payment };
|
||||||
|
} else {
|
||||||
|
this.logger.log(`Payment ${payment.id} marked as COMPLETED after gateway verification (code 101)`);
|
||||||
|
return await this.processSuccessfulVerification(payment, verifyData.ref_id, qryRnr);
|
||||||
|
}
|
||||||
|
//
|
||||||
} else {
|
} else {
|
||||||
await this.handleFailedPayment(payment.id, qryRnr);
|
await this.handleFailedPayment(payment.id, qryRnr);
|
||||||
return { status: PaymentStatus.FAILED, payment };
|
return { status: payment.status, payment };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//===============================================
|
//===============================================
|
||||||
@@ -250,6 +259,7 @@ export class PaymentsService {
|
|||||||
{ status: PaymentStatus.COMPLETED, transactionId: transactionId.toString() },
|
{ status: PaymentStatus.COMPLETED, transactionId: transactionId.toString() },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.removePaymentReminderJobs(payment.id);
|
||||||
await this.handleReferralReward(payment, queryRunner);
|
await this.handleReferralReward(payment, queryRunner);
|
||||||
return this.walletsService.createPaymentTransaction(userId, amount, payment, queryRunner);
|
return this.walletsService.createPaymentTransaction(userId, amount, payment, queryRunner);
|
||||||
}
|
}
|
||||||
@@ -614,4 +624,22 @@ export class PaymentsService {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//===============================================
|
||||||
|
/**
|
||||||
|
* Remove all scheduled/delayed payment jobs for a paymentId from the queue
|
||||||
|
*/
|
||||||
|
private async removePaymentReminderJobs(paymentId: string) {
|
||||||
|
const jobs = await this.paymentQueue.getJobs(["delayed", "waiting"]);
|
||||||
|
for (const job of jobs) {
|
||||||
|
if (
|
||||||
|
(job.name === PAYMENT.FIRST_REMINDER || job.name === PAYMENT.SECOND_REMINDER || job.name === PAYMENT.CANCELLATION) &&
|
||||||
|
job.data &&
|
||||||
|
job.data.paymentId === paymentId
|
||||||
|
) {
|
||||||
|
await job.remove();
|
||||||
|
this.logger.log(`Removed scheduled job ${job.name} for payment ${paymentId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ export class PaymentProcessor extends WorkerProcessor {
|
|||||||
private async processJobWithTransaction(job: Job<PaymentJobData>, queryRunner: QueryRunner) {
|
private async processJobWithTransaction(job: Job<PaymentJobData>, queryRunner: QueryRunner) {
|
||||||
switch (job.name) {
|
switch (job.name) {
|
||||||
case PAYMENT.START:
|
case PAYMENT.START:
|
||||||
return this.handlePaymentStart(job as Job<{ payment: Payment }>, queryRunner);
|
return await this.handlePaymentStart(job as Job<{ payment: Payment }>, queryRunner);
|
||||||
case PAYMENT.FIRST_REMINDER:
|
case PAYMENT.FIRST_REMINDER:
|
||||||
return this.handleFirstReminder(job as Job<{ paymentId: string }>, queryRunner);
|
return await this.handleFirstReminder(job as Job<{ paymentId: string }>, queryRunner);
|
||||||
case PAYMENT.SECOND_REMINDER:
|
case PAYMENT.SECOND_REMINDER:
|
||||||
return this.handleSecondReminder(job as Job<{ paymentId: string }>, queryRunner);
|
return await this.handleSecondReminder(job as Job<{ paymentId: string }>, queryRunner);
|
||||||
case PAYMENT.CANCELLATION:
|
case PAYMENT.CANCELLATION:
|
||||||
return this.handlePaymentCancellation(job as Job<{ paymentId: string }>, queryRunner);
|
return await this.handlePaymentCancellation(job as Job<{ paymentId: string }>, queryRunner);
|
||||||
default:
|
default:
|
||||||
this.logger.error(`Unknown job name: ${job.name}`);
|
this.logger.error(`Unknown job name: ${job.name}`);
|
||||||
return;
|
return;
|
||||||
@@ -77,7 +77,9 @@ export class PaymentProcessor extends WorkerProcessor {
|
|||||||
private async handleFirstReminder(job: Job<{ paymentId: string }>, queryRunner: QueryRunner) {
|
private async handleFirstReminder(job: Job<{ paymentId: string }>, queryRunner: QueryRunner) {
|
||||||
const payment = await this.fetchPayment(job.data.paymentId, queryRunner);
|
const payment = await this.fetchPayment(job.data.paymentId, queryRunner);
|
||||||
|
|
||||||
if (!this.isPaymentPending(payment, queryRunner)) {
|
const isPending = await this.isPaymentPending(payment, queryRunner);
|
||||||
|
|
||||||
|
if (!isPending) {
|
||||||
this.logger.log(`Payment ${payment.id} is no longer pending, skipping reminder`);
|
this.logger.log(`Payment ${payment.id} is no longer pending, skipping reminder`);
|
||||||
return payment;
|
return payment;
|
||||||
}
|
}
|
||||||
@@ -92,7 +94,9 @@ export class PaymentProcessor extends WorkerProcessor {
|
|||||||
private async handleSecondReminder(job: Job<{ paymentId: string }>, queryRunner: QueryRunner) {
|
private async handleSecondReminder(job: Job<{ paymentId: string }>, queryRunner: QueryRunner) {
|
||||||
const payment = await this.fetchPayment(job.data.paymentId, queryRunner);
|
const payment = await this.fetchPayment(job.data.paymentId, queryRunner);
|
||||||
|
|
||||||
if (!this.isPaymentPending(payment, queryRunner)) {
|
const isPending = await this.isPaymentPending(payment, queryRunner);
|
||||||
|
|
||||||
|
if (!isPending) {
|
||||||
this.logger.log(`Payment ${payment.id} is no longer pending, skipping reminder`);
|
this.logger.log(`Payment ${payment.id} is no longer pending, skipping reminder`);
|
||||||
return payment;
|
return payment;
|
||||||
}
|
}
|
||||||
@@ -107,7 +111,9 @@ export class PaymentProcessor extends WorkerProcessor {
|
|||||||
private async handlePaymentCancellation(job: Job<{ paymentId: string }>, queryRunner: QueryRunner) {
|
private async handlePaymentCancellation(job: Job<{ paymentId: string }>, queryRunner: QueryRunner) {
|
||||||
const payment = await this.fetchPayment(job.data.paymentId, queryRunner);
|
const payment = await this.fetchPayment(job.data.paymentId, queryRunner);
|
||||||
|
|
||||||
if (!this.isPaymentPending(payment, queryRunner)) {
|
const isPending = await this.isPaymentPending(payment, queryRunner);
|
||||||
|
|
||||||
|
if (!isPending) {
|
||||||
this.logger.log(`Payment ${payment.id} is no longer pending, skipping cancellation`);
|
this.logger.log(`Payment ${payment.id} is no longer pending, skipping cancellation`);
|
||||||
return payment;
|
return payment;
|
||||||
}
|
}
|
||||||
@@ -169,7 +175,7 @@ export class PaymentProcessor extends WorkerProcessor {
|
|||||||
private async fetchPayment(paymentId: string, queryRunner: QueryRunner): Promise<Payment> {
|
private async fetchPayment(paymentId: string, queryRunner: QueryRunner): Promise<Payment> {
|
||||||
const payment = await queryRunner.manager.findOne(Payment, {
|
const payment = await queryRunner.manager.findOne(Payment, {
|
||||||
where: { id: paymentId },
|
where: { id: paymentId },
|
||||||
relations: { user: true },
|
relations: { user: true, paymentGateway: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!payment) throw new Error(`Payment not found: ${paymentId}`);
|
if (!payment) throw new Error(`Payment not found: ${paymentId}`);
|
||||||
@@ -179,6 +185,7 @@ export class PaymentProcessor extends WorkerProcessor {
|
|||||||
//===============================================
|
//===============================================
|
||||||
|
|
||||||
private async isPaymentPending(payment: Payment, queryRunner: QueryRunner): Promise<boolean> {
|
private async isPaymentPending(payment: Payment, queryRunner: QueryRunner): Promise<boolean> {
|
||||||
|
// 1. Trust DB first
|
||||||
if (payment.status !== PaymentStatus.PENDING) {
|
if (payment.status !== PaymentStatus.PENDING) {
|
||||||
this.logger.log(`Payment ${payment.id} is not in pending status`);
|
this.logger.log(`Payment ${payment.id} is not in pending status`);
|
||||||
return false;
|
return false;
|
||||||
@@ -191,15 +198,24 @@ export class PaymentProcessor extends WorkerProcessor {
|
|||||||
const verifyData = await gateway.verifyPayment({ reference: payment.reference, amount: payment.amount });
|
const verifyData = await gateway.verifyPayment({ reference: payment.reference, amount: payment.amount });
|
||||||
|
|
||||||
const result = await this.paymentsService.processVerificationResult(verifyData, payment, queryRunner);
|
const result = await this.paymentsService.processVerificationResult(verifyData, payment, queryRunner);
|
||||||
if (result.status === PaymentStatus.FAILED || result.status === PaymentStatus.PENDING) {
|
|
||||||
this.logger.log(`Payment ${payment.id} verification returned code ${verifyData.code} (failed or pending)`);
|
// If still pending or failed, return true (will trigger reminder/cancellation as needed)
|
||||||
|
if (result.status === PaymentStatus.PENDING || result.status === PaymentStatus.FAILED || result.status === PaymentStatus.CANCELLED) {
|
||||||
|
this.logger.log(`Payment ${payment.id} is still pending or failed or cancelled (gateway code: ${verifyData.code})`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(`Payment ${payment.id} verification returned code ${verifyData.code} (completed)`);
|
if (result.status === PaymentStatus.COMPLETED) {
|
||||||
|
this.logger.log(`Payment ${payment.id} is completed (gateway code: ${verifyData.code})`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For any other code, treat as not pending
|
||||||
|
this.logger.log(`Payment ${payment.id} gateway returned code ${verifyData.code}, treating as not pending`);
|
||||||
return false;
|
return false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Error verifying payment ${payment.id}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
this.logger.error(`Error verifying payment ${payment.id}: ${error instanceof Error ? error.message : error}`);
|
||||||
|
// On error, assume still pending to avoid false negatives
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user