diff --git a/android/.gradle/8.9/checksums/checksums.lock b/android/.gradle/8.9/checksums/checksums.lock index e9a7e47..8a22d9c 100644 Binary files a/android/.gradle/8.9/checksums/checksums.lock and b/android/.gradle/8.9/checksums/checksums.lock differ diff --git a/android/.gradle/8.9/checksums/md5-checksums.bin b/android/.gradle/8.9/checksums/md5-checksums.bin index d5c10cd..b733a7f 100644 Binary files a/android/.gradle/8.9/checksums/md5-checksums.bin and b/android/.gradle/8.9/checksums/md5-checksums.bin differ diff --git a/android/.gradle/8.9/checksums/sha1-checksums.bin b/android/.gradle/8.9/checksums/sha1-checksums.bin index 11f2a50..e190ec1 100644 Binary files a/android/.gradle/8.9/checksums/sha1-checksums.bin and b/android/.gradle/8.9/checksums/sha1-checksums.bin differ diff --git a/android/.gradle/8.9/executionHistory/executionHistory.lock b/android/.gradle/8.9/executionHistory/executionHistory.lock new file mode 100644 index 0000000..9c651a7 Binary files /dev/null and b/android/.gradle/8.9/executionHistory/executionHistory.lock differ diff --git a/docs/README.md b/docs/README.md index 977212e..caa7361 100644 --- a/docs/README.md +++ b/docs/README.md @@ -77,7 +77,7 @@ void main() { ``` 调用层 (原始字段) ↓ -ProxyClient.request() +ProxyClient.request()(自动加 `pkg`;默认加 `User_token`) ↓ 字段映射 (原始 → V2) ↓ @@ -86,6 +86,10 @@ AES 加密 发送请求 ``` +**请求头**:`ProxyClient` 会将 [AppConfig.packageName] 写入映射后的包名字段(原始名 `pkg`);若已设置用户 token,默认还会写入 `User_token`。**`UserApi.fast_login` 等无需登录态接口**内部使用 `includeUserTokenInHeader: false`,避免把旧 token 打进 `filter_type`。其余请求也可在直接调用 `ProxyClient.request` 时传入该参数。 + +**请求体**:各 `*Api` 方法使用与《客户端指南》解密表一致的**原始字段名**(如 `referer`、`deviceId`、`fileUrls` / `contentType` / `content`)。 + ### 3.2 响应流程 ``` @@ -131,9 +135,10 @@ AES 解密 final res = await UserApi.fastLogin( deviceId: '设备ID', sign: 'MD5(deviceId)大写', + app: 'HAndroid', // 必填:HIOS / HAndroid referer: '归因来源', // 可选 ch: '渠道号', // 可选 - type: '类型', // 可选 + type: 'fb', // 可选;未传时默认 fb ); if (res.isSuccess) { @@ -157,9 +162,17 @@ if (res.isSuccess) { #### 5.1.2 getCommonInfo - 获取用户通用信息 ```dart +final config = ApiClient.instance.config; +final backendApp = defaultTargetPlatform == TargetPlatform.iOS + ? config.backendAppTypeIOS + : config.backendAppTypeAndroid; + final res = await UserApi.getCommonInfo( - app: '应用ID', - userId: '用户ID', // 可选 + app: backendApp, // 必填:HIOS / HAndroid 等与后端约定 + pkg: config.packageName, // 必填:包名 + userId: '用户ID', // 可选 + deviceId: '设备ID', // 可选 + // client / ch / inviteBy / clientId 可选 ); if (res.isSuccess) { @@ -173,7 +186,7 @@ if (res.isSuccess) { ```dart final res = await UserApi.getAccount( - app: '应用ID', + app: 'HAndroid', userId: '用户ID', // 可选 ); @@ -191,6 +204,46 @@ if (res.isSuccess) { - `isVip`: 是否 VIP - `freeTimes`: 免费次数 +#### 5.1.4 getAppLanguage - App 语言配置 + +`GET /v1/config/app-language`(需登录) + +```dart +final res = await UserApi.getAppLanguage( + pkg: ApiClient.instance.config.packageName, + lang: 'en', // 可选 +); +``` + +**返回实体**: `AppLanguageConfigResponse`(`config` 为语言配置 JSON 字符串) + +#### 5.1.5 getCreditsPage - 积分分页流水 + +与 `ImageApi.getCreditsPageInfo` 相同,路径 `GET /v1/user/credits-page`。 + +```dart +final res = await UserApi.getCreditsPage(page: '1', size: '10', type: '1'); +``` + +#### 5.1.6 getUserPayments - 用户支付记录 + +```dart +final res = await UserApi.getUserPayments( + app: backendApp, + userId: '用户ID', +); +``` + +**返回实体**: `UserPaymentsListResponse`(`items`: `UserPaymentRecord` 列表) + +#### 5.1.7 getUnreadMessageCount - 未读消息数 + +```dart +final res = await UserApi.getUnreadMessageCount(types: '类型列表'); // types 可选 +``` + +**返回实体**: `UnreadMessageCountResponse` + --- ### 5.2 PaymentApi - 支付相关 @@ -199,7 +252,8 @@ if (res.isSuccess) { ```dart final res = await PaymentApi.getGooglePayActivities( - app: '应用ID', // 可选 + app: 'HAndroid', // 可选;默认 `AppConfig.backendAppTypeAndroid` + client: '客户端', // 可选 country: '国家', // 可选 ); @@ -226,7 +280,7 @@ if (res.isSuccess) { ```dart final res = await PaymentApi.getPaymentMethods( - activityId: '活动ID', + activityId: 12345, // int,与接口一致 country: '国家', // 可选 ); @@ -252,7 +306,7 @@ if (res.isSuccess) { ```dart final res = await PaymentApi.createPayment( - app: '应用ID', + app: 'HAndroid', // HIOS / HAndroid userId: '用户ID', activityId: '活动ID', paymentMethod: '支付方式', @@ -271,7 +325,24 @@ if (res.isSuccess) { - `payUrl`: 支付链接 - `status`: 状态 -#### 5.2.4 getOrderDetail - 获取订单详情 +#### 5.2.4 getPaymentDetailList - 支付订单列表 + +`GET /v1/payment/getPaymentDetailList` + +```dart +final res = await PaymentApi.getPaymentDetailList( + app: 'HAndroid', + userId: '用户ID', + paymentMethod: 'GOOGLEPAY', // 可选 + filterStatus: 'SUCCESS', // 可选 +); +``` + +**返回实体**: `PaymentOrderListResponse`(`orders`: `PaymentOrderSummary` 列表) + +#### 5.2.5 getOrderDetail - 获取订单详情 + +Query 使用原始字段 `id`(值为订单/支付 ID)。 ```dart final res = await PaymentApi.getOrderDetail( @@ -290,7 +361,7 @@ if (res.isSuccess) { - `status`: 状态 - `amount`: 金额 -#### 5.2.5 googlepay - Google Pay 回调 +#### 5.2.6 googlepay - Google Pay 回调 ```dart final res = await PaymentApi.googlepay( @@ -410,26 +481,29 @@ if (res.isSuccess) { #### 5.3.6 getCreditsPageInfo - 获取积分页面信息 +对应 `GET /v1/user/credits-page`(与 `UserApi.getCreditsPage` 相同)。 + ```dart final res = await ImageApi.getCreditsPageInfo( - app: '应用ID', - userId: '用户ID', // 可选 - ch: '渠道', // 可选 + page: '1', + size: '10', + type: '1', ); +// 或:UserApi.getCreditsPage(page: '1', size: '10', type: '1'); + if (res.isSuccess) { final info = res.data!; - final credits = info.credits; - final freeTimes = info.freeTimes; - final isVip = info.isVip; + final total = info.total; + final records = info.records; + final credits = info.credits; // 若后端仍返回单屏字段则可用 } ``` **返回实体**: `CreditsPageInfoResponse` -- `credits`: 积分 -- `freeTimes`: 免费次数 -- `isVip`: 是否 VIP -- `vipExpireTime`: VIP 过期时间 +- `total` / `current` / `pages` / `size` / `records`: 分页与流水(新接口) +- `credits` / `freeTimes` / `isVip` / `vipExpireTime`: 兼容旧字段 +- `records` 项见 `CreditRecordItem` --- @@ -587,6 +661,7 @@ class AuthService { final res = await UserApi.fastLogin( deviceId: deviceId, sign: sign, + app: 'HAndroid', // 或 AppConfig.backendAppTypeAndroid / IOS referer: 'organic', ); @@ -613,7 +688,7 @@ class PaymentService { } // 2. 获取支付方式 - static Future> getPaymentMethods(String activityId) async { + static Future> getPaymentMethods(int activityId) async { final res = await PaymentApi.getPaymentMethods(activityId: activityId); return res.data?.paymentMethods ?? []; } @@ -625,7 +700,7 @@ class PaymentService { required String paymentMethod, }) async { final res = await PaymentApi.createPayment( - app: ApiClient.instance.config.appId, + app: ApiClient.instance.config.backendAppTypeAndroid, userId: userId, activityId: activityId, paymentMethod: paymentMethod, diff --git a/docs/payment_flow.md b/docs/payment_flow.md index 0de55d2..29025ad 100644 --- a/docs/payment_flow.md +++ b/docs/payment_flow.md @@ -47,15 +47,13 @@ ### 4.1 接口 ```dart -// Android +// Android(app 默认 backendAppTypeAndroid,可传 client) final res = await PaymentApi.getGooglePayActivities( - app: '应用ID', // 可选 country: '国家', // 可选 ); -// iOS +// iOS(app 默认 backendAppTypeIOS) final res = await PaymentApi.getApplePayActivities( - app: '应用ID', // 可选 country: '国家', // 可选 ); ``` @@ -85,7 +83,7 @@ class PaymentProductItem { ```dart final res = await PaymentApi.getPaymentMethods( - activityId: '活动ID', + activityId: 12345, // int country: '国家', // 可选 ); @@ -104,7 +102,7 @@ if (res.isSuccess) { ```dart final res = await PaymentApi.createPayment( - app: '应用ID', + app: 'HAndroid', // HIOS / HAndroid userId: '用户ID', activityId: '活动ID', paymentMethod: '支付方式', @@ -132,7 +130,7 @@ if (res.isSuccess) { ```dart // 1. 创建订单(直接走谷歌支付) final createRes = await PaymentApi.createPayment( - app: '应用ID', + app: 'HAndroid', userId: '用户ID', activityId: '活动ID', paymentMethod: 'GooglePay', @@ -232,6 +230,7 @@ class GooglePayCallbackResponse { | `getApplePayActivities` | GET | `PaymentProductsResponse` | | `getPaymentMethods` | POST | `PaymentMethodsResponse` | | `createPayment` | POST | `CreatePaymentResponse` | +| `getPaymentDetailList` | GET | `PaymentOrderListResponse` | | `getOrderDetail` | GET | `OrderDetailResponse` | | `googlepay` | POST | `GooglePayCallbackResponse` | @@ -250,7 +249,7 @@ class GooglePayCallbackResponse { | 活动 ID | activityId | - | | 支付方式 | paymentMethod | - | | 支付子类型 | paymentType | - | -| 订单 ID | orderId | orderId | +| 订单 / 支付 ID(详情 query) | `id`(Dart 仍用参数名 orderId) | orderId | | 购买签名 | signature | - | | 购买数据 | purchaseData | - | | 商品 ID | - | productId | diff --git a/lib/src/api/proxy_client.dart b/lib/src/api/proxy_client.dart index cabd0d3..23ff287 100644 --- a/lib/src/api/proxy_client.dart +++ b/lib/src/api/proxy_client.dart @@ -102,12 +102,20 @@ class ProxyClient { /// [headers]、[queryParams]、[body] 使用**原始字段名**, /// 框架会按 [AppConfig.fieldMapping] 转为 V2 字段名后发送。 /// 响应 data 会自动从 V2 转回原始字段名。 + /// + /// **请求头(自动注入)** + /// - [AppConfig.packageName] → 原始字段名 `pkg`(经映射后的请求头键,如 `stakeholder`) + /// - 若已 [userToken] 且 [includeUserTokenInHeader] 为 true,则注入 `User_token` + /// + /// 与文档一致:**设备快速登录等无需登录态接口**应传 `includeUserTokenInHeader: false`, + /// 避免历史 token 进入 `filter_type`。 Future request({ required String path, required String method, Map? headers, Map? queryParams, Map? body, + bool includeUserTokenInHeader = true, }) async { final pk = config.proxyKeys; final mapping = config.fieldMapping; @@ -116,7 +124,9 @@ class ProxyClient { if (config.packageName.isNotEmpty) { headersMap[mapping.headerPackageNameField] = config.packageName; } - if (userToken != null && userToken!.isNotEmpty) { + if (includeUserTokenInHeader && + userToken != null && + userToken!.isNotEmpty) { headersMap[mapping.headerUserTokenField] = userToken!; } headersMap = mapping.mapRequest(headersMap); @@ -168,6 +178,8 @@ class ProxyClient { /// /// [headers]、[queryParams]、[body] 使用**原始字段名**。 /// [entityFactory] 用于将映射后的 data 转换为实体对象。 + /// + /// 参见 [request] 的 [includeUserTokenInHeader] 说明。 Future> requestEntity({ required String path, required String method, @@ -175,6 +187,7 @@ class ProxyClient { Map? headers, Map? queryParams, Map? body, + bool includeUserTokenInHeader = true, }) async { final response = await request( path: path, @@ -182,6 +195,7 @@ class ProxyClient { headers: headers, queryParams: queryParams, body: body, + includeUserTokenInHeader: includeUserTokenInHeader, ); if (response.isSuccess && response.data is Map) { diff --git a/lib/src/config/default_field_mapping.dart b/lib/src/config/default_field_mapping.dart index 028f367..3c99a69 100644 --- a/lib/src/config/default_field_mapping.dart +++ b/lib/src/config/default_field_mapping.dart @@ -28,6 +28,9 @@ const FieldMapping petsHeroAIFieldMapping = FieldMapping({ // === 支付 === 'activityId': 'warrior', 'country': 'vambrace', + 'client': 'filter', + 'id': 'timing', + 'filterStatus': 'quest', 'orderId': 'federation', 'payUrl': 'convert', 'productId': 'helm', @@ -51,6 +54,8 @@ const FieldMapping petsHeroAIFieldMapping = FieldMapping({ 'cursor': 'platoon', 'declaration': 'declaration', 'quest': 'quest', + 'imgCount': 'indicator', + 'aspectRatio': 'caption', 'ext': 'nexus', 'imgUrl': 'congregation', 'poseId': 'profit', diff --git a/lib/src/entities/image_entities.dart b/lib/src/entities/image_entities.dart index a28671d..8ff5f26 100644 --- a/lib/src/entities/image_entities.dart +++ b/lib/src/entities/image_entities.dart @@ -368,13 +368,67 @@ class MyTasksResponse extends Entity { }; } -/// 积分页面信息响应 +/// 积分流水单条(`/v1/user/credits-page` 的 records) +class CreditRecordItem extends Entity { + CreditRecordItem({ + this.subBusinessType, + this.credits, + this.createTime, + this.businessId, + this.type, + this.businessType, + }); + + final String? subBusinessType; + final int? credits; + final int? createTime; + final String? businessId; + final int? type; + final String? businessType; + + static int? _toInt(dynamic v) { + if (v == null) return null; + if (v is int) return v; + if (v is num) return v.toInt(); + if (v is String && v.isNotEmpty) return int.tryParse(v); + return null; + } + + @override + factory CreditRecordItem.fromJson(Map json) { + return CreditRecordItem( + subBusinessType: json['subBusinessType'] as String?, + credits: _toInt(json['credits']), + createTime: _toInt(json['createTime']), + businessId: json['businessId'] as String?, + type: _toInt(json['type']), + businessType: json['businessType'] as String?, + ); + } + + @override + Map toJson() => { + 'subBusinessType': subBusinessType, + 'credits': credits, + 'createTime': createTime, + 'businessId': businessId, + 'type': type, + 'businessType': businessType, + }; +} + +/// 积分页面信息响应(兼容旧版单块字段 + `/v1/user/credits-page` 分页结构) class CreditsPageInfoResponse extends Entity { CreditsPageInfoResponse({ this.credits, this.freeTimes, this.vipExpireTime, this.isVip, + this.total, + this.current, + this.pages, + this.size, + this.records, }); final int? credits; @@ -382,13 +436,43 @@ class CreditsPageInfoResponse extends Entity { final String? vipExpireTime; final bool? isVip; + /// 分页:总条数 + final int? total; + + /// 分页:当前页 + final int? current; + + /// 分页:总页数 + final int? pages; + + /// 分页:每页大小 + final int? size; + + final List? records; + + static int? _toInt(dynamic v) { + if (v == null) return null; + if (v is int) return v; + if (v is num) return v.toInt(); + if (v is String && v.isNotEmpty) return int.tryParse(v); + return null; + } + @override factory CreditsPageInfoResponse.fromJson(Map json) { + final recList = json['records'] as List?; return CreditsPageInfoResponse( - credits: json['credits'] as int?, - freeTimes: json['freeTimes'] as int?, + credits: _toInt(json['credits']), + freeTimes: _toInt(json['freeTimes']), vipExpireTime: json['vipExpireTime'] as String?, - isVip: json['isVip'] as bool?, + isVip: json['isVip'] is bool ? json['isVip'] as bool? : null, + total: _toInt(json['total']), + current: _toInt(json['current']), + pages: _toInt(json['pages']), + size: _toInt(json['size']), + records: recList + ?.map((e) => CreditRecordItem.fromJson(e as Map)) + .toList(), ); } @@ -398,5 +482,10 @@ class CreditsPageInfoResponse extends Entity { 'freeTimes': freeTimes, 'vipExpireTime': vipExpireTime, 'isVip': isVip, + 'total': total, + 'current': current, + 'pages': pages, + 'size': size, + 'records': records?.map((e) => e.toJson()).toList(), }; } diff --git a/lib/src/entities/payment_entities.dart b/lib/src/entities/payment_entities.dart index cf65e2a..e05b991 100644 --- a/lib/src/entities/payment_entities.dart +++ b/lib/src/entities/payment_entities.dart @@ -169,10 +169,11 @@ class OrderDetailResponse extends Entity { @override factory OrderDetailResponse.fromJson(Map json) { + final idRaw = json['orderId'] ?? json['id']; return OrderDetailResponse( - orderId: json['orderId'] as String?, + orderId: idRaw == null ? null : idRaw.toString(), status: json['status'] as String?, - amount: json['amount'] as String?, + amount: json['amount'] == null ? null : json['amount'].toString(), ); } @@ -198,8 +199,9 @@ class GooglePayCallbackResponse extends Entity { @override factory GooglePayCallbackResponse.fromJson(Map json) { + final idRaw = json['orderId'] ?? json['id']; return GooglePayCallbackResponse( - orderId: json['orderId'] as String?, + orderId: idRaw == null ? null : idRaw.toString(), status: json['status'] as String?, creditsAdded: json['creditsAdded'] as bool?, ); @@ -212,3 +214,84 @@ class GooglePayCallbackResponse extends Entity { 'creditsAdded': creditsAdded, }; } + +/// `getPaymentDetailList` 返回的订单摘要项 +class PaymentOrderSummary extends Entity { + PaymentOrderSummary({ + this.id, + this.status, + this.currency, + this.count, + this.activityId, + this.addCredits, + this.redirectUrl, + }); + + final String? id; + final String? status; + final String? currency; + final String? count; + final String? activityId; + final bool? addCredits; + final String? redirectUrl; + + static String? _str(dynamic v) { + if (v == null) return null; + if (v is String) return v; + return v.toString(); + } + + @override + factory PaymentOrderSummary.fromJson(Map json) { + return PaymentOrderSummary( + id: _str(json['id']), + status: json['status'] as String?, + currency: json['currency'] as String?, + count: _str(json['count']), + activityId: _str(json['activityId']), + addCredits: json['addCredits'] is bool ? json['addCredits'] as bool? : null, + redirectUrl: json['redirectUrl'] as String?, + ); + } + + @override + Map toJson() => { + 'id': id, + 'status': status, + 'currency': currency, + 'count': count, + 'activityId': activityId, + 'addCredits': addCredits, + 'redirectUrl': redirectUrl, + }; +} + +/// `/v1/payment/getPaymentDetailList` data 为数组时的包装 +class PaymentOrderListResponse extends Entity { + PaymentOrderListResponse({required this.orders}); + + final List orders; + + static PaymentOrderListResponse fromDataList(List list) { + return PaymentOrderListResponse( + orders: [ + for (final e in list) + PaymentOrderSummary.fromJson(e as Map), + ], + ); + } + + @override + factory PaymentOrderListResponse.fromJson(Map json) { + final raw = json['orders'] as List?; + if (raw == null) { + return PaymentOrderListResponse(orders: const []); + } + return PaymentOrderListResponse.fromDataList(raw); + } + + @override + Map toJson() => { + 'orders': orders.map((e) => e.toJson()).toList(), + }; +} diff --git a/lib/src/entities/user_entities.dart b/lib/src/entities/user_entities.dart index 624f7bf..782c3f2 100644 --- a/lib/src/entities/user_entities.dart +++ b/lib/src/entities/user_entities.dart @@ -290,3 +290,167 @@ class AccountResponse extends Entity { 'freeTimes': freeTimes, }; } + +/// `/v1/config/app-language` 响应 data +class AppLanguageConfigResponse extends Entity { + AppLanguageConfigResponse({this.config}); + + /// 语言配置 JSON 字符串 + final String? config; + + @override + factory AppLanguageConfigResponse.fromJson(Map json) { + return AppLanguageConfigResponse( + config: json['config'] as String?, + ); + } + + @override + Map toJson() => {'config': config}; +} + +/// `/v1/user/payments` 单条支付记录 +class UserPaymentRecord extends Entity { + UserPaymentRecord({ + this.amount, + this.payTime, + this.payMethod, + this.currency, + this.payId, + }); + + final int? amount; + final int? payTime; + final String? payMethod; + final String? currency; + final String? payId; + + static int? _toInt(dynamic v) { + if (v == null) return null; + if (v is int) return v; + if (v is num) return v.toInt(); + if (v is String && v.isNotEmpty) return int.tryParse(v); + return null; + } + + @override + factory UserPaymentRecord.fromJson(Map json) { + return UserPaymentRecord( + amount: _toInt(json['amount']), + payTime: _toInt(json['payTime']), + payMethod: json['payMethod'] as String?, + currency: json['currency'] as String?, + payId: json['payId'] as String?, + ); + } + + @override + Map toJson() => { + 'amount': amount, + 'payTime': payTime, + 'payMethod': payMethod, + 'currency': currency, + 'payId': payId, + }; +} + +/// `/v1/user/payments` 列表包装 +class UserPaymentsListResponse extends Entity { + UserPaymentsListResponse({required this.items}); + + final List items; + + static UserPaymentsListResponse fromDataList(List list) { + return UserPaymentsListResponse( + items: [ + for (final e in list) + UserPaymentRecord.fromJson(e as Map), + ], + ); + } + + @override + factory UserPaymentsListResponse.fromJson(Map json) { + final raw = json['items'] as List?; + if (raw == null) { + return UserPaymentsListResponse(items: const []); + } + return UserPaymentsListResponse.fromDataList(raw); + } + + @override + Map toJson() => { + 'items': items.map((e) => e.toJson()).toList(), + }; +} + +/// 未读数按类型 +class UnreadCountItem extends Entity { + UnreadCountItem({this.count, this.type}); + + final int? count; + final String? type; + + static int? _toInt(dynamic v) { + if (v == null) return null; + if (v is int) return v; + if (v is num) return v.toInt(); + if (v is String && v.isNotEmpty) return int.tryParse(v); + return null; + } + + @override + factory UnreadCountItem.fromJson(Map json) { + return UnreadCountItem( + count: _toInt(json['count']), + type: json['type'] as String?, + ); + } + + @override + Map toJson() => { + 'count': count, + 'type': type, + }; +} + +/// `/v1/user/unread-message-count` 响应 data +class UnreadMessageCountResponse extends Entity { + UnreadMessageCountResponse({ + this.unreadCountList, + this.totalUnreadCount, + this.lastUpdateTime, + }); + + final List? unreadCountList; + final int? totalUnreadCount; + final int? lastUpdateTime; + + static int? _toInt(dynamic v) { + if (v == null) return null; + if (v is int) return v; + if (v is num) return v.toInt(); + if (v is String && v.isNotEmpty) return int.tryParse(v); + return null; + } + + @override + factory UnreadMessageCountResponse.fromJson(Map json) { + final raw = json['unreadCountList'] as List?; + return UnreadMessageCountResponse( + unreadCountList: raw + ?.map((e) => UnreadCountItem.fromJson(e as Map)) + .toList(), + totalUnreadCount: _toInt(json['totalUnreadCount']), + lastUpdateTime: _toInt(json['lastUpdateTime']), + ); + } + + @override + Map toJson() => { + 'unreadCountList': + unreadCountList?.map((e) => e.toJson()).toList(), + 'totalUnreadCount': totalUnreadCount, + 'lastUpdateTime': lastUpdateTime, + }; +} diff --git a/lib/src/services/auth_service.dart b/lib/src/services/auth_service.dart index 4ccc019..105cb90 100644 --- a/lib/src/services/auth_service.dart +++ b/lib/src/services/auth_service.dart @@ -201,6 +201,9 @@ abstract class FrameworkAuthService { if (uid.isEmpty) return; final config = ApiClient.instance.config; + final backendApp = defaultTargetPlatform == TargetPlatform.iOS + ? config.backendAppTypeIOS + : config.backendAppTypeAndroid; // 上报 Adjust 归因 final adjustReferer = await AttributionService.getAdjustReferrer(); @@ -210,7 +213,7 @@ abstract class FrameworkAuthService { : 'android_adjust'; try { final rAdjust = await UserApi.referrer( - app: config.appId, + app: backendApp, userId: uid, referer: adjustReferer, deviceId: deviceId, @@ -232,7 +235,7 @@ abstract class FrameworkAuthService { if (playReferrer != null && playReferrer.isNotEmpty) { try { final rGg = await UserApi.referrer( - app: config.appId, + app: backendApp, userId: uid, referer: playReferrer, deviceId: deviceId, @@ -252,8 +255,10 @@ abstract class FrameworkAuthService { // 获取通用信息 try { final commonRes = await UserApi.getCommonInfo( - app: config.appId, + app: backendApp, + pkg: config.packageName, userId: uid, + deviceId: deviceId, ); if (commonRes.isSuccess && commonRes.data != null) { _callbacks?.onCommonInfoLoaded(commonRes.data!); diff --git a/lib/src/services/feedback_api.dart b/lib/src/services/feedback_api.dart index 2a458d8..797b955 100644 --- a/lib/src/services/feedback_api.dart +++ b/lib/src/services/feedback_api.dart @@ -3,6 +3,8 @@ import '../api/proxy_client.dart'; import '../entities/feedback_entities.dart'; /// 举报/反馈相关 API(使用原始字段名) +/// +/// **Body**(`submit`):`fileUrls`、`contentType`、`content`(与文档顺序一致)。 abstract final class FeedbackApi { static ProxyClient get _client => ApiClient.instance.proxy; @@ -31,8 +33,8 @@ abstract final class FeedbackApi { entityFactory: SubmitFeedbackResponse.fromJson, body: { 'fileUrls': fileUrls, - 'content': content, 'contentType': contentType, + 'content': content, }, ); } diff --git a/lib/src/services/image_api.dart b/lib/src/services/image_api.dart index 2d005df..d0e32cf 100644 --- a/lib/src/services/image_api.dart +++ b/lib/src/services/image_api.dart @@ -3,6 +3,8 @@ import '../api/proxy_client.dart'; import '../entities/image_entities.dart'; /// 图片/视频生成相关 API(使用原始字段名) +/// +/// **请求头**:需登录接口自动附带 `pkg` 与 `User_token`(参见 [ProxyClient.request])。 abstract final class ImageApi { static ProxyClient get _client => ApiClient.instance.proxy; @@ -47,13 +49,20 @@ abstract final class ImageApi { } /// 创建文生图任务 + /// + /// **Body**(原始字段):`imgCount`(默认 1)、`aspectRatio`(可选)、`prompt`(必填)。 static Future> createTxt2Img({ required String app, required String prompt, String? ch, String? userId, + int imgCount = 1, + String? aspectRatio, + + /// 兼容旧调用,等价于 [aspectRatio]。 String? quest, }) async { + final ratio = aspectRatio ?? quest; return _client.requestEntity( path: '/v1/image/txt2img_create', method: 'POST', @@ -64,8 +73,8 @@ abstract final class ImageApi { if (userId != null) 'userId': userId, }, body: { - 'declaration': 1, - if (quest != null) 'quest': quest, + 'imgCount': imgCount, + if (ratio != null && ratio.isNotEmpty) 'aspectRatio': ratio, 'prompt': prompt, }, ); @@ -209,21 +218,27 @@ abstract final class ImageApi { ); } - /// 获取积分页面信息 + /// 获取积分页面信息(与 `UserApi.getCreditsPage` 相同,走 `GET /v1/user/credits-page`) + /// + /// 保留此方法以兼容旧调用;[app]、[userId]、[ch] 不再参与请求(接口只吃分页参数)。 static Future> getCreditsPageInfo({ - required String app, + String page = '1', + String size = '10', + String type = '1', + String? app, String? userId, String? ch, }) async { return _client.requestEntity( - path: '/v1/image/getCreditsPageInfo', + path: '/v1/user/credits-page', method: 'GET', entityFactory: CreditsPageInfoResponse.fromJson, queryParams: { - 'app': app, - if (userId != null) 'userId': userId, - if (ch != null) 'ch': ch, + 'page': page, + 'size': size, + 'type': type, }, ); } } + diff --git a/lib/src/services/payment_api.dart b/lib/src/services/payment_api.dart index d793aaf..282c88a 100644 --- a/lib/src/services/payment_api.dart +++ b/lib/src/services/payment_api.dart @@ -3,53 +3,61 @@ import '../api/proxy_client.dart'; import '../entities/payment_entities.dart'; /// 支付相关 API(使用原始字段名) +/// +/// **请求头**:需登录接口自动附带 `pkg` 与 `User_token`。 abstract final class PaymentApi { static ProxyClient get _client => ApiClient.instance.proxy; /// 获取 Google 商品列表(Android) + /// + /// 默认 `app` 为 [AppConfig.backendAppTypeAndroid];可选 [client]、[country]。 static Future> getGooglePayActivities({ String? app, - String? shield, + String? client, String? country, String? pkg, }) async { + final cfg = ApiClient.instance.config; return _client.requestEntity( path: '/v1/payment/getGooglePayActivities', method: 'GET', entityFactory: PaymentProductsResponse.fromJson, queryParams: { - 'app': app ?? ApiClient.instance.config.appId, - 'pkg': pkg ?? ApiClient.instance.config.packageName, - if (shield != null) 'shield': shield, - if (country != null) 'country': country, + 'app': app ?? cfg.backendAppTypeAndroid, + 'pkg': pkg ?? cfg.packageName, + if (client != null && client.isNotEmpty) 'client': client, + if (country != null && country.isNotEmpty) 'country': country, }, ); } /// 获取 Apple 商品列表(iOS) + /// + /// 默认 `app` 为 [AppConfig.backendAppTypeIOS]。 static Future> getApplePayActivities({ String? app, - String? shield, + String? client, String? country, String? pkg, }) async { + final cfg = ApiClient.instance.config; return _client.requestEntity( path: '/v1/payment/getApplePayActivities', method: 'GET', entityFactory: PaymentProductsResponse.fromJson, queryParams: { - 'app': app ?? ApiClient.instance.config.backendAppTypeIOS, - 'pkg': pkg ?? ApiClient.instance.config.packageName, - if (shield != null) 'shield': shield, - if (country != null) 'country': country, + 'app': app ?? cfg.backendAppTypeIOS, + 'pkg': pkg ?? cfg.packageName, + if (client != null && client.isNotEmpty) 'client': client, + if (country != null && country.isNotEmpty) 'country': country, }, ); } - /// 获取支付方式列表 + /// 获取支付方式列表(body 中 [activityId] 为 int,与接口一致) static Future> getPaymentMethods({ - required String activityId, + required int activityId, String? country, }) async { return _client.requestEntity( @@ -64,6 +72,13 @@ abstract final class PaymentApi { } /// 创建支付订单 + /// + /// **Query**:`app`、`userId`。 + /// **Body**:必填字段 + 文档中的可选字段(以下为原始名,非空才写入): + /// `lastName`、`country`、`expireMonth`、`accountName`、`userInfoType`、 + /// `automaticRenewal`、`channel`、`cvcCode`、`channelType`、`firstName`、 + /// `subPaymentMethod`、`phone`、`tgOrderId`、`tgId`、`name`、`expireYear`、 + /// `card`、`status`;另保留换皮用的 `lineage`、`armor`(视 [FieldMapping] 而定)。 static Future> createPayment({ required String app, required String userId, @@ -72,6 +87,24 @@ abstract final class PaymentApi { String? paymentType, String? lineage, String? armor, + String? lastName, + String? country, + String? expireMonth, + String? accountName, + String? userInfoType, + bool? automaticRenewal, + String? channel, + String? cvcCode, + String? channelType, + String? firstName, + String? subPaymentMethod, + String? phone, + String? tgOrderId, + String? tgId, + String? name, + String? expireYear, + String? card, + String? status, }) async { return _client.requestEntity( path: '/v1/payment/createPayment', @@ -85,13 +118,72 @@ abstract final class PaymentApi { 'paymentMethod': paymentMethod, if (paymentType != null && paymentType.isNotEmpty) 'paymentType': paymentType, - if (lineage != null) 'lineage': lineage, - if (armor != null) 'armor': armor, + if (lineage != null && lineage.isNotEmpty) 'lineage': lineage, + if (armor != null && armor.isNotEmpty) 'armor': armor, + if (lastName != null && lastName.isNotEmpty) 'lastName': lastName, + if (country != null && country.isNotEmpty) 'country': country, + if (expireMonth != null && expireMonth.isNotEmpty) + 'expireMonth': expireMonth, + if (accountName != null && accountName.isNotEmpty) + 'accountName': accountName, + if (userInfoType != null && userInfoType.isNotEmpty) + 'userInfoType': userInfoType, + if (automaticRenewal != null) 'automaticRenewal': automaticRenewal, + if (channel != null && channel.isNotEmpty) 'channel': channel, + if (cvcCode != null && cvcCode.isNotEmpty) 'cvcCode': cvcCode, + if (channelType != null && channelType.isNotEmpty) + 'channelType': channelType, + if (firstName != null && firstName.isNotEmpty) 'firstName': firstName, + if (subPaymentMethod != null && subPaymentMethod.isNotEmpty) + 'subPaymentMethod': subPaymentMethod, + if (phone != null && phone.isNotEmpty) 'phone': phone, + if (tgOrderId != null && tgOrderId.isNotEmpty) 'tgOrderId': tgOrderId, + if (tgId != null && tgId.isNotEmpty) 'tgId': tgId, + if (name != null && name.isNotEmpty) 'name': name, + if (expireYear != null && expireYear.isNotEmpty) 'expireYear': expireYear, + if (card != null && card.isNotEmpty) 'card': card, + if (status != null && status.isNotEmpty) 'status': status, }, ); } - /// 获取订单详情 + /// 支付订单列表(data 为数组) + static Future> + getPaymentDetailList({ + required String app, + String? userId, + String? paymentMethod, + String? filterStatus, + }) async { + final response = await _client.request( + path: '/v1/payment/getPaymentDetailList', + method: 'GET', + queryParams: { + 'app': app, + if (userId != null && userId.isNotEmpty) 'userId': userId, + if (paymentMethod != null && paymentMethod.isNotEmpty) + 'paymentMethod': paymentMethod, + if (filterStatus != null && filterStatus.isNotEmpty) + 'filterStatus': filterStatus, + }, + ); + if (response.isSuccess && response.data is List) { + return EntityResponse( + code: response.code, + msg: response.msg, + data: PaymentOrderListResponse.fromDataList( + response.data! as List, + ), + ); + } + return EntityResponse( + code: response.code, + msg: response.msg, + data: null, + ); + } + + /// 获取订单详情(query 使用原始字段 `id` 表示订单/支付 ID) static Future> getOrderDetail({ required String userId, required String orderId, @@ -102,30 +194,35 @@ abstract final class PaymentApi { entityFactory: OrderDetailResponse.fromJson, queryParams: { 'userId': userId, - 'orderId': orderId, + 'id': orderId, }, ); } /// Google 支付结果回调 + /// + /// **Query**:`app`、`userId`。 + /// **Body**(顺序与文档一致):`signature`、`purchaseData`、`id`、`userId`。 static Future> googlepay({ required String signature, required String purchaseData, required String orderId, required String userId, + String? app, }) async { + final cfg = ApiClient.instance.config; return _client.requestEntity( path: '/v1/payment/googlepay', method: 'POST', entityFactory: GooglePayCallbackResponse.fromJson, queryParams: { - 'app': ApiClient.instance.config.appId, + 'app': app ?? cfg.backendAppTypeAndroid, 'userId': userId, }, body: { 'signature': signature, 'purchaseData': purchaseData, - 'orderId': orderId, + 'id': orderId, 'userId': userId, }, ); diff --git a/lib/src/services/user_api.dart b/lib/src/services/user_api.dart index b444bfc..238197f 100644 --- a/lib/src/services/user_api.dart +++ b/lib/src/services/user_api.dart @@ -1,26 +1,30 @@ import '../api/api_client.dart'; import '../api/api_response.dart'; import '../api/proxy_client.dart'; +import '../entities/image_entities.dart'; import '../entities/user_entities.dart'; /// 用户相关 API(使用原始字段名) +/// +/// **请求头**:除 [UserApi.fastLogin] 外,需登录接口均由 [ProxyClient] 自动附带 +/// `pkg`(包名)与 `User_token`(已设置 token 时)。fast_login 仅带 `pkg`,不带 token。 +/// +/// **请求体**:与《客户端指南》一致,使用解密后的**原始字段名**(如 `referer`、`deviceId`)。 abstract final class UserApi { static ProxyClient get _client => ApiClient.instance.proxy; /// 设备快速登录 - /// [deviceId] 设备ID (必填) - /// [sign] 签名,MD5(deviceId)大写32位 (必填) - /// [referer] 归因信息 - /// [ch] 渠道 - /// [type] referrer类型 (af/fb/gg/ios_adjust/android_adjust) - /// [app] 应用类型 (iOS: HIOS / Android: HAndroid) + /// + /// **请求头**:仅 `pkg`(无 `User_token`)。 + /// **Query**:`app`、`type`(默认 `fb`)、`pkg`、`ch`(可选)。 + /// **Body**:`referer`、`sign`、`deviceId`。 static Future> fastLogin({ required String deviceId, required String sign, + required String app, String? referer, String? ch, String? type, - String? app, }) async { final config = ApiClient.instance.config; return _client.requestEntity( @@ -28,20 +32,25 @@ abstract final class UserApi { method: 'POST', entityFactory: FastLoginResponse.fromJson, queryParams: { - if (ch != null) 'ch': ch, + if (ch != null && ch.isNotEmpty) 'ch': ch, 'pkg': config.packageName, - if (type != null) 'type': type, - if (app != null) 'app': app, + 'type': type ?? 'fb', + 'app': app, }, body: { 'referer': referer ?? '', 'sign': sign, 'deviceId': deviceId, }, + includeUserTokenInHeader: false, ); } /// 归因上报 + /// + /// **请求头**:`pkg`、`User_token`。 + /// **Query**:`app`、`userId`、`type`(默认 `fb`)、`pkg`。 + /// **Body**:`referer`、`deviceId`。 static Future referrer({ required String app, required String userId, @@ -56,7 +65,7 @@ abstract final class UserApi { queryParams: { 'app': app, 'userId': userId, - if (type != null) 'type': type, + 'type': type ?? 'fb', 'pkg': pkg ?? ApiClient.instance.config.packageName, }, body: { @@ -67,15 +76,20 @@ abstract final class UserApi { } /// 获取用户通用信息 + /// + /// 与当前接口约定一致(原始 query 字段名,会经 [AppConfig.fieldMapping] 映射): + /// - [app] 必填:应用渠道标识(常见 iOS `HIOS` / Android `HAndroid`,与 fast_login 一致) + /// - [pkg] 必填:应用包名 + /// - 其余可选:[client]、[userId]、[ch]、[inviteBy]、[deviceId]、[clientId] static Future> getCommonInfo({ required String app, - String? shield, + required String pkg, + String? client, String? userId, String? ch, - String? item, + String? inviteBy, String? deviceId, - String? gauntlet, - String? pkg, + String? clientId, }) async { return _client.requestEntity( path: '/v1/user/common_info', @@ -83,13 +97,29 @@ abstract final class UserApi { entityFactory: CommonInfoResponse.fromJson, queryParams: { 'app': app, - if (shield != null) 'shield': shield, - if (userId != null) 'userId': userId, - if (ch != null) 'ch': ch, - if (item != null) 'item': item, - if (deviceId != null) 'deviceId': deviceId, - if (gauntlet != null) 'gauntlet': gauntlet, - if (pkg != null) 'pkg': pkg, + 'pkg': pkg, + if (client != null && client.isNotEmpty) 'client': client, + if (userId != null && userId.isNotEmpty) 'userId': userId, + if (ch != null && ch.isNotEmpty) 'ch': ch, + if (inviteBy != null && inviteBy.isNotEmpty) 'inviteBy': inviteBy, + if (deviceId != null && deviceId.isNotEmpty) 'deviceId': deviceId, + if (clientId != null && clientId.isNotEmpty) 'clientId': clientId, + }, + ); + } + + /// 获取 App 语言配置(需登录态) + static Future> getAppLanguage({ + required String pkg, + String? lang, + }) async { + return _client.requestEntity( + path: '/v1/config/app-language', + method: 'GET', + entityFactory: AppLanguageConfigResponse.fromJson, + queryParams: { + 'pkg': pkg, + if (lang != null && lang.isNotEmpty) 'lang': lang, }, ); } @@ -110,6 +140,68 @@ abstract final class UserApi { ); } + /// 积分分页流水(`GET /v1/user/credits-page`) + static Future> getCreditsPage({ + String page = '1', + String size = '10', + String type = '1', + }) async { + return _client.requestEntity( + path: '/v1/user/credits-page', + method: 'GET', + entityFactory: CreditsPageInfoResponse.fromJson, + queryParams: { + 'page': page, + 'size': size, + 'type': type, + }, + ); + } + + /// 用户支付记录列表(`GET /v1/user/payments`,data 为数组) + static Future> getUserPayments({ + required String app, + String? userId, + }) async { + final response = await _client.request( + path: '/v1/user/payments', + method: 'GET', + queryParams: { + 'app': app, + if (userId != null && userId.isNotEmpty) 'userId': userId, + }, + ); + if (response.isSuccess && response.data is List) { + return EntityResponse( + code: response.code, + msg: response.msg, + data: UserPaymentsListResponse.fromDataList( + response.data! as List, + ), + ); + } + return EntityResponse( + code: response.code, + msg: response.msg, + data: null, + ); + } + + /// 未读消息数(`types` 可为逗号分隔或后端约定的枚举串) + static Future> + getUnreadMessageCount({ + String? types, + }) async { + return _client.requestEntity( + path: '/v1/user/unread-message-count', + method: 'GET', + entityFactory: UnreadMessageCountResponse.fromJson, + queryParams: { + if (types != null && types.isNotEmpty) 'types': types, + }, + ); + } + /// 删除账号 static Future deleteAccount({ required String app,