122 lines
3.9 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
}
|