api_service.dart 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /// 后端 API 服务:dio 封装 + 各支付渠道创建/查询/收银台接口
  2. library;
  3. import 'package:dio/dio.dart';
  4. import '../constants.dart';
  5. import '../models/checkout.dart';
  6. import '../models/payment.dart';
  7. /// 统一 API 客户端(单例)
  8. class APIServer {
  9. APIServer._() : dio = Dio() {
  10. dio.options
  11. ..baseUrl = kApiBaseUrl
  12. ..connectTimeout = const Duration(seconds: 15)
  13. ..receiveTimeout = const Duration(seconds: 15)
  14. ..responseType = ResponseType.json;
  15. }
  16. static final APIServer _instance = APIServer._();
  17. /// 获取单例
  18. factory APIServer() => _instance;
  19. final Dio dio;
  20. /// 后端返回的是否 mock 模式(判断依据:stripe publishable_key 是否为 mock)
  21. static bool isMockPublishableKey(String key) =>
  22. key.startsWith('pk_test_mock');
  23. /// 发起 POST 请求并解析
  24. Future<T> post<T>(
  25. String path,
  26. T Function(Map<String, dynamic> json) parse, {
  27. Map<String, dynamic>? body,
  28. }) async {
  29. final resp = await dio.post<Map<String, dynamic>>(path, data: body);
  30. return parse(resp.data!);
  31. }
  32. /// 发起 GET 请求并解析
  33. Future<T> get<T>(
  34. String path,
  35. T Function(Map<String, dynamic> json) parse,
  36. ) async {
  37. final resp = await dio.get<Map<String, dynamic>>(path);
  38. return parse(resp.data!);
  39. }
  40. // ===== 支付创建 =====
  41. /// 创建微信支付(source: app=App支付, web=Native扫码)
  42. Future<WechatPaymentCreatedResponse> createWechatPayment({
  43. required String productId,
  44. String source = 'app',
  45. }) {
  46. return post<WechatPaymentCreatedResponse>(
  47. '/v1/payment/wechatpay/',
  48. WechatPaymentCreatedResponse.fromJson,
  49. body: {'product_id': productId, 'source': source},
  50. );
  51. }
  52. /// 创建支付宝支付(source: app=App支付, web=手机网站支付)
  53. Future<OtherPayCreatedReponse> createAlipayPayment({
  54. required String productId,
  55. String source = 'app',
  56. }) {
  57. return post<OtherPayCreatedReponse>(
  58. '/v1/payment/alipay/',
  59. OtherPayCreatedReponse.fromJson,
  60. body: {'product_id': productId, 'source': source},
  61. );
  62. }
  63. /// 创建云闪付支付(source: app=App支付返回 tn 拉起云闪付, web=手机网站跳转)
  64. Future<UnionPayCreatedResponse> createUnionPayPayment({
  65. required String productId,
  66. String source = 'app',
  67. }) {
  68. return post<UnionPayCreatedResponse>(
  69. '/v1/payment/unionpay/',
  70. UnionPayCreatedResponse.fromJson,
  71. body: {'product_id': productId, 'source': source},
  72. );
  73. }
  74. /// 创建 Stripe 支付(PaymentIntent)
  75. Future<StripePaymentCreatedResponse> createStripePayment({
  76. required String productId,
  77. String source = 'app',
  78. String currency = 'cny',
  79. }) {
  80. return post<StripePaymentCreatedResponse>(
  81. '/v1/payment/stripe/',
  82. StripePaymentCreatedResponse.fromJson,
  83. body: {
  84. 'product_id': productId,
  85. 'source': source,
  86. 'currency': currency,
  87. },
  88. );
  89. }
  90. /// 创建 Apple Pay 支付(PSP 模式,响应与 Stripe 一致)
  91. Future<StripePaymentCreatedResponse> createApplePayment({
  92. required String productId,
  93. String source = 'app',
  94. String currency = 'usd',
  95. }) {
  96. return post<StripePaymentCreatedResponse>(
  97. '/v1/payment/apple/',
  98. StripePaymentCreatedResponse.fromJson,
  99. body: {
  100. 'product_id': productId,
  101. 'source': source,
  102. 'currency': currency,
  103. },
  104. );
  105. }
  106. /// 创建 Google Pay 支付(PSP 模式,响应与 Stripe 一致)
  107. Future<StripePaymentCreatedResponse> createGooglePayment({
  108. required String productId,
  109. String source = 'app',
  110. String currency = 'usd',
  111. }) {
  112. return post<StripePaymentCreatedResponse>(
  113. '/v1/payment/google/',
  114. StripePaymentCreatedResponse.fromJson,
  115. body: {
  116. 'product_id': productId,
  117. 'source': source,
  118. 'currency': currency,
  119. },
  120. );
  121. }
  122. // ===== 状态查询 =====
  123. /// 查询支付状态(最终确认 / 轮询)
  124. Future<PaymentStatus> queryPaymentStatus(String paymentId) {
  125. return get<PaymentStatus>(
  126. '/v1/payment/$paymentId',
  127. PaymentStatus.fromJson,
  128. );
  129. }
  130. // ===== 收银台 =====
  131. /// 创建网页收银台会话
  132. Future<CheckoutSessionResponse> createCheckoutSession({
  133. required String channel,
  134. required String productId,
  135. String? returnUrl,
  136. }) {
  137. return post<CheckoutSessionResponse>(
  138. '/v1/checkout/session',
  139. CheckoutSessionResponse.fromJson,
  140. body: {
  141. 'channel': channel,
  142. 'product_id': productId,
  143. 'return_url': returnUrl,
  144. },
  145. );
  146. }
  147. /// 查询收银台会话状态
  148. Future<CheckoutSessionStatus> queryCheckoutStatus(String paymentId) {
  149. return get<CheckoutSessionStatus>(
  150. '/v1/checkout/session/$paymentId',
  151. CheckoutSessionStatus.fromJson,
  152. );
  153. }
  154. // ===== 商品 =====
  155. /// 拉取商品列表
  156. Future<PaymentProducts> fetchProducts() {
  157. return get<PaymentProducts>(
  158. '/v1/products',
  159. PaymentProducts.fromJson,
  160. );
  161. }
  162. // ===== Mock 模拟成功(仅后端 MOCK_MODE) =====
  163. /// 模拟支付成功(走与真实回调相同的幂等流程)
  164. Future<void> simulatePaymentSuccess(String paymentId) async {
  165. await dio.post<Map<String, dynamic>>(
  166. '/v1/payment/simulate/success',
  167. data: {'payment_id': paymentId},
  168. );
  169. }
  170. // ===== 错误解析 =====
  171. /// 将异常统一解析为友好中文提示
  172. String resolveError(Object error) {
  173. if (error is DioException) {
  174. final data = error.response?.data;
  175. if (data is Map) {
  176. final err = data['error'];
  177. if (err is Map && err['message'] != null) {
  178. return '服务端错误:${err['message']}';
  179. }
  180. }
  181. switch (error.type) {
  182. case DioExceptionType.connectionTimeout:
  183. case DioExceptionType.receiveTimeout:
  184. case DioExceptionType.sendTimeout:
  185. return '网络超时,请稍后重试';
  186. case DioExceptionType.connectionError:
  187. return '无法连接服务器,请检查网络或后端是否启动';
  188. default:
  189. return '请求失败:${error.message}';
  190. }
  191. }
  192. return error.toString();
  193. }
  194. }