# 币商列表 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` | 币商用户长 ID，JSON 中按字符串返回，避免 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`，按登录失效、参数错误、服务错误分别处理。
