rwadurian/backend/services/wallet-service/src/application/event-handlers/withdrawal-status.handler.ts

122 lines
3.9 KiB
TypeScript

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<void> {
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<void> {
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;
}
}
}