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:
parent
be09a8beac
commit
9bc7bb1200
|
|
@ -177,6 +177,8 @@ services:
|
||||||
- KAFKA_BROKERS=kafka:29092
|
- KAFKA_BROKERS=kafka:29092
|
||||||
- KAFKA_CLIENT_ID=wallet-service
|
- KAFKA_CLIENT_ID=wallet-service
|
||||||
- KAFKA_GROUP_ID=wallet-service-group
|
- KAFKA_GROUP_ID=wallet-service-group
|
||||||
|
# Identity Service - 用于短信验证和密码验证
|
||||||
|
- IDENTITY_SERVICE_URL=http://identity-service:3000
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
|
||||||
|
|
@ -1253,14 +1253,14 @@ export class WalletApplicationService {
|
||||||
// =============== Withdrawal ===============
|
// =============== 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 userId = BigInt(command.userId);
|
||||||
const amount = Money.USDT(command.amount);
|
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);
|
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) {
|
if (command.amount < this.MIN_WITHDRAWAL_AMOUNT) {
|
||||||
|
|
@ -1303,7 +1305,7 @@ export class WalletApplicationService {
|
||||||
// 验证余额是否足够
|
// 验证余额是否足够
|
||||||
if (wallet.balances.usdt.available.lessThan(totalRequired)) {
|
if (wallet.balances.usdt.available.lessThan(totalRequired)) {
|
||||||
throw new BadRequestException(
|
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'),
|
amount: Money.signed(-totalRequired.value, 'USDT'),
|
||||||
balanceAfter: wallet.balances.usdt.available,
|
balanceAfter: wallet.balances.usdt.available,
|
||||||
refOrderId: savedOrder.orderNo,
|
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);
|
await this.ledgerRepo.save(freezeEntry);
|
||||||
|
|
||||||
|
|
@ -1344,8 +1346,8 @@ export class WalletApplicationService {
|
||||||
userId: userId.toString(),
|
userId: userId.toString(),
|
||||||
walletId: wallet.walletId.toString(),
|
walletId: wallet.walletId.toString(),
|
||||||
amount: command.amount.toString(),
|
amount: command.amount.toString(),
|
||||||
fee: this.WITHDRAWAL_FEE.toString(),
|
fee: feeAmount.toString(),
|
||||||
netAmount: (command.amount - this.WITHDRAWAL_FEE).toString(),
|
netAmount: (command.amount - feeAmount).toString(),
|
||||||
assetType: 'USDT',
|
assetType: 'USDT',
|
||||||
chainType: command.chainType,
|
chainType: command.chainType,
|
||||||
toAddress: command.toAddress,
|
toAddress: command.toAddress,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export class IdentityClientService {
|
||||||
private readonly httpClient: AxiosInstance;
|
private readonly httpClient: AxiosInstance;
|
||||||
|
|
||||||
constructor(private readonly configService: ConfigService) {
|
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({
|
this.httpClient = axios.create({
|
||||||
baseURL: baseUrl,
|
baseURL: baseUrl,
|
||||||
|
|
@ -106,7 +106,7 @@ export class IdentityClientService {
|
||||||
this.logger.log(`发送提取验证短信: userId=${userId}`);
|
this.logger.log(`发送提取验证短信: userId=${userId}`);
|
||||||
|
|
||||||
await this.httpClient.post(
|
await this.httpClient.post(
|
||||||
'/sms/send-withdraw-code',
|
'/user/sms/send-withdraw-code',
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -141,7 +141,7 @@ export class IdentityClientService {
|
||||||
this.logger.log(`验证提取短信验证码: userId=${userId}`);
|
this.logger.log(`验证提取短信验证码: userId=${userId}`);
|
||||||
|
|
||||||
const response = await this.httpClient.post(
|
const response = await this.httpClient.post(
|
||||||
'/sms/verify-withdraw-code',
|
'/user/sms/verify-withdraw-code',
|
||||||
{ code: smsCode },
|
{ code: smsCode },
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -183,7 +183,7 @@ export class IdentityClientService {
|
||||||
this.logger.log(`验证登录密码: userId=${userId}`);
|
this.logger.log(`验证登录密码: userId=${userId}`);
|
||||||
|
|
||||||
const response = await this.httpClient.post(
|
const response = await this.httpClient.post(
|
||||||
'/verify-password',
|
'/user/verify-password',
|
||||||
{ password },
|
{ password },
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class _WithdrawConfirmPageState extends ConsumerState<WithdrawConfirmPage> {
|
||||||
String? _maskedPhoneNumber;
|
String? _maskedPhoneNumber;
|
||||||
|
|
||||||
/// 手续费率
|
/// 手续费率
|
||||||
final double _feeRate = 0.001;
|
final double _feeRate = 0.001; // 0.1%
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -465,7 +465,7 @@ class _WithdrawConfirmPageState extends ConsumerState<WithdrawConfirmPage> {
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildDetailRow('提取网络', _getNetworkName(widget.params.network)),
|
_buildDetailRow('提取网络', _getNetworkName(widget.params.network)),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildDetailRow('接收地址', _formatAddress(widget.params.address)),
|
_buildDetailRow('接收账号', _formatAddress(widget.params.address)),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildDetailRow('提取数量', '${widget.params.amount.toStringAsFixed(2)} 绿积分'),
|
_buildDetailRow('提取数量', '${widget.params.amount.toStringAsFixed(2)} 绿积分'),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class _WithdrawUsdtPageState extends ConsumerState<WithdrawUsdtPage> {
|
||||||
final double _feeRate = 0.001; // 0.1%
|
final double _feeRate = 0.001; // 0.1%
|
||||||
|
|
||||||
/// 最小提款金额
|
/// 最小提款金额
|
||||||
final double _minAmount = 10.0;
|
final double _minAmount = 100.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -181,7 +181,7 @@ class _WithdrawUsdtPageState extends ConsumerState<WithdrawUsdtPage> {
|
||||||
|
|
||||||
// 验证地址
|
// 验证地址
|
||||||
if (address.isEmpty) {
|
if (address.isEmpty) {
|
||||||
_showErrorSnackBar('请输入接收地址');
|
_showErrorSnackBar('请输入接收账号');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -331,7 +331,7 @@ class _WithdrawUsdtPageState extends ConsumerState<WithdrawUsdtPage> {
|
||||||
String _getNetworkDescription(WithdrawNetwork network) {
|
String _getNetworkDescription(WithdrawNetwork network) {
|
||||||
switch (network) {
|
switch (network) {
|
||||||
case WithdrawNetwork.kava:
|
case WithdrawNetwork.kava:
|
||||||
return 'Kava EVM 网络';
|
return 'Kava安全网络';
|
||||||
case WithdrawNetwork.bsc:
|
case WithdrawNetwork.bsc:
|
||||||
return 'BNB Smart Chain 网络';
|
return 'BNB Smart Chain 网络';
|
||||||
}
|
}
|
||||||
|
|
@ -674,7 +674,7 @@ class _WithdrawUsdtPageState extends ConsumerState<WithdrawUsdtPage> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'接收地址',
|
'接收账号',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontFamily: 'Inter',
|
fontFamily: 'Inter',
|
||||||
|
|
@ -966,7 +966,7 @@ class _WithdrawUsdtPageState extends ConsumerState<WithdrawUsdtPage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildNoticeItem('请确保接收地址正确,错误地址将导致资产丢失'),
|
_buildNoticeItem('请确保接收账号正确,错误账号将导致资产丢失'),
|
||||||
_buildNoticeItem('请选择正确的网络,不同网络之间不可互转'),
|
_buildNoticeItem('请选择正确的网络,不同网络之间不可互转'),
|
||||||
_buildNoticeItem('提取需要进行手机短信验证'),
|
_buildNoticeItem('提取需要进行手机短信验证'),
|
||||||
_buildNoticeItem('提取通常在 1-30 分钟内到账'),
|
_buildNoticeItem('提取通常在 1-30 分钟内到账'),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue