新增:历史记录对接文档

This commit is contained in:
ivan 2026-04-22 17:24:35 +08:00
parent 8c15058b96
commit fe0e81a5f2
6 changed files with 405 additions and 230 deletions

View File

@ -0,0 +1,92 @@
# Credit Record 需求与实现方案(框架对齐)
本文档独立说明 `Credit Record`(积分流水)模块,避免与生成历史记录(`My History`)说明混排。
---
## 1. 模块目标与范围
`Credit Record` 用于展示用户积分变动流水,支持:
- 首屏加载
- 下拉刷新
- 滚动分页加载Load More
---
## 2. 展示数据规则
每条流水展示:
- **积分变动值**(由 `type + credits` 决定)
- `type == 1`:展示 `+N`
- `type == 2`:展示 `-N`
- 兜底按 `credits` 正负展示
- **日期**`createTime` 格式化为 `yyyy/MM/dd`
空态文案:`No records.`
---
## 3. 接口对接
积分流水接口:
- `UserApi.getCreditsPage`
- 对应后端:`GET /v1/user/credits-page`
- 参数:
- `page`: 页码(从 1 开始)
- `size`: 每页条数(当前 30
---
## 4. 分页加载实现方案
### 4.1 状态字段
建议维护以下分页状态(字段命名由各客户端自行定义):
- `pageSize`(建议 30
- `loading`
- `loadingMore`
- `hasMore`
- `records`
- `lastLoadedPage`
- `listGeneration`(刷新版本号,用于隔离过期请求)
### 4.2 首屏与刷新
1. 进入页面或下拉刷新时请求 `page=1`
2. 清空旧数据并重置分页状态。
3. 成功后覆盖列表并更新 `lastLoadedPage``hasMore`
### 4.3 滚动加载更多
1. 监听滚动位置,接近底部阈值(约 240px触发。
2. 若 `loading/loadingMore/hasMore` 条件不满足则不发起请求。
3. 请求 `nextPage = lastLoadedPage + 1`
4. 成功后 `addAll` 追加并更新分页状态。
### 4.4 hasMore 判定优先级
1. 优先使用后端 `pages/current`
2. 其次使用 `total`
3. 最后兜底 `incoming.length >= pageSize`
---
## 5. 异常与边界处理
- 登录失败:显示错误态 `Sign in failed`
- 接口失败:保留当前列表并停止 `loadingMore`
- 空数据:显示 `No records.`
- 刷新期间旧请求返回:通过 `listGeneration` 丢弃
---
## 6. 验收清单
1. 首屏、下拉刷新、滚动分页三种路径都可稳定工作。
2. 快速滚动不出现重复数据或错序。
3. 接口异常时不导致列表清空闪退。
4. 积分符号展示与 `type` 语义一致。

View File

@ -0,0 +1,142 @@
# 生成历史记录需求与实现方案(框架对齐)
本文档仅面向 `My History`(生成历史记录)模块的产品需求、数据展示规则、接口对接与交互实现说明,供客户端与 `client_proxy_framework` 对齐开发。
---
## 1. 页面目标与范围
`My History` 用于展示用户已创建任务的结果与状态,支持查看、下载、删除、继续追踪进度。
---
## 2. My History 需求定义
### 2.1 展示数据字段(卡片)
每个历史卡片展示以下信息:
- **封面图**(见 2.2 封面取值规则)
- **创建日期**:由 `MyTaskItem.createTime` 格式化为 `MMM d, yyyy`
- **24 小时剩余时间**`createTime + 24h - now`
- 剩余时间大于 0展示 `xh xxm left`
- 小于等于 0展示 `Expired`
- **操作区**
- 支持按客户端配置决定是否在卡片上展示 `Download` 入口(非必选)
- 不展示下载入口时,操作区可仅展示状态文案(由任务状态映射)
- 若客户端采用“详情页下载”方案,则卡片点击进入详情页后再提供下载能力
- **删除按钮**:可触发删除确认弹窗
顶部需展示“有效期提醒”提示(文案可换皮配置,不要求固定英文):
- 语义必须包含两点:
1. 任务内容仅保留 24 小时(或 1 天);
2. 过期前需要下载保存。
- 各换皮应用可按品牌语气自定义标题与正文,但不得改变上述核心含义。
- 示例语义(非固定文案):
- 标题:`24-hour expiry` / `Available for 24 hours`
- 正文:`Each item is kept for 24 hours after creation. Download before it expires.`
### 2.2 封面取值规则(重点)
封面来源按以下优先级:
1. **优先使用 `resultUrl`(网络封面)**
- `resultUrl``http/https` 且为图片地址:直接展示网络图。
2. **若 `resultUrl` 判定为视频地址**(如 `.mp4/.m3u8/.webm/.mov`
- 优先使用本地封面 `localCoverPath`(由 `ImageTaskHistory.localCoverPathsForMyTaskItems(tasks)` 提供)。
- 若本地封面不存在,展示视频占位图(摄像机 icon
3. **若 `resultUrl` 不可用**
- 回退使用 `localCoverPath`
4. **仍无可用封面**
- 展示默认背景色占位块。
### 2.3 接口对接
历史列表接口:
- `ImageApi.getMyTasks`
- 对应后端:`GET /v1/image/my-tasks`
- 当前请求参数:
- `app`: `currentBackendAppType()`
- `page`: `'1'`
- `pageSize`: `'30'`
删除任务接口:
- `ImageApi.deleteTask`
- 入参:`taskId`int
- 成功后前端需从列表移除对应卡片并清理本地封面映射缓存。
任务进度接口(点击生成中任务后轮询):
- `ImageApi.getProgress`
- 对应后端:`GET /v1/image/progress`
### 2.4 点击行为与跳转
卡片点击按任务状态分流:
1. **有远端结果地址**
跳转任务结果详情页(携带 `taskId``resultUrl`)。
2. **任务生成中**
跳转任务进度页,建议每 5 秒轮询一次进度接口,成功后自动进入结果详情页。
3. **状态显示完成但媒体地址未就绪**
提示:`Media is not ready yet. Pull to refresh.`
4. **其余不可进入状态**
根据状态映射给出阻塞提示文案。
辅助交互(支持按客户端策略配置):
- **Download 点击(若卡片启用该入口)**:下载 `resultUrl` 到系统相册(图片/视频分流保存)。
- **详情页下载(若卡片不提供下载入口)**:进入结果详情页后再执行下载。
- **Delete 点击**:先弹窗二次确认,再调用删除接口,成功后移除条目并提示 `Deleted`
---
## 3. My History 分页加载方案(建议落地)
建议客户端按统一分页模式实现 `My History`,支持首屏加载、下拉刷新与滚动加载更多。
### 3.1 增加分页状态
建议维护以下分页状态(命名由各客户端自行定义):
- `pageSize`(建议 30
- `lastLoadedPage`
- `hasMore`
- `loadingMore`
- `listGeneration`(用于刷新隔离)
### 3.2 请求策略
1. **首屏/下拉刷新**:请求第 1 页,覆盖列表。
2. **滚动到底触发**:请求 `nextPage = lastLoadedPage + 1`,并追加数据。
3. **hasMore 判定**:优先使用后端分页字段(若有),否则按返回条数兜底。
4. **封面映射增量更新**:对新增任务批次补齐本地封面映射并合并到当前缓存。
### 3.3 去重与一致性
- 追加分页时按 `taskId` 去重,避免刷新/重试导致重复卡片。
- 删除任务后同步从列表数据与封面缓存中移除。
- 当用户从进度页返回后,可触发轻量刷新(仅首屏页)保障状态最新。
---
## 4. 异常与边界处理
- 登录未完成:展示 loading登录失败展示错误态。
- 接口失败:展示错误文案 + Retry。
- 空数据:`My History` 显示 `No tasks yet.`
- 非法 `taskId`:删除时拦截并提示 `Invalid task id`
- 下载失败:展示 `Save failed` 或系统权限错误。
---
## 5. 验收清单(建议)
1. `My History` 封面优先级符合 2.2(图片、视频、本地回退、占位)。
2. 卡片点击分流准确(结果页/进度页/提示)。
3. 删除成功后 UI 与本地封面缓存同步移除。
4. `My History` 分页补齐后,快滑场景无重复、无错序,且 `hasMore` 判定稳定。

View File

@ -1,298 +1,198 @@
# 支付流程文档 # 支付流程(多客户端通用)
## 1. 概述 ## 1. 概述
本文档描述 Android 项目Flutter的完整支付流程包括商品获取、支付方式选择、订单创建、Google Play 内购以及补单机制。 本文档描述换皮应用可复用的支付全流程,覆盖:商品获取、支付方式选择、订单创建、平台内购回调、订单核销、补单与账户刷新。
文档以“能力与流程”表达,不绑定具体客户端项目、目录路径或某一语言实现。
--- ---
## 2. 支付流程总览 ## 2. 支付流程总览
``` ```text
用户点击 Buy 用户点击充值 / Buy
├─ enableThirdPartyPayment === true 且已登录 ├─ 已登录 且 开启第三方支付
│ │ │ │
│ ├─ getPaymentMethods(activityId) 获取支付方式 │ ├─ 拉取支付方式(基于商品/活动)
│ ├─ 弹窗选择支付方式 │ ├─ 用户选择支付方式
│ ├─ createPayment 创建订单 │ ├─ 创建订单(服务端生成业务订单)
│ │ │ │
│ ├─ 若选中的是 Google Pay │ ├─ 若为平台内购(如 Google Play
│ │ ├─ 调起 Google Play 内购 │ │ ├─ 调起商店购买
│ │ ├─ 拿到 purchaseData + signature │ │ ├─ 获取 purchaseData + signature + storeOrderId
│ │ └─ googlepay 回调验证 │ │ └─ 回调服务端校验与入账
│ │ │ │
│ └─ 否则(其他支付方式) │ └─ 若为外部支付
│ └─ 打开 payUrl 在外部浏览器完成支付 │ └─ 打开 payUrl 在 Web/外部浏览器完成支付
└─ enableThirdPartyPayment !== true 或未登录 └─ 未登录 或 未开启第三方支付
└─ 仅 Android直接调起 Google Play 内购 └─ 可走平台内购直购分支(由业务策略决定)
``` ```
--- ---
## 3. 支付分支依据 ## 3. 支付分支依据(通用)
| 条件 | 说明 | | 条件 | 说明 |
|------|------| |------|------|
| `UserState.enableThirdPartyPayment` | 登录后由 AuthService 从 `/v1/user/common_info` 响应写入 | | 是否登录 | 通常需要用户身份用于订单归属与回调入账 |
| `UserState.userId` | 用户登录后存储的用户 ID | | 是否开启第三方支付 | 由服务端用户配置或渠道策略控制 |
| **第三方支付** | `enableThirdPartyPayment == true``userId` 非空 | | 当前平台能力 | Android / iOS 对内购与外部支付支持差异 |
| **直接谷歌支付** | 其他情况(未开第三方支付或未登录)| | 商品配置 | 某些商品只允许特定支付方式 |
建议将分支判断集中在统一策略层,避免同一逻辑散落在多个页面。
--- ---
## 4. 商品展示与获取 ## 4. 商品展示与获取
### 4.1 接口 ### 4.1 获取时机
```dart - 进入充值页时拉取一次;
// Androidapp 默认 backendAppTypeAndroid可传 client - 下拉刷新或切换国家/渠道后重新拉取;
final res = await PaymentApi.getGooglePayActivities( - 支付成功后建议刷新,确保档位与余额展示一致。
country: '国家', // 可选
);
// iOSapp 默认 backendAppTypeIOS ### 4.2 商品字段(通用语义)
final res = await PaymentApi.getApplePayActivities(
country: '国家', // 可选
);
```
### 4.2 返回实体 | 字段语义 | 说明 |
|------|------|
```dart | `productId` | 商店商品 ID内购必填 |
class PaymentProductsResponse { | `activityId` | 服务端活动/档位 ID建单常用 |
List<PaymentProductItem>? productList; | `actualAmount` | 实付金额 |
} | `originAmount` | 原价(可用于划线价) |
| `bonus` / `bonusCredits` | 赠送积分 |
class PaymentProductItem { | `credits` | 到账积分(如与赠送分开展示) |
String? productId; // 商品 ID (对应 helm) | `title` | 商品文案(可能由服务端直接下发) |
String? activityId; // 活动 ID (对应 warrior)
String? actualAmount; // 实际金额 (对应 guardian)
String? originAmount; // 原价 (对应 curriculum)
int? bonus; // 赠送积分 (对应 forge)
String? title; // 标题 (对应 glossary)
}
```
--- ---
## 5. 第三方支付流程 ## 5. 第三方支付流程(通用)
### 5.1 取支付方式 ### 5.1 拉取支付方式
```dart 以商品或活动为上下文请求支付方式列表,常见字段包括:
final res = await PaymentApi.getPaymentMethods(
activityId: 12345, // int
country: '国家', // 可选
);
if (res.isSuccess) { - `paymentMethod`:主支付方式(例如平台内购、钱包、卡支付等)
final methods = res.data!.paymentMethods; - `subPaymentMethod`:子方式(可选)
// methods 包含: - `name` / `icon`:展示信息
// - paymentMethod: 支付方式 (如 GOOGLEPAY) - `recommend`:是否推荐
// - subPaymentMethod: 子支付方式
// - name: 显示名称
// - icon: 图标 URL
// - recommend: 是否推荐
}
```
### 5.2 创建订单 ### 5.2 创建订单
```dart 创建订单请求通常包含:
final res = await PaymentApi.createPayment(
app: 'HAndroid', // HIOS / HAndroid
userId: '用户ID',
activityId: '活动ID',
paymentMethod: '支付方式',
paymentType: '支付子类型', // 可选
);
if (res.isSuccess) { - 应用标识(`app`
final order = res.data!; - 用户标识(`userId`
final orderId = order.orderId; // 订单 ID - 商品或活动标识(`activityId` / `productId`
final payUrl = order.payUrl; // 支付链接 - 支付方式(`paymentMethod`、可选 `paymentType`
}
```
### 5.3 支付分支 响应通常返回:
- **Google Pay**: 调起内购 → 获取 purchaseData/signature → 调用 `googlepay` 回调 - `orderId`(业务订单号)
- **其他方式**: 打开 `payUrl` 在外部浏览器 - `payUrl`(外部支付链接;仅外部支付场景)
- `status`(订单状态)
- 可选 `federation`(用于与商店订单映射)
### 5.3 分支执行
- **平台内购**:拉起商店购买 -> 取回凭据 -> 回调服务端核验 -> 成功后完成/消费交易。
- **外部支付**:打开 `payUrl` 完成支付 -> 回前台后查询订单状态或拉取账户余额。
--- ---
## 6. 直接谷歌支付流程 ## 6. 平台内购标准链路Android/iOS 可类比)
仅 Android且不经过 `getPaymentMethods``createPayment`(三方支付关闭时): 1. 用 `productId` 调起商店支付;
2. 拿到商店返回的交易凭据(如 `purchaseData``signature``storeOrderId`
3. 调服务端回调接口完成验签与入账;
4. 服务端成功后,客户端执行“完成交易/消费交易”(防止重复拥有问题);
5. 刷新账户余额与订单状态。
```dart > 关键原则:**先服务端验单成功,再本地完成/消费交易**,避免凭据丢失导致资产不一致。
// 1. 创建订单(直接走谷歌支付)
final createRes = await PaymentApi.createPayment(
app: 'HAndroid',
userId: '用户ID',
activityId: '活动ID',
paymentMethod: 'GooglePay',
paymentType: 'GooglePay',
);
// 2. 调起 Google Play 内购
final purchaseResult = await GooglePlayPurchaseService.launchPurchaseAndReturnData(
productId: '商品ID',
);
// 3. 回调验证
if (purchaseResult != null) {
final res = await PaymentApi.googlepay(
signature: purchaseResult.payload.signature,
purchaseData: purchaseResult.payload.purchaseData,
orderId: orderId ?? purchaseResult.orderId,
userId: userId,
);
}
```
--- ---
## 7. Google Play 内购统一入口 ## 7. 补单机制(订单恢复)
### 7.1 调起内购 ### 7.1 触发时机
```dart - 应用启动后;
final result = await GooglePlayPurchaseService.launchPurchaseAndReturnData( - 进入充值页时;
productId: '商品ID', - 支付异常返回时(例如网络中断、回调超时)。
);
if (result != null) { ### 7.2 恢复流程
// result.orderId: Google 订单号
// result.payload.purchaseData: 用于 merchant
// result.payload.signature: 用于 signature
// result.purchaseDetails: PurchaseDetails 对象
}
```
### 7.2 回调验证 1. 拉取未完成/未核销的商店订单(历史订单 + 监听流中的待处理订单);
2. 对每笔订单尝试找到业务订单映射(如 `federation` / `orderId`
```dart 3. 若可映射:再次走服务端回调核验,成功后完成/消费交易;
final res = await PaymentApi.googlepay( 4. 若不可映射:按产品策略处理(仅完成/消费以解除占用,或进入人工排查队列);
signature: result.payload.signature, 5. 恢复结束后刷新账户与充值记录。
purchaseData: result.payload.purchaseData,
orderId: orderId,
userId: userId,
);
if (res.isSuccess) {
// 核销订单
await GooglePlayPurchaseService.completeAndConsumePurchase(
result.purchaseDetails,
);
}
```
### 7.3 响应实体
```dart
class GooglePayCallbackResponse {
String? orderId;
String? status; // SUCCESS / FAILED
bool? creditsAdded; // 是否已加积分
}
```
--- ---
## 8. 补单机制 ## 8. API 能力清单(通用命名)
### 8.1 触发时机 | 能力 | 典型方法 | 说明 |
|------|------|------|
- 进入充值页时调用 `GooglePlayPurchaseService.runOrderRecovery()` | 获取商品列表 | `GET` | Android/iOS 可分端点 |
| 获取支付方式 | `POST`/`GET` | 入参通常含 `activityId` |
### 8.2 补单流程 | 创建订单 | `POST` | 返回 `orderId``payUrl` 等 |
| 平台内购回调 | `POST` | 提交 `purchaseData``signature` 等 |
1. 获取未核销订单: `getUnacknowledgedPurchases()` | 订单列表 | `GET` | 历史支付记录 |
- 合并 `queryPastPurchases``purchaseStream` 的待处理订单 | 订单详情 | `GET` | 查询单笔订单状态 |
2. 对每笔订单:
- 查询本地存储的 `federation` 映射
- 若存在 federation: 调用 `googlepay` 回调 → 成功后 consume
- 若无 federation: 仅执行 consume 解除「已拥有此内容」
3. 补单成功后刷新账户
--- ---
## 9. API 汇总 ## 9. 核心字段对照(通用)
| 接口 | 方法 | 返回实体 | | 业务含义 | 常见请求字段 | 常见响应字段 |
|------|------|----------| |------|------|------|
| `getGooglePayActivities` | GET | `PaymentProductsResponse` | | 应用标识 | `app` | - |
| `getApplePayActivities` | GET | `PaymentProductsResponse` | | 用户标识 | `userId` | - |
| `getPaymentMethods` | POST | `PaymentMethodsResponse` | | 活动/档位 | `activityId` | - |
| `createPayment` | POST | `CreatePaymentResponse` | | 支付方式 | `paymentMethod` | - |
| `getPaymentDetailList` | GET | `PaymentOrderListResponse` | | 子支付方式 | `paymentType` | - |
| `getOrderDetail` | GET | `OrderDetailResponse` | | 订单标识 | `orderId` / `id` | `orderId` |
| `googlepay` | POST | `GooglePayCallbackResponse` | | 购买签名 | `signature` | - |
| 购买数据 | `purchaseData` | - |
| 商品 ID | - | `productId` |
| 实付金额 | - | `actualAmount` |
| 原价 | - | `originAmount` |
| 赠送积分 | - | `bonus` / `bonusCredits` |
| 支付链接 | - | `payUrl` |
| 订单状态 | - | `status` |
--- ---
## 10. 字段映射说明 ## 10. 常见问题
框架自动完成字段映射,调用层使用原始字段名。 ### 10.1 商品未找到
### 请求 → 响应 字段对照 - **现象**:发起内购时提示商品不存在;
- **常见原因**:客户端 `productId` 与商店后台配置不一致,或商品未上架到当前测试轨道;
- **排查建议**:核对包名/应用 ID、商品 ID、地区、测试账号与轨道配置。
| 业务含义 | 请求字段 | 响应字段 | ### 10.2 回调成功但积分未更新
|----------|----------|----------|
| 应用 ID | app | - | - **现象**:支付侧显示成功,账户余额未变;
| 用户 ID | userId | - | - **常见原因**:回调后未刷新账户、订单状态未落库、幂等冲突;
| 活动 ID | activityId | - | - **排查建议**:检查回调响应、订单状态接口与账户刷新链路。
| 支付方式 | paymentMethod | - |
| 支付子类型 | paymentType | - | ### 10.3 重复购买被拦截(已拥有此内容)
| 订单 / 支付 ID详情 query | `id`Dart 仍用参数名 orderId | orderId |
| 购买签名 | signature | - | - **现象**:商店提示已拥有,无法再次购买;
| 购买数据 | purchaseData | - | - **常见原因**:上一笔交易未完成/未消费;
| 商品 ID | - | productId | - **排查建议**:执行补单恢复流程并确保完成/消费逻辑成功。
| 实际金额 | - | actualAmount |
| 原价 | - | originAmount |
| 赠送积分 | - | bonus |
| 支付链接 | - | payUrl |
| 订单状态 | - | status |
--- ---
## 11. 代码文件位置 ## 11. 实施建议(多客户端复用)
| 功能 | 文件路径 | - 以“流程能力层”封装支付,不把分支逻辑写死在页面;
|------|----------| - 页面只关心:档位展示、用户选择、状态反馈;
| 充值页面 | `lib/features/recharge/recharge_screen.dart` | - 建单、回调、补单、轮询统一由支付服务层负责;
| 支付 API | `lib/core/api/services/payment_api.dart` | - 统一错误码与文案映射,避免各端提示不一致;
| Google Play 内购服务 | `lib/features/recharge/google_play_purchase_service.dart` | - 对关键节点埋点:拉起支付、建单成功、回调成功、补单成功、入账成功;
| 支付方式模型 | `lib/features/recharge/models/payment_method_item.dart` | - 保持接口字段可映射,支持不同换皮应用的字段命名差异。
| 商品模型 | `lib/features/recharge/models/activity_item.dart` |
| 购买结果模型 | `lib/features/recharge/models/google_pay_purchase_result.dart` |
| WebView 支付页 | `lib/features/recharge/payment_webview_screen.dart` |
---
## 12. 常见问题
### 12.1 商品未找到
- 原因: 客户端 `productId` 与 Google Play 后台「产品 ID」不一致
- 排查: 检查 Play 后台产品 ID 配置
### 12.2 补单
- 未确认订单可能不会出现在 `queryPastPurchases`
- 应用启动时订阅 `purchaseStream` 接收重新下发
- 补单会合并两者的待处理订单
---
## 13. 注意事项
- 所有 Google Play 内购统一使用 `launchPurchaseAndReturnData()` 方法
- 回调验证成功后必须调用 `completePurchase` + `consumePurchase`
- 支付 URL 打开方式取决于 `createPayment` 返回的 `payUrl` 字段
- 订单状态轮询: 间隔 1/3/7/15/31/63 秒

View File

@ -512,6 +512,31 @@ class CreateTaskResponse extends Entity {
}; };
} }
/// `POST /v1/image/delete-task` `data` FunyMee `hint` / `type`
class DeleteTaskResponse extends Entity {
DeleteTaskResponse({
this.hint,
this.type,
});
final String? hint;
final String? type;
@override
factory DeleteTaskResponse.fromJson(Map<String, dynamic> json) {
return DeleteTaskResponse(
hint: json['hint'] as String?,
type: json['type'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'hint': hint,
'type': type,
};
}
/// ///
/// ///
/// FunyMee `state`线 `bitrate`16 `status` /// FunyMee `state`线 `bitrate`16 `status`

View File

@ -43,14 +43,15 @@ class PaymentProductItem extends Entity {
); );
} }
/// `saturation``bonusCredits``remix``addCredits``contrast``bonus` `credits``baseCredits` /// `saturation``bonusCredits``remix``addCredits` `credits``baseCredits` ****
///
/// 退 [json] `bonus`线 `contrast` [PaymentProductItem.bonus]
/// UI `bonus + bonusCredits`
static int? _parseBonusCredits(Map<String, dynamic> json) { static int? _parseBonusCredits(Map<String, dynamic> json) {
final direct = _intField(json, 'bonusCredits'); final direct = _intField(json, 'bonusCredits');
if (direct != null && direct > 0) return direct; if (direct != null && direct > 0) return direct;
final add = _intField(json, 'addCredits'); final add = _intField(json, 'addCredits');
if (add != null && add > 0) return add; if (add != null && add > 0) return add;
final contrastGift = _intField(json, 'bonus');
if (contrastGift != null && contrastGift > 0) return contrastGift;
final total = _intField(json, 'credits') ?? _intField(json, 'padding'); final total = _intField(json, 'credits') ?? _intField(json, 'padding');
final base = _intField(json, 'baseCredits'); final base = _intField(json, 'baseCredits');
if (total != null && base != null && total > base) return total - base; if (total != null && base != null && total > base) return total - base;

View File

@ -293,6 +293,21 @@ abstract final class ImageApi {
); );
} }
///
///
/// **Body**`taskId`int [FieldMapping] 线 FunyMee `taskId` `exponential`
/// FunyMee `POST /v1/image/delete-task` `User_token`
static Future<EntityResponse<DeleteTaskResponse>> deleteTask({
required int taskId,
}) async {
return _client.requestEntity(
path: '/v1/image/delete-task',
method: 'POST',
entityFactory: DeleteTaskResponse.fromJson,
body: {'taskId': taskId},
);
}
/// ///
static Future<EntityResponse<MyTasksResponse>> getMyTasks({ static Future<EntityResponse<MyTasksResponse>> getMyTasks({
required String app, required String app,