fix(withdraw): 修复提取功能短信验证和手续费计算

- 修复 wallet-service 调用 identity-service 的 API 路径(添加 /user 前缀)
- 修复 identity-client 默认端口从 3001 改为 3000
- 添加 docker-compose 中 IDENTITY_SERVICE_URL 环境变量配置
- 手续费改为按 0.1% 费率动态计算(前后端统一)
- 最小提取金额从 10 改为 100
- 文案修改:Kava EVM 网络 → Kava安全网络,接收地址 → 接收账号

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-22 03:14:22 -08:00
parent be09a8beac
commit 9bc7bb1200
5 changed files with 24 additions and 20 deletions

View File

@ -177,6 +177,8 @@ services:
- KAFKA_BROKERS=kafka:29092
- KAFKA_CLIENT_ID=wallet-service
- KAFKA_GROUP_ID=wallet-service-group
# Identity Service - 用于短信验证和密码验证
- IDENTITY_SERVICE_URL=http://identity-service:3000
depends_on:
postgres:
condition: service_healthy

View File

@ -1253,14 +1253,14 @@ export class WalletApplicationService {
// =============== Withdrawal ===============
/**
* ()
* (0.1%)
*/
private readonly WITHDRAWAL_FEE = 1; // 1 USDT
private readonly WITHDRAWAL_FEE_RATE = 0.001;
/**
*
*/
private readonly MIN_WITHDRAWAL_AMOUNT = 10; // 10 USDT
private readonly MIN_WITHDRAWAL_AMOUNT = 100; // 100 USDT
/**
*
@ -1281,10 +1281,12 @@ export class WalletApplicationService {
}> {
const userId = BigInt(command.userId);
const amount = Money.USDT(command.amount);
const fee = Money.USDT(this.WITHDRAWAL_FEE);
// 计算手续费 = 金额 * 费率
const feeAmount = command.amount * this.WITHDRAWAL_FEE_RATE;
const fee = Money.USDT(feeAmount);
const totalRequired = amount.add(fee);
this.logger.log(`Processing withdrawal request for user ${userId}: ${command.amount} USDT to ${command.toAddress}`);
this.logger.log(`Processing withdrawal request for user ${userId}: ${command.amount} USDT to ${command.toAddress}, fee: ${feeAmount}`);
// 验证最小提现金额
if (command.amount < this.MIN_WITHDRAWAL_AMOUNT) {
@ -1303,7 +1305,7 @@ export class WalletApplicationService {
// 验证余额是否足够
if (wallet.balances.usdt.available.lessThan(totalRequired)) {
throw new BadRequestException(
`余额不足: 需要 ${totalRequired.value} USDT (金额 ${command.amount} + 手续费 ${this.WITHDRAWAL_FEE}), 当前可用 ${wallet.balances.usdt.available.value} USDT`,
`余额不足: 需要 ${totalRequired.value} USDT (金额 ${command.amount} + 手续费 ${feeAmount.toFixed(2)}), 当前可用 ${wallet.balances.usdt.available.value} USDT`,
);
}
@ -1333,7 +1335,7 @@ export class WalletApplicationService {
amount: Money.signed(-totalRequired.value, 'USDT'),
balanceAfter: wallet.balances.usdt.available,
refOrderId: savedOrder.orderNo,
memo: `Withdrawal freeze: ${command.amount} USDT + ${this.WITHDRAWAL_FEE} USDT fee`,
memo: `Withdrawal freeze: ${command.amount} USDT + ${feeAmount.toFixed(2)} USDT fee (${this.WITHDRAWAL_FEE_RATE * 100}%)`,
});
await this.ledgerRepo.save(freezeEntry);
@ -1344,8 +1346,8 @@ export class WalletApplicationService {
userId: userId.toString(),
walletId: wallet.walletId.toString(),
amount: command.amount.toString(),
fee: this.WITHDRAWAL_FEE.toString(),
netAmount: (command.amount - this.WITHDRAWAL_FEE).toString(),
fee: feeAmount.toString(),
netAmount: (command.amount - feeAmount).toString(),
assetType: 'USDT',
chainType: command.chainType,
toAddress: command.toAddress,

View File

@ -12,7 +12,7 @@ export class IdentityClientService {
private readonly httpClient: AxiosInstance;
constructor(private readonly configService: ConfigService) {
const baseUrl = this.configService.get<string>('IDENTITY_SERVICE_URL', 'http://localhost:3001');
const baseUrl = this.configService.get<string>('IDENTITY_SERVICE_URL', 'http://localhost:3000');
this.httpClient = axios.create({
baseURL: baseUrl,
@ -106,7 +106,7 @@ export class IdentityClientService {
this.logger.log(`发送提取验证短信: userId=${userId}`);
await this.httpClient.post(
'/sms/send-withdraw-code',
'/user/sms/send-withdraw-code',
{},
{
headers: {
@ -141,7 +141,7 @@ export class IdentityClientService {
this.logger.log(`验证提取短信验证码: userId=${userId}`);
const response = await this.httpClient.post(
'/sms/verify-withdraw-code',
'/user/sms/verify-withdraw-code',
{ code: smsCode },
{
headers: {
@ -183,7 +183,7 @@ export class IdentityClientService {
this.logger.log(`验证登录密码: userId=${userId}`);
const response = await this.httpClient.post(
'/verify-password',
'/user/verify-password',
{ password },
{
headers: {

View File

@ -51,7 +51,7 @@ class _WithdrawConfirmPageState extends ConsumerState<WithdrawConfirmPage> {
String? _maskedPhoneNumber;
///
final double _feeRate = 0.001;
final double _feeRate = 0.001; // 0.1%
@override
void initState() {
@ -465,7 +465,7 @@ class _WithdrawConfirmPageState extends ConsumerState<WithdrawConfirmPage> {
const SizedBox(height: 16),
_buildDetailRow('提取网络', _getNetworkName(widget.params.network)),
const SizedBox(height: 12),
_buildDetailRow('接收地址', _formatAddress(widget.params.address)),
_buildDetailRow('接收账号', _formatAddress(widget.params.address)),
const SizedBox(height: 12),
_buildDetailRow('提取数量', '${widget.params.amount.toStringAsFixed(2)} 绿积分'),
const SizedBox(height: 12),

View File

@ -56,7 +56,7 @@ class _WithdrawUsdtPageState extends ConsumerState<WithdrawUsdtPage> {
final double _feeRate = 0.001; // 0.1%
///
final double _minAmount = 10.0;
final double _minAmount = 100.0;
@override
void initState() {
@ -181,7 +181,7 @@ class _WithdrawUsdtPageState extends ConsumerState<WithdrawUsdtPage> {
//
if (address.isEmpty) {
_showErrorSnackBar('请输入接收地址');
_showErrorSnackBar('请输入接收账号');
return;
}
@ -331,7 +331,7 @@ class _WithdrawUsdtPageState extends ConsumerState<WithdrawUsdtPage> {
String _getNetworkDescription(WithdrawNetwork network) {
switch (network) {
case WithdrawNetwork.kava:
return 'Kava EVM 网络';
return 'Kava安全网络';
case WithdrawNetwork.bsc:
return 'BNB Smart Chain 网络';
}
@ -674,7 +674,7 @@ class _WithdrawUsdtPageState extends ConsumerState<WithdrawUsdtPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'接收地址',
'接收账号',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
@ -966,7 +966,7 @@ class _WithdrawUsdtPageState extends ConsumerState<WithdrawUsdtPage> {
],
),
const SizedBox(height: 12),
_buildNoticeItem('请确保接收地址正确,错误地址将导致资产丢失'),
_buildNoticeItem('请确保接收账号正确,错误账号将导致资产丢失'),
_buildNoticeItem('请选择正确的网络,不同网络之间不可互转'),
_buildNoticeItem('提取需要进行手机短信验证'),
_buildNoticeItem('提取通常在 1-30 分钟内到账'),