hyapp-server/docs/flutter对接/币商列表Flutter对接.md
2026-05-26 03:40:01 +08:00

150 lines
4.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 币商列表 Flutter 对接
本文描述 Flutter App 获取当前用户所在区域启用币商列表的接口。币商身份、状态和区域过滤由 `user-service` 维护App 只展示 gateway 返回的列表,不传 `region_id`,也不在本地缓存为跨区域通用数据。
## 接口
`GET /api/v1/wallet/coin-sellers`
登录接口,需要 `Authorization: Bearer <access_token>`。接口按当前登录用户的 `users.region_id` 查询同区域、状态为 `active`、资产类型为 `COIN_SELLER_COIN` 的币商。
请求头:
| Header | 必填 | 说明 |
| --- | --- | --- |
| `Authorization` | 是 | App 登录 access token。 |
| `X-App-Code` | 否 | App 编码,默认 `lalu`。 |
| `X-Request-ID` | 否 | 客户端链路 ID服务端也会生成并在响应中返回。 |
Query无。不要传 `region_id``country_id``user_id`
示例:
```http
GET /api/v1/wallet/coin-sellers
Authorization: Bearer <access_token>
X-App-Code: lalu
```
成功响应:
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_abc",
"data": {
"items": [
{
"user_id": "801",
"display_user_id": "900801",
"username": "Seller One",
"avatar": "https://cdn.example/avatar.png",
"country_id": 86,
"country_code": "CN",
"country_name": "China",
"country_display_name": "中国",
"region_id": 30,
"region_code": "east-asia",
"region_name": "East Asia",
"status": "active",
"merchant_asset_type": "COIN_SELLER_COIN",
"updated_at_ms": 1700000001000
}
],
"total": 1
}
}
```
字段规则:
| 字段 | 说明 |
| --- | --- |
| `user_id` | 币商用户长 IDJSON 中按字符串返回,避免 Dart/JS 大整数精度问题。 |
| `display_user_id` | 当前展示短号,可用于转账页展示和输入确认。 |
| `username` | 币商昵称;为空时客户端可展示短号。 |
| `avatar` | 币商头像 URL为空时展示默认头像。 |
| `country_id` | 币商用户当前国家 ID。 |
| `country_code` | 国家编码,例如 `CN`。 |
| `country_name` | 英文或标准国家名。 |
| `country_display_name` | 客户端展示优先使用的国家名。 |
| `region_id` | 币商用户当前区域 ID应与当前用户区域一致。 |
| `status` | 当前只会返回 `active`。 |
| `merchant_asset_type` | 当前固定为 `COIN_SELLER_COIN`。 |
| `updated_at_ms` | 币商身份最近更新时间Unix epoch milliseconds。 |
## Flutter 解析示例
```dart
class CoinSellerList {
CoinSellerList({
required this.items,
required this.total,
});
final List<CoinSeller> items;
final int total;
factory CoinSellerList.fromJson(Map<String, dynamic> json) {
return CoinSellerList(
items: (json['items'] as List<dynamic>? ?? const [])
.map((item) => CoinSeller.fromJson(item as Map<String, dynamic>))
.toList(),
total: (json['total'] as num?)?.toInt() ?? 0,
);
}
}
class CoinSeller {
CoinSeller({
required this.userId,
required this.displayUserId,
required this.username,
required this.avatar,
required this.countryId,
required this.countryDisplayName,
required this.regionId,
required this.status,
required this.merchantAssetType,
required this.updatedAtMs,
});
final String userId;
final String displayUserId;
final String username;
final String avatar;
final int countryId;
final String countryDisplayName;
final int regionId;
final String status;
final String merchantAssetType;
final int updatedAtMs;
String get title => username.isNotEmpty ? username : displayUserId;
factory CoinSeller.fromJson(Map<String, dynamic> json) {
return CoinSeller(
userId: json['user_id'] as String? ?? '',
displayUserId: json['display_user_id'] as String? ?? '',
username: json['username'] as String? ?? '',
avatar: json['avatar'] as String? ?? '',
countryId: (json['country_id'] as num?)?.toInt() ?? 0,
countryDisplayName: json['country_display_name'] as String? ?? '',
regionId: (json['region_id'] as num?)?.toInt() ?? 0,
status: json['status'] as String? ?? '',
merchantAssetType: json['merchant_asset_type'] as String? ?? '',
updatedAtMs: (json['updated_at_ms'] as num?)?.toInt() ?? 0,
);
}
}
```
## 客户端处理
1. 先解析外层 envelope只有 `code == "OK"` 时读取 `data.items`
2. 空列表表示当前区域暂无启用币商;展示空状态,不切换到其他区域。
3. 点击币商进入转账或联系流程时,使用 `display_user_id` 做用户可见确认,实际后续接口仍以服务端解析结果为准。
4. 用户切换国家或完成资料变更后重新拉取本接口,不复用旧区域列表。
5. 请求失败时记录 `request_id`,按登录失效、参数错误、服务错误分别处理。