chore: add logic for remove completed payment
This commit is contained in:
@@ -10,9 +10,8 @@ export function bullMqConfig(): SharedBullAsyncConfiguration {
|
||||
},
|
||||
prefix: "danak-console",
|
||||
defaultJobOptions: {
|
||||
removeOnComplete: false,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: false,
|
||||
attempts: 5,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -38,13 +38,6 @@ export class CreateAnnouncementDto {
|
||||
@ApiProperty({ description: "Is this announcement important?", example: false })
|
||||
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()
|
||||
@IsNotEmpty({ message: AnnouncementMessage.SERVICE_MUST_BE_ID })
|
||||
@IsUUID("4", { message: AnnouncementMessage.SERVICE_MUST_BE_UUID })
|
||||
|
||||
@@ -24,7 +24,13 @@ import { ReferralsModule } from "../referrals/referrals.module";
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Payment, PaymentGateway, BankAccount, DepositRequest]),
|
||||
BullModule.registerQueue({ name: PAYMENT.QUEUE_NAME }),
|
||||
BullModule.registerQueue({
|
||||
name: PAYMENT.QUEUE_NAME,
|
||||
defaultJobOptions: {
|
||||
removeOnComplete: true,
|
||||
removeOnFail: false,
|
||||
},
|
||||
}),
|
||||
WalletsModule,
|
||||
NotificationModule,
|
||||
ReferralsModule,
|
||||
|
||||
@@ -176,10 +176,19 @@ export class PaymentsService {
|
||||
if (verifyData.code === 100) {
|
||||
return this.processSuccessfulVerification(payment, verifyData.ref_id, qryRnr);
|
||||
} 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 {
|
||||
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() },
|
||||
);
|
||||
|
||||
await this.removePaymentReminderJobs(payment.id);
|
||||
await this.handleReferralReward(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) {
|
||||
switch (job.name) {
|
||||
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:
|
||||
return this.handleFirstReminder(job as Job<{ paymentId: string }>, queryRunner);
|
||||
return await this.handleFirstReminder(job as Job<{ paymentId: string }>, queryRunner);
|
||||
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:
|
||||
return this.handlePaymentCancellation(job as Job<{ paymentId: string }>, queryRunner);
|
||||
return await this.handlePaymentCancellation(job as Job<{ paymentId: string }>, queryRunner);
|
||||
default:
|
||||
this.logger.error(`Unknown job name: ${job.name}`);
|
||||
return;
|
||||
@@ -77,7 +77,9 @@ export class PaymentProcessor extends WorkerProcessor {
|
||||
private async handleFirstReminder(job: Job<{ paymentId: string }>, queryRunner: 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`);
|
||||
return payment;
|
||||
}
|
||||
@@ -92,7 +94,9 @@ export class PaymentProcessor extends WorkerProcessor {
|
||||
private async handleSecondReminder(job: Job<{ paymentId: string }>, queryRunner: 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`);
|
||||
return payment;
|
||||
}
|
||||
@@ -107,7 +111,9 @@ export class PaymentProcessor extends WorkerProcessor {
|
||||
private async handlePaymentCancellation(job: Job<{ paymentId: string }>, queryRunner: 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`);
|
||||
return payment;
|
||||
}
|
||||
@@ -169,7 +175,7 @@ export class PaymentProcessor extends WorkerProcessor {
|
||||
private async fetchPayment(paymentId: string, queryRunner: QueryRunner): Promise<Payment> {
|
||||
const payment = await queryRunner.manager.findOne(Payment, {
|
||||
where: { id: paymentId },
|
||||
relations: { user: true },
|
||||
relations: { user: true, paymentGateway: true },
|
||||
});
|
||||
|
||||
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> {
|
||||
// 1. Trust DB first
|
||||
if (payment.status !== PaymentStatus.PENDING) {
|
||||
this.logger.log(`Payment ${payment.id} is not in pending status`);
|
||||
return false;
|
||||
@@ -191,15 +198,24 @@ export class PaymentProcessor extends WorkerProcessor {
|
||||
const verifyData = await gateway.verifyPayment({ reference: payment.reference, amount: payment.amount });
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user