hyapp-server/docs/flutter对接/金币流水对接.md

138 lines
4.2 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 读取当前登录用户金币流水的接口。金币余额和流水事实由 `wallet-service` 维护;客户端只展示 gateway 返回的分录投影,不在本地推算余额。
## 接口
`GET /api/v1/wallet/coin-transactions`
登录接口,需要 `Authorization: Bearer <access_token>`。接口固定查询当前登录用户的 `COIN` 资产流水,不接受客户端传 `user_id``asset_type`
请求头:
| Header | 必填 | 说明 |
| --- | --- | --- |
| `Authorization` | 是 | App 登录 access token。 |
| `X-App-Code` | 否 | App 编码,默认 `lalu`。 |
Query
| 字段 | 必填 | 默认 | 说明 |
| --- | --- | --- | --- |
| `page` | 否 | `1` | 页码,从 1 开始。 |
| `page_size` | 否 | `30` | 每页数量。首屏不传时返回最近 30 条。 |
排序固定为 `created_at_ms DESC, entry_id DESC`,即最新流水在前。分页字段使用通用 `page` / `page_size`,不要使用业务专属分页名。
示例:
```http
GET /api/v1/wallet/coin-transactions?page=1&page_size=30
Authorization: Bearer <access_token>
X-App-Code: lalu
```
成功响应:
```json
{
"code": "OK",
"message": "ok",
"request_id": "req_abc",
"data": {
"items": [
{
"entry_id": 101,
"transaction_id": "wtx_abc",
"biz_type": "coin_seller_transfer",
"asset_type": "COIN",
"available_delta": 3000,
"frozen_delta": 0,
"available_after": 12800,
"frozen_after": 0,
"counterparty_user_id": 90001,
"room_id": "",
"created_at_ms": 1710000000000
}
],
"total": 48,
"page": 1,
"page_size": 30
}
}
```
字段规则:
| 字段 | 说明 |
| --- | --- |
| `available_delta` | 本次可用金币变化;正数是收入,负数是支出。 |
| `available_after` | 本条流水落账后的可用金币余额,余额展示以该字段为准。 |
| `biz_type` | 业务类型,例如 `coin_seller_transfer``gift_debit``task_reward``game_credit``game_debit``game_refund``vip_purchase`。客户端可本地映射展示文案。 |
| `counterparty_user_id` | 对手用户长 ID没有对手方时为 `0`。 |
| `room_id` | 房间相关流水的房间 ID非房间流水为空字符串。 |
| `created_at_ms` | Unix epoch milliseconds展示层按用户本地时区格式化。 |
## Flutter 解析示例
```dart
class CoinTransactionPage {
CoinTransactionPage({
required this.items,
required this.total,
required this.page,
required this.pageSize,
});
final List<CoinTransaction> items;
final int total;
final int page;
final int pageSize;
factory CoinTransactionPage.fromJson(Map<String, dynamic> json) {
return CoinTransactionPage(
items: (json['items'] as List<dynamic>? ?? const [])
.map((item) => CoinTransaction.fromJson(item as Map<String, dynamic>))
.toList(),
total: (json['total'] as num?)?.toInt() ?? 0,
page: (json['page'] as num?)?.toInt() ?? 1,
pageSize: (json['page_size'] as num?)?.toInt() ?? 30,
);
}
}
class CoinTransaction {
CoinTransaction({
required this.entryId,
required this.transactionId,
required this.bizType,
required this.availableDelta,
required this.availableAfter,
required this.createdAtMs,
});
final int entryId;
final String transactionId;
final String bizType;
final int availableDelta;
final int availableAfter;
final int createdAtMs;
bool get isIncome => availableDelta > 0;
int get amount => availableDelta.abs();
factory CoinTransaction.fromJson(Map<String, dynamic> json) {
return CoinTransaction(
entryId: (json['entry_id'] as num).toInt(),
transactionId: json['transaction_id'] as String? ?? '',
bizType: json['biz_type'] as String? ?? '',
availableDelta: (json['available_delta'] as num?)?.toInt() ?? 0,
availableAfter: (json['available_after'] as num?)?.toInt() ?? 0,
createdAtMs: (json['created_at_ms'] as num?)?.toInt() ?? 0,
);
}
}
```
接口调用时,先解析外层 envelope只有 `code == "OK"` 时读取 `data`;其他 `code` 按登录态、参数错误或服务错误处理,并把 `request_id` 记录到客户端日志。