feat(c2c): 卖单自动获取账户ID和Kava地址,移除手动输入

后端创建卖单时自动从 identity-service 获取卖家 Kava 地址并存入订单,
前端发布页面自动展示 accountSequence(只读),不再需要手动输入1.0系统ID。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-29 11:08:56 -08:00
parent 2b7a30983e
commit 263f1ecf8e
7 changed files with 31 additions and 45 deletions

View File

@ -57,6 +57,8 @@ export class C2cController {
paymentAccount: order.paymentAccount || undefined, paymentAccount: order.paymentAccount || undefined,
paymentQrCode: order.paymentQrCode || undefined, paymentQrCode: order.paymentQrCode || undefined,
paymentRealName: order.paymentRealName || undefined, paymentRealName: order.paymentRealName || undefined,
// 卖家 Kava 地址
sellerKavaAddress: order.sellerKavaAddress || undefined,
// 超时信息 // 超时信息
paymentTimeoutMinutes: order.paymentTimeoutMinutes, paymentTimeoutMinutes: order.paymentTimeoutMinutes,
confirmTimeoutMinutes: order.confirmTimeoutMinutes, confirmTimeoutMinutes: order.confirmTimeoutMinutes,

View File

@ -168,6 +168,8 @@ export class C2cOrderResponseDto {
paymentAccount?: string; paymentAccount?: string;
paymentQrCode?: string; paymentQrCode?: string;
paymentRealName?: string; paymentRealName?: string;
// 卖家 Kava 地址(绿积分转账地址)
sellerKavaAddress?: string;
// 超时信息 // 超时信息
paymentTimeoutMinutes: number; paymentTimeoutMinutes: number;
confirmTimeoutMinutes: number; confirmTimeoutMinutes: number;

View File

@ -3,6 +3,7 @@ import { C2cOrderRepository, C2cOrderEntity } from '../../infrastructure/persist
import { TradingAccountRepository } from '../../infrastructure/persistence/repositories/trading-account.repository'; import { TradingAccountRepository } from '../../infrastructure/persistence/repositories/trading-account.repository';
import { RedisService } from '../../infrastructure/redis/redis.service'; import { RedisService } from '../../infrastructure/redis/redis.service';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service'; import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
import { IdentityClient } from '../../infrastructure/identity/identity.client';
import { Money } from '../../domain/value-objects/money.vo'; import { Money } from '../../domain/value-objects/money.vo';
import Decimal from 'decimal.js'; import Decimal from 'decimal.js';
@ -57,6 +58,7 @@ export class C2cService {
private readonly tradingAccountRepository: TradingAccountRepository, private readonly tradingAccountRepository: TradingAccountRepository,
private readonly redis: RedisService, private readonly redis: RedisService,
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly identityClient: IdentityClient,
) {} ) {}
/** /**
@ -109,16 +111,12 @@ export class C2cService {
throw new BadRequestException('数量必须大于0'); throw new BadRequestException('数量必须大于0');
} }
// 卖单必须提供收款信息 // 卖单:自动获取卖家 Kava 地址(用于绿积分转账)
let sellerKavaAddress: string | null = null;
if (type === C2C_ORDER_TYPE.SELL) { if (type === C2C_ORDER_TYPE.SELL) {
if (!options?.paymentMethod) { sellerKavaAddress = await this.identityClient.getUserKavaAddress(accountSequence);
throw new BadRequestException('卖单必须提供收款方式'); if (!sellerKavaAddress) {
} throw new BadRequestException('未找到您的 Kava 钱包地址,请先绑定钱包');
if (!options?.paymentAccount) {
throw new BadRequestException('卖单必须提供收款账号');
}
if (!options?.paymentRealName) {
throw new BadRequestException('卖单必须提供收款人实名');
} }
} }
@ -169,6 +167,8 @@ export class C2cService {
paymentAccount: options?.paymentAccount, paymentAccount: options?.paymentAccount,
paymentQrCode: options?.paymentQrCode, paymentQrCode: options?.paymentQrCode,
paymentRealName: options?.paymentRealName, paymentRealName: options?.paymentRealName,
// 卖家 Kava 地址(卖单时自动获取)
sellerKavaAddress,
remark: options?.remark, remark: options?.remark,
}); });

View File

@ -111,6 +111,8 @@ export class C2cOrderRepository {
paymentAccount?: string; paymentAccount?: string;
paymentQrCode?: string; paymentQrCode?: string;
paymentRealName?: string; paymentRealName?: string;
// 卖家 Kava 地址
sellerKavaAddress?: string | null;
remark?: string; remark?: string;
}): Promise<C2cOrderEntity> { }): Promise<C2cOrderEntity> {
const record = await this.prisma.c2cOrder.create({ const record = await this.prisma.c2cOrder.create({
@ -132,6 +134,8 @@ export class C2cOrderRepository {
paymentAccount: data.paymentAccount, paymentAccount: data.paymentAccount,
paymentQrCode: data.paymentQrCode, paymentQrCode: data.paymentQrCode,
paymentRealName: data.paymentRealName, paymentRealName: data.paymentRealName,
// 卖家 Kava 地址
sellerKavaAddress: data.sellerKavaAddress,
remark: data.remark, remark: data.remark,
}, },
}); });

View File

@ -108,7 +108,6 @@ abstract class TradingRemoteDataSource {
String? paymentAccount, String? paymentAccount,
String? paymentQrCode, String? paymentQrCode,
String? paymentRealName, String? paymentRealName,
String? systemId, // 1.0ID绿
String? remark, String? remark,
}); });
@ -467,7 +466,6 @@ class TradingRemoteDataSourceImpl implements TradingRemoteDataSource {
String? paymentAccount, String? paymentAccount,
String? paymentQrCode, String? paymentQrCode,
String? paymentRealName, String? paymentRealName,
String? systemId, // 1.0ID绿
String? remark, String? remark,
}) async { }) async {
try { try {
@ -483,7 +481,6 @@ class TradingRemoteDataSourceImpl implements TradingRemoteDataSource {
if (paymentAccount != null) data['paymentAccount'] = paymentAccount; if (paymentAccount != null) data['paymentAccount'] = paymentAccount;
if (paymentQrCode != null) data['paymentQrCode'] = paymentQrCode; if (paymentQrCode != null) data['paymentQrCode'] = paymentQrCode;
if (paymentRealName != null) data['paymentRealName'] = paymentRealName; if (paymentRealName != null) data['paymentRealName'] = paymentRealName;
if (systemId != null && systemId.isNotEmpty) data['systemId'] = systemId;
if (remark != null && remark.isNotEmpty) data['remark'] = remark; if (remark != null && remark.isNotEmpty) data['remark'] = remark;
final response = await client.post( final response = await client.post(

View File

@ -32,7 +32,6 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
Set<String> _selectedPaymentMethods = {'GREEN_POINTS'}; // 绿 Set<String> _selectedPaymentMethods = {'GREEN_POINTS'}; // 绿
final _paymentAccountController = TextEditingController(); final _paymentAccountController = TextEditingController();
final _paymentRealNameController = TextEditingController(); final _paymentRealNameController = TextEditingController();
final _systemIdController = TextEditingController(); // 1.0ID
@override @override
void dispose() { void dispose() {
@ -41,7 +40,6 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
_remarkController.dispose(); _remarkController.dispose();
_paymentAccountController.dispose(); _paymentAccountController.dispose();
_paymentRealNameController.dispose(); _paymentRealNameController.dispose();
_systemIdController.dispose();
super.dispose(); super.dispose();
} }
@ -400,32 +398,27 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// 1.0ID绿 // ID
const Text( const Text(
'1.0系统ID', '账户ID',
style: TextStyle(fontSize: 14, color: _grayText), style: TextStyle(fontSize: 14, color: _grayText),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text( const Text(
'买家将通过此ID向您转账绿积分', '买家将通过此账户向您转账绿积分',
style: TextStyle(fontSize: 12, color: _grayText), style: TextStyle(fontSize: 12, color: _grayText),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
TextField( Container(
controller: _systemIdController, width: double.infinity,
decoration: InputDecoration( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
hintText: '请输入您的1.0系统ID', decoration: BoxDecoration(
hintStyle: const TextStyle(color: _grayText), color: _bgGray,
filled: true, borderRadius: BorderRadius.circular(12),
fillColor: _bgGray, ),
border: OutlineInputBorder( child: Text(
borderRadius: BorderRadius.circular(12), ref.watch(userNotifierProvider).accountSequence ?? '',
borderSide: BorderSide.none, style: const TextStyle(fontSize: 15, color: _darkText),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2),
),
), ),
), ),
@ -675,8 +668,6 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
// //
bool isValid = price > 0 && quantity > 0; bool isValid = price > 0 && quantity > 0;
if (isSell) { if (isSell) {
// 1.0ID绿
isValid = isValid && _systemIdController.text.trim().isNotEmpty;
// //
if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) { if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) {
isValid = isValid && isValid = isValid &&
@ -774,13 +765,6 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
// //
if (isSell) { if (isSell) {
// 1.0ID绿
if (_systemIdController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请填写1.0系统ID'), backgroundColor: _red),
);
return;
}
// //
if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) { if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) {
if (_paymentAccountController.text.trim().isEmpty) { if (_paymentAccountController.text.trim().isEmpty) {
@ -832,7 +816,7 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
const SizedBox(height: 8), const SizedBox(height: 8),
Text('收款方式: $paymentMethodTexts'), Text('收款方式: $paymentMethodTexts'),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('1.0系统ID: ${_systemIdController.text.trim()}'), Text('账户ID: ${ref.read(userNotifierProvider).accountSequence ?? ''}'),
if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) ...[ if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) ...[
const SizedBox(height: 4), const SizedBox(height: 4),
Text('收款账号: ${_paymentAccountController.text.trim()}'), Text('收款账号: ${_paymentAccountController.text.trim()}'),
@ -874,7 +858,6 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
paymentMethod: isSell ? _selectedPaymentMethods.join(',') : null, paymentMethod: isSell ? _selectedPaymentMethods.join(',') : null,
paymentAccount: isSell ? _paymentAccountController.text.trim() : null, paymentAccount: isSell ? _paymentAccountController.text.trim() : null,
paymentRealName: isSell ? _paymentRealNameController.text.trim() : null, paymentRealName: isSell ? _paymentRealNameController.text.trim() : null,
systemId: isSell ? _systemIdController.text.trim() : null,
remark: remark.isEmpty ? null : remark, remark: remark.isEmpty ? null : remark,
); );

View File

@ -87,7 +87,6 @@ class C2cTradingNotifier extends StateNotifier<C2cTradingState> {
String? paymentAccount, String? paymentAccount,
String? paymentQrCode, String? paymentQrCode,
String? paymentRealName, String? paymentRealName,
String? systemId, // 1.0ID绿
String? remark, String? remark,
}) async { }) async {
state = state.copyWith(isLoading: true, clearError: true); state = state.copyWith(isLoading: true, clearError: true);
@ -102,7 +101,6 @@ class C2cTradingNotifier extends StateNotifier<C2cTradingState> {
paymentAccount: paymentAccount, paymentAccount: paymentAccount,
paymentQrCode: paymentQrCode, paymentQrCode: paymentQrCode,
paymentRealName: paymentRealName, paymentRealName: paymentRealName,
systemId: systemId,
remark: remark, remark: remark,
); );
state = state.copyWith(isLoading: false, lastOrder: order); state = state.copyWith(isLoading: false, lastOrder: order);