/// 后端 API 服务:dio 封装 + 各支付渠道创建/查询/收银台接口 library; import 'package:dio/dio.dart'; import '../constants.dart'; import '../models/checkout.dart'; import '../models/payment.dart'; /// 统一 API 客户端(单例) class APIServer { APIServer._() : dio = Dio() { dio.options ..baseUrl = kApiBaseUrl ..connectTimeout = const Duration(seconds: 15) ..receiveTimeout = const Duration(seconds: 15) ..responseType = ResponseType.json; } static final APIServer _instance = APIServer._(); /// 获取单例 factory APIServer() => _instance; final Dio dio; /// 后端返回的是否 mock 模式(判断依据:stripe publishable_key 是否为 mock) static bool isMockPublishableKey(String key) => key.startsWith('pk_test_mock'); /// 发起 POST 请求并解析 Future post( String path, T Function(Map json) parse, { Map? body, }) async { final resp = await dio.post>(path, data: body); return parse(resp.data!); } /// 发起 GET 请求并解析 Future get( String path, T Function(Map json) parse, ) async { final resp = await dio.get>(path); return parse(resp.data!); } // ===== 支付创建 ===== /// 创建微信支付(source: app=App支付, web=Native扫码) Future createWechatPayment({ required String productId, String source = 'app', }) { return post( '/v1/payment/wechatpay/', WechatPaymentCreatedResponse.fromJson, body: {'product_id': productId, 'source': source}, ); } /// 创建支付宝支付(source: app=App支付, web=手机网站支付) Future createAlipayPayment({ required String productId, String source = 'app', }) { return post( '/v1/payment/alipay/', OtherPayCreatedReponse.fromJson, body: {'product_id': productId, 'source': source}, ); } /// 创建云闪付支付(source: app=App支付返回 tn 拉起云闪付, web=手机网站跳转) Future createUnionPayPayment({ required String productId, String source = 'app', }) { return post( '/v1/payment/unionpay/', UnionPayCreatedResponse.fromJson, body: {'product_id': productId, 'source': source}, ); } /// 创建 Stripe 支付(PaymentIntent) Future createStripePayment({ required String productId, String source = 'app', String currency = 'cny', }) { return post( '/v1/payment/stripe/', StripePaymentCreatedResponse.fromJson, body: { 'product_id': productId, 'source': source, 'currency': currency, }, ); } /// 创建 Apple Pay 支付(PSP 模式,响应与 Stripe 一致) Future createApplePayment({ required String productId, String source = 'app', String currency = 'usd', }) { return post( '/v1/payment/apple/', StripePaymentCreatedResponse.fromJson, body: { 'product_id': productId, 'source': source, 'currency': currency, }, ); } /// 创建 Google Pay 支付(PSP 模式,响应与 Stripe 一致) Future createGooglePayment({ required String productId, String source = 'app', String currency = 'usd', }) { return post( '/v1/payment/google/', StripePaymentCreatedResponse.fromJson, body: { 'product_id': productId, 'source': source, 'currency': currency, }, ); } // ===== 状态查询 ===== /// 查询支付状态(最终确认 / 轮询) Future queryPaymentStatus(String paymentId) { return get( '/v1/payment/$paymentId', PaymentStatus.fromJson, ); } // ===== 收银台 ===== /// 创建网页收银台会话 Future createCheckoutSession({ required String channel, required String productId, String? returnUrl, }) { return post( '/v1/checkout/session', CheckoutSessionResponse.fromJson, body: { 'channel': channel, 'product_id': productId, 'return_url': returnUrl, }, ); } /// 查询收银台会话状态 Future queryCheckoutStatus(String paymentId) { return get( '/v1/checkout/session/$paymentId', CheckoutSessionStatus.fromJson, ); } // ===== 商品 ===== /// 拉取商品列表 Future fetchProducts() { return get( '/v1/products', PaymentProducts.fromJson, ); } // ===== Mock 模拟成功(仅后端 MOCK_MODE) ===== /// 模拟支付成功(走与真实回调相同的幂等流程) Future simulatePaymentSuccess(String paymentId) async { await dio.post>( '/v1/payment/simulate/success', data: {'payment_id': paymentId}, ); } // ===== 错误解析 ===== /// 将异常统一解析为友好中文提示 String resolveError(Object error) { if (error is DioException) { final data = error.response?.data; if (data is Map) { final err = data['error']; if (err is Map && err['message'] != null) { return '服务端错误:${err['message']}'; } } switch (error.type) { case DioExceptionType.connectionTimeout: case DioExceptionType.receiveTimeout: case DioExceptionType.sendTimeout: return '网络超时,请稍后重试'; case DioExceptionType.connectionError: return '无法连接服务器,请检查网络或后端是否启动'; default: return '请求失败:${error.message}'; } } return error.toString(); } }