import { Injectable, Logger, OnModuleInit, Inject } from '@nestjs/common'; import { WithdrawalEventConsumerService, WithdrawalConfirmedPayload, WithdrawalFailedPayload, } from '@/infrastructure/kafka/withdrawal-event-consumer.service'; import { IWithdrawalOrderRepository, WITHDRAWAL_ORDER_REPOSITORY, IWalletAccountRepository, WALLET_ACCOUNT_REPOSITORY, } from '@/domain/repositories'; /** * Withdrawal Status Handler * * Handles withdrawal status events from blockchain-service. * Updates withdrawal order status and handles fund refunds on failure. */ @Injectable() export class WithdrawalStatusHandler implements OnModuleInit { private readonly logger = new Logger(WithdrawalStatusHandler.name); constructor( private readonly withdrawalEventConsumer: WithdrawalEventConsumerService, @Inject(WITHDRAWAL_ORDER_REPOSITORY) private readonly withdrawalRepo: IWithdrawalOrderRepository, @Inject(WALLET_ACCOUNT_REPOSITORY) private readonly walletRepo: IWalletAccountRepository, ) {} onModuleInit() { this.withdrawalEventConsumer.onWithdrawalConfirmed( this.handleWithdrawalConfirmed.bind(this), ); this.withdrawalEventConsumer.onWithdrawalFailed( this.handleWithdrawalFailed.bind(this), ); this.logger.log(`[INIT] WithdrawalStatusHandler registered`); } /** * Handle withdrawal confirmed event * Update order status to CONFIRMED and store txHash */ private async handleWithdrawalConfirmed( payload: WithdrawalConfirmedPayload, ): Promise { this.logger.log(`[CONFIRMED] Processing withdrawal confirmation`); this.logger.log(`[CONFIRMED] orderNo: ${payload.orderNo}`); this.logger.log(`[CONFIRMED] txHash: ${payload.txHash}`); try { // Find the withdrawal order const order = await this.withdrawalRepo.findByOrderNo(payload.orderNo); if (!order) { this.logger.error(`[CONFIRMED] Order not found: ${payload.orderNo}`); return; } // Update order status: FROZEN -> BROADCASTED -> CONFIRMED // If still FROZEN, first mark as broadcasted with txHash if (order.isFrozen) { order.markAsBroadcasted(payload.txHash); } // Then mark as confirmed if (order.isBroadcasted) { order.markAsConfirmed(); } await this.withdrawalRepo.save(order); this.logger.log(`[CONFIRMED] Order ${payload.orderNo} confirmed successfully`); } catch (error) { this.logger.error(`[CONFIRMED] Failed to process confirmation for ${payload.orderNo}`, error); throw error; } } /** * Handle withdrawal failed event * Update order status to FAILED and refund frozen funds */ private async handleWithdrawalFailed( payload: WithdrawalFailedPayload, ): Promise { this.logger.log(`[FAILED] Processing withdrawal failure`); this.logger.log(`[FAILED] orderNo: ${payload.orderNo}`); this.logger.log(`[FAILED] error: ${payload.error}`); try { // Find the withdrawal order const order = await this.withdrawalRepo.findByOrderNo(payload.orderNo); if (!order) { this.logger.error(`[FAILED] Order not found: ${payload.orderNo}`); return; } // Mark order as failed order.markAsFailed(payload.error); await this.withdrawalRepo.save(order); // Refund frozen funds back to available balance if needed if (order.needsUnfreeze()) { const wallet = await this.walletRepo.findByUserId(order.userId.value); if (wallet) { // Unfreeze the amount (add back to available balance) wallet.unfreeze(order.amount); await this.walletRepo.save(wallet); this.logger.log(`[FAILED] Refunded ${order.amount.value} USDT to user ${order.userId}`); } } this.logger.log(`[FAILED] Order ${payload.orderNo} marked as failed`); } catch (error) { this.logger.error(`[FAILED] Failed to process failure for ${payload.orderNo}`, error); throw error; } } }