feat(client): pay 支付领域模型 + PaymentApi + 支付流控制器(轮询 activated)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
This commit is contained in:
wangjia
2026-07-10 23:19:57 +08:00
parent 16b8823f1f
commit e66f79fdf6
7 changed files with 456 additions and 1 deletions
+3 -1
View File
@@ -91,12 +91,14 @@ class ApiClient {
Never _throwFromResponse(http.Response resp) {
String zh = '操作失败 (HTTP ${resp.statusCode})';
String en = 'Request failed (HTTP ${resp.statusCode})';
String? code;
try {
final b = jsonDecode(resp.body) as Map<String, dynamic>;
zh = b['message_zh'] as String? ?? zh;
en = b['message_en'] as String? ?? en;
code = b['code'] as String?;
} catch (_) {}
throw AuthApiException(statusCode: resp.statusCode, messageZh: zh, messageEn: en);
throw AuthApiException(statusCode: resp.statusCode, messageZh: zh, messageEn: en, code: code);
}
void dispose() => _client.close();
+4
View File
@@ -14,12 +14,16 @@ class AuthApiException implements Exception {
required this.statusCode,
required this.messageZh,
required this.messageEn,
this.code,
});
final int statusCode;
final String messageZh;
final String messageEn;
/// 服务端 apierr 的机器码(如 CURRENCY_MISMATCH),旧调用点可空。
final String? code;
@override
String toString() => 'AuthApiException($statusCode): $messageZh';
}
+38
View File
@@ -0,0 +1,38 @@
// payment_api.dart — /v1/pay 代理端点封装(JWT 经 ApiClient 自动注入)。
import '../models/payment.dart';
import 'api_client.dart';
class PaymentApi {
PaymentApi(this._c);
final ApiClient _c;
Future<List<PayCatalogItem>> catalog() async {
final body = await _c.getJson('/v1/pay/catalog');
final items = (body['items'] as List<dynamic>?) ?? const [];
return [for (final it in items) PayCatalogItem.fromJson(it as Map<String, dynamic>)];
}
Future<PayOrder> createOrder({
required String sku,
required String method,
Map<String, String>? metadata,
}) async =>
PayOrder.fromJson(await _c.postJson('/v1/pay/orders', {
'sku': sku,
'method': method,
if (metadata != null && metadata.isNotEmpty) 'metadata': metadata,
}));
Future<PayOrderStatus> orderStatus(String orderNo) async =>
PayOrderStatus.fromJson(await _c.getJson('/v1/pay/orders/$orderNo'));
Future<PayOrder> retry(String orderNo, {required String method, Map<String, String>? metadata}) async =>
PayOrder.fromJson(await _c.postJson('/v1/pay/orders/$orderNo/retry', {
'method': method,
if (metadata != null && metadata.isNotEmpty) 'metadata': metadata,
}));
Future<void> cancel(String orderNo) async {
await _c.postJson('/v1/pay/orders/$orderNo/cancel');
}
}