Jelajahi Sumber

[new]增加支付以及引力引擎支付上报

zk 8 bulan lalu
induk
melakukan
f7da63200d
69 mengubah file dengan 2254 tambahan dan 179 penghapusan
  1. TEMPAT SAMPAH
      assets/images/icon_alipay_payment.webp
  2. TEMPAT SAMPAH
      assets/images/icon_alipay_scan_payment.webp
  3. TEMPAT SAMPAH
      assets/images/icon_cb_selected.webp
  4. TEMPAT SAMPAH
      assets/images/icon_cb_un_select.webp
  5. TEMPAT SAMPAH
      assets/images/icon_member_retain_close.webp
  6. TEMPAT SAMPAH
      assets/images/icon_wechat_payment.webp
  7. TEMPAT SAMPAH
      assets/images/icon_wechat_scan_payment.webp
  8. TEMPAT SAMPAH
      assets/images/img_member_retain_container.webp
  9. 14 1
      assets/string/base/string.xml
  10. 13 1
      lib/data/api/atmob_api.dart
  11. 92 19
      lib/data/api/atmob_api.g.dart
  12. 19 0
      lib/data/api/request/order_status_request.dart
  13. 71 0
      lib/data/api/request/order_status_request.g.dart
  14. 24 0
      lib/data/api/request/submit_and_request_pay_request.dart
  15. 75 0
      lib/data/api/request/submit_and_request_pay_request.g.dart
  16. 4 0
      lib/data/api/response/member_status_response.dart
  17. 14 0
      lib/data/api/response/order_status_response.dart
  18. 18 0
      lib/data/api/response/order_status_response.g.dart
  19. 44 0
      lib/data/api/response/request_pay_response.dart
  20. 25 0
      lib/data/api/response/request_pay_response.g.dart
  21. 47 0
      lib/data/bean/wechat_payment_sign_bean.dart
  22. 31 0
      lib/data/bean/wechat_payment_sign_bean.g.dart
  23. 57 2
      lib/data/consts/build_config.dart
  24. 8 0
      lib/data/consts/event_id.dart
  25. 17 0
      lib/data/consts/payment_type.dart
  26. 11 16
      lib/data/repositories/account_repository.dart
  27. 22 0
      lib/data/repositories/member_repository.dart
  28. 15 8
      lib/di/get_it.config.dart
  29. 6 0
      lib/dialog/common_confirm_dialog.dart
  30. 19 0
      lib/dialog/common_confirm_dialog_impl.dart
  31. 83 0
      lib/dialog/member_retain_dialog.dart
  32. 38 0
      lib/handler/event_handler.dart
  33. 4 1
      lib/main.dart
  34. 4 0
      lib/module/feedback/feed_back_controller.dart
  35. 163 11
      lib/module/member/member_controller.dart
  36. 178 114
      lib/module/member/member_page.dart
  37. 41 1
      lib/resource/assets.gen.dart
  38. 30 2
      lib/resource/string.gen.dart
  39. 120 0
      lib/sdk/gravity/gravity_helper.dart
  40. 1 1
      lib/sdk/wechat/wechat_helper.dart
  41. 1 2
      lib/sdk/wechat/wechat_share_util.dart
  42. 16 0
      lib/utils/common_util.dart
  43. 119 0
      lib/utils/payment_status_manager.dart
  44. 29 0
      plugins/agile_pay/.gitignore
  45. 10 0
      plugins/agile_pay/.metadata
  46. 3 0
      plugins/agile_pay/CHANGELOG.md
  47. 1 0
      plugins/agile_pay/LICENSE
  48. 39 0
      plugins/agile_pay/README.md
  49. 4 0
      plugins/agile_pay/analysis_options.yaml
  50. 4 0
      plugins/agile_pay/lib/flutter_pay.dart
  51. 46 0
      plugins/agile_pay/lib/src/agile_pay.dart
  52. 7 0
      plugins/agile_pay/lib/src/alipay/ali_pay_info.dart
  53. 58 0
      plugins/agile_pay/lib/src/alipay/alipay.dart
  54. 6 0
      plugins/agile_pay/lib/src/applepay/apple_pay.dart
  55. 5 0
      plugins/agile_pay/lib/src/applepay/apple_pay_info.dart
  56. 33 0
      plugins/agile_pay/lib/src/assist/agile_pay_state_info.dart
  57. 149 0
      plugins/agile_pay/lib/src/assist/apple_or_google_pay.dart
  58. 17 0
      plugins/agile_pay/lib/src/assist/apple_or_google_pay_info.dart
  59. 4 0
      plugins/agile_pay/lib/src/assist/product_type.dart
  60. 52 0
      plugins/agile_pay/lib/src/code/agile_pay_code.dart
  61. 6 0
      plugins/agile_pay/lib/src/googlepay/google_pay.dart
  62. 5 0
      plugins/agile_pay/lib/src/googlepay/google_pay_info.dart
  63. 50 0
      plugins/agile_pay/lib/src/listener/agile_pay_state.dart
  64. 7 0
      plugins/agile_pay/lib/src/listener/i_agile_pay.dart
  65. 88 0
      plugins/agile_pay/lib/src/wxpay/wechat_pay.dart
  66. 41 0
      plugins/agile_pay/lib/src/wxpay/wechat_pay_info.dart
  67. 61 0
      plugins/agile_pay/pubspec.yaml
  68. 72 0
      pubspec.lock
  69. 13 0
      pubspec.yaml

TEMPAT SAMPAH
assets/images/icon_alipay_payment.webp


TEMPAT SAMPAH
assets/images/icon_alipay_scan_payment.webp


TEMPAT SAMPAH
assets/images/icon_cb_selected.webp


TEMPAT SAMPAH
assets/images/icon_cb_un_select.webp


TEMPAT SAMPAH
assets/images/icon_member_retain_close.webp


TEMPAT SAMPAH
assets/images/icon_wechat_payment.webp


TEMPAT SAMPAH
assets/images/icon_wechat_scan_payment.webp


TEMPAT SAMPAH
assets/images/img_member_retain_container.webp


+ 14 - 1
assets/string/base/string.xml

@@ -236,7 +236,7 @@
     </string>
     <string name="logout_account">注销账号</string>
     <string name="account_logout_success">注销成功</string>
-    <string name="record_number">备案号:皖ICP备2023011908号-2A</string>
+    <string name="record_number">备案号:皖ICP备2024057362号-19A</string>
     <string name="permission_setting">快速设置</string>
     <string name="permission_location_setting">定位权限开启</string>
     <string name="permission_location_setting_subtitle">
@@ -270,4 +270,17 @@
     <string name="member_tips">
         本应用功能仅限于家庭成员和亲人朋友之间使用,根据相关法规和隐私协议规定,共享位置功能需要对方下载得到好友授权同意才能正常使用。
     </string>
+    <string name="member_continue_pay">继续支付</string>
+    <string name="member_please_choice_goods">请选择支付商品</string>
+    <string name="member_please_choice_payment">请选择支付方式</string>
+
+    <string name="pay_loading">请求中...</string>
+    <string name="pay_user_cancel">用户取消支付</string>
+    <string name="pay_not_support">不支持该支付类型</string>
+    <string name="pay_wx_evn_error">微信未安装或微信版本不支持</string>
+    <string name="pay_error">支付失败,请稍后重试</string>
+    <string name="pay_query_pay_state">正在查询订单状态..</string>
+    <string name="pay_not_connect_store">无法连接到商店</string>
+    <string name="pay_success_title">支付成功</string>
+    <string name="pay_success_desc">您的订单已成功支付</string>
 </resources>

+ 13 - 1
lib/data/api/atmob_api.dart

@@ -9,9 +9,11 @@ import 'package:location/data/api/request/friends_operation_request.dart';
 import 'package:location/data/api/request/login_request.dart';
 import 'package:location/data/api/request/message_request.dart';
 import 'package:location/data/api/request/operation_friend_request.dart';
+import 'package:location/data/api/request/order_status_request.dart';
 import 'package:location/data/api/request/query_track_request.dart';
 import 'package:location/data/api/request/request_friendlist_request.dart';
 import 'package:location/data/api/request/send_code_request.dart';
+import 'package:location/data/api/request/submit_and_request_pay_request.dart';
 import 'package:location/data/api/response/configs_response.dart';
 import 'package:location/data/api/response/contact_list_response.dart';
 import 'package:location/data/api/response/contact_may_day_all_response.dart';
@@ -21,8 +23,10 @@ import 'package:location/data/api/response/login_response.dart';
 import 'package:location/data/api/response/member_status_response.dart';
 import 'package:location/data/api/response/member_trial_response.dart';
 import 'package:location/data/api/response/message_response.dart';
+import 'package:location/data/api/response/order_status_response.dart';
 import 'package:location/data/api/response/query_track_response.dart';
 import 'package:location/data/api/response/request_friend_list_response.dart';
+import 'package:location/data/api/response/request_pay_response.dart';
 import 'package:retrofit/error_logger.dart';
 import 'package:retrofit/http.dart';
 
@@ -43,7 +47,7 @@ abstract class AtmobApi {
       @Body() LoginRequest request);
 
   @POST("/s/v1/user/member")
-  Future<BaseResponse<MemberStatusResponse?>> getMemberStatus(
+  Future<BaseResponse<MemberStatusResponse>> getMemberStatus(
       @Body() AppBaseRequest request);
 
   @POST("/s/v1/friend/list")
@@ -144,4 +148,12 @@ abstract class AtmobApi {
   @POST("/s/v1/item/list")
   Future<BaseResponse<ItemListResponse>> getMemberList(
       @Body() AppBaseRequest request);
+
+  @POST("/s/v1/order/submitAndRequestPay")
+  Future<BaseResponse<RequestPayResponse>> submitAndRequestPay(
+      @Body() SubmitAndRequestPayRequest request);
+
+  @POST("/s/v1/order/payStatus")
+  Future<BaseResponse<OrderStatusResponse>> orderStatus(
+      @Body() OrderStatusRequest request);
 }

+ 92 - 19
lib/data/api/atmob_api.g.dart

@@ -97,38 +97,35 @@ class _AtmobApi implements AtmobApi {
   }
 
   @override
-  Future<BaseResponse<MemberStatusResponse?>> getMemberStatus(
+  Future<BaseResponse<MemberStatusResponse>> getMemberStatus(
       AppBaseRequest request) async {
     final _extra = <String, dynamic>{};
     final queryParameters = <String, dynamic>{};
     final _headers = <String, dynamic>{};
     final _data = <String, dynamic>{};
     _data.addAll(request.toJson());
-    final _options =
-        _setStreamType<BaseResponse<MemberStatusResponse?>>(Options(
+    final _options = _setStreamType<BaseResponse<MemberStatusResponse>>(Options(
       method: 'POST',
       headers: _headers,
       extra: _extra,
     )
-            .compose(
-              _dio.options,
-              '/s/v1/user/member',
-              queryParameters: queryParameters,
-              data: _data,
-            )
-            .copyWith(
-                baseUrl: _combineBaseUrls(
-              _dio.options.baseUrl,
-              baseUrl,
-            )));
+        .compose(
+          _dio.options,
+          '/s/v1/user/member',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
     final _result = await _dio.fetch<Map<String, dynamic>>(_options);
-    late BaseResponse<MemberStatusResponse?> _value;
+    late BaseResponse<MemberStatusResponse> _value;
     try {
-      _value = BaseResponse<MemberStatusResponse?>.fromJson(
+      _value = BaseResponse<MemberStatusResponse>.fromJson(
         _result.data!,
-        (json) => json == null
-            ? null
-            : MemberStatusResponse.fromJson(json as Map<String, dynamic>),
+        (json) => MemberStatusResponse.fromJson(json as Map<String, dynamic>),
       );
     } on Object catch (e, s) {
       errorLogger?.logError(e, s, _options);
@@ -1130,6 +1127,82 @@ class _AtmobApi implements AtmobApi {
     return _value;
   }
 
+  @override
+  Future<BaseResponse<RequestPayResponse>> submitAndRequestPay(
+      SubmitAndRequestPayRequest request) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<RequestPayResponse>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+        .compose(
+          _dio.options,
+          '/s/v1/order/submitAndRequestPay',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<RequestPayResponse> _value;
+    try {
+      _value = BaseResponse<RequestPayResponse>.fromJson(
+        _result.data!,
+        (json) => RequestPayResponse.fromJson(json as Map<String, dynamic>),
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
+  Future<BaseResponse<OrderStatusResponse>> orderStatus(
+      OrderStatusRequest request) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<OrderStatusResponse>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+        .compose(
+          _dio.options,
+          '/s/v1/order/payStatus',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<OrderStatusResponse> _value;
+    try {
+      _value = BaseResponse<OrderStatusResponse>.fromJson(
+        _result.data!,
+        (json) => OrderStatusResponse.fromJson(json as Map<String, dynamic>),
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
   RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
     if (T != dynamic &&
         !(requestOptions.responseType == ResponseType.bytes ||

+ 19 - 0
lib/data/api/request/order_status_request.dart

@@ -0,0 +1,19 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../../base/app_base_request.dart';
+
+part 'order_status_request.g.dart';
+
+@JsonSerializable()
+class OrderStatusRequest extends AppBaseRequest {
+  @JsonKey(name: 'outTradeNo')
+  String outTradeNo;
+
+  @JsonKey(name: 'receiptData')
+  String? receiptData;
+
+  OrderStatusRequest(this.outTradeNo, this.receiptData);
+
+  @override
+  Map<String, dynamic> toJson() => _$OrderStatusRequestToJson(this);
+}

+ 71 - 0
lib/data/api/request/order_status_request.g.dart

@@ -0,0 +1,71 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'order_status_request.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+OrderStatusRequest _$OrderStatusRequestFromJson(Map<String, dynamic> json) =>
+    OrderStatusRequest(
+      json['outTradeNo'] as String,
+      json['receiptData'] as String?,
+    )
+      ..appPlatform = (json['appPlatform'] as num).toInt()
+      ..os = json['os'] as String
+      ..osVersion = json['osVersion'] as String
+      ..packageName = json['packageName'] as String?
+      ..appVersionName = json['appVersionName'] as String?
+      ..appVersionCode = (json['appVersionCode'] as num?)?.toInt()
+      ..channelName = json['channelName'] as String?
+      ..appId = (json['appId'] as num?)?.toInt()
+      ..tgPlatform = (json['tgPlatform'] as num?)?.toInt()
+      ..oaid = json['oaid'] as String?
+      ..aaid = json['aaid'] as String?
+      ..androidId = json['androidId'] as String?
+      ..imei = json['imei'] as String?
+      ..simImei0 = json['simImei0'] as String?
+      ..simImei1 = json['simImei1'] as String?
+      ..mac = json['mac'] as String?
+      ..idfa = json['idfa'] as String?
+      ..idfv = json['idfv'] as String?
+      ..machineId = json['machineId'] as String?
+      ..brand = json['brand'] as String?
+      ..model = json['model'] as String?
+      ..wifiName = json['wifiName'] as String?
+      ..region = json['region'] as String?
+      ..locLng = (json['locLng'] as num?)?.toDouble()
+      ..locLat = (json['locLat'] as num?)?.toDouble()
+      ..authToken = json['authToken'] as String?;
+
+Map<String, dynamic> _$OrderStatusRequestToJson(OrderStatusRequest instance) =>
+    <String, dynamic>{
+      'appPlatform': instance.appPlatform,
+      'os': instance.os,
+      'osVersion': instance.osVersion,
+      'packageName': instance.packageName,
+      'appVersionName': instance.appVersionName,
+      'appVersionCode': instance.appVersionCode,
+      'channelName': instance.channelName,
+      'appId': instance.appId,
+      'tgPlatform': instance.tgPlatform,
+      'oaid': instance.oaid,
+      'aaid': instance.aaid,
+      'androidId': instance.androidId,
+      'imei': instance.imei,
+      'simImei0': instance.simImei0,
+      'simImei1': instance.simImei1,
+      'mac': instance.mac,
+      'idfa': instance.idfa,
+      'idfv': instance.idfv,
+      'machineId': instance.machineId,
+      'brand': instance.brand,
+      'model': instance.model,
+      'wifiName': instance.wifiName,
+      'region': instance.region,
+      'locLng': instance.locLng,
+      'locLat': instance.locLat,
+      'authToken': instance.authToken,
+      'outTradeNo': instance.outTradeNo,
+      'receiptData': instance.receiptData,
+    };

+ 24 - 0
lib/data/api/request/submit_and_request_pay_request.dart

@@ -0,0 +1,24 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:location/base/app_base_request.dart';
+
+part 'submit_and_request_pay_request.g.dart';
+
+@JsonSerializable()
+class SubmitAndRequestPayRequest extends AppBaseRequest {
+  @JsonKey(name: 'itemId')
+  int goodsId;
+
+  @JsonKey(name: 'payPlatform')
+  int payPlatform;
+
+  @JsonKey(name: 'payMethod')
+  int payMethod;
+
+  SubmitAndRequestPayRequest(
+      {required this.goodsId,
+      required this.payPlatform,
+      required this.payMethod});
+
+  @override
+  Map<String, dynamic> toJson() => _$SubmitAndRequestPayRequestToJson(this);
+}

+ 75 - 0
lib/data/api/request/submit_and_request_pay_request.g.dart

@@ -0,0 +1,75 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'submit_and_request_pay_request.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+SubmitAndRequestPayRequest _$SubmitAndRequestPayRequestFromJson(
+        Map<String, dynamic> json) =>
+    SubmitAndRequestPayRequest(
+      goodsId: (json['itemId'] as num).toInt(),
+      payPlatform: (json['payPlatform'] as num).toInt(),
+      payMethod: (json['payMethod'] as num).toInt(),
+    )
+      ..appPlatform = (json['appPlatform'] as num).toInt()
+      ..os = json['os'] as String
+      ..osVersion = json['osVersion'] as String
+      ..packageName = json['packageName'] as String?
+      ..appVersionName = json['appVersionName'] as String?
+      ..appVersionCode = (json['appVersionCode'] as num?)?.toInt()
+      ..channelName = json['channelName'] as String?
+      ..appId = (json['appId'] as num?)?.toInt()
+      ..tgPlatform = (json['tgPlatform'] as num?)?.toInt()
+      ..oaid = json['oaid'] as String?
+      ..aaid = json['aaid'] as String?
+      ..androidId = json['androidId'] as String?
+      ..imei = json['imei'] as String?
+      ..simImei0 = json['simImei0'] as String?
+      ..simImei1 = json['simImei1'] as String?
+      ..mac = json['mac'] as String?
+      ..idfa = json['idfa'] as String?
+      ..idfv = json['idfv'] as String?
+      ..machineId = json['machineId'] as String?
+      ..brand = json['brand'] as String?
+      ..model = json['model'] as String?
+      ..wifiName = json['wifiName'] as String?
+      ..region = json['region'] as String?
+      ..locLng = (json['locLng'] as num?)?.toDouble()
+      ..locLat = (json['locLat'] as num?)?.toDouble()
+      ..authToken = json['authToken'] as String?;
+
+Map<String, dynamic> _$SubmitAndRequestPayRequestToJson(
+        SubmitAndRequestPayRequest instance) =>
+    <String, dynamic>{
+      'appPlatform': instance.appPlatform,
+      'os': instance.os,
+      'osVersion': instance.osVersion,
+      'packageName': instance.packageName,
+      'appVersionName': instance.appVersionName,
+      'appVersionCode': instance.appVersionCode,
+      'channelName': instance.channelName,
+      'appId': instance.appId,
+      'tgPlatform': instance.tgPlatform,
+      'oaid': instance.oaid,
+      'aaid': instance.aaid,
+      'androidId': instance.androidId,
+      'imei': instance.imei,
+      'simImei0': instance.simImei0,
+      'simImei1': instance.simImei1,
+      'mac': instance.mac,
+      'idfa': instance.idfa,
+      'idfv': instance.idfv,
+      'machineId': instance.machineId,
+      'brand': instance.brand,
+      'model': instance.model,
+      'wifiName': instance.wifiName,
+      'region': instance.region,
+      'locLng': instance.locLng,
+      'locLat': instance.locLat,
+      'authToken': instance.authToken,
+      'itemId': instance.goodsId,
+      'payPlatform': instance.payPlatform,
+      'payMethod': instance.payMethod,
+    };

+ 4 - 0
lib/data/api/response/member_status_response.dart

@@ -25,6 +25,10 @@ class MemberStatusResponse {
   @JsonKey(name: 'permanent')
   final bool permanent;
 
+  // @JsonKey(name: 'deviceId')
+  //TODO 待后台发布后需修改为deviceId
+  String get deviceId => userId;
+
   MemberStatusResponse({
     required this.userId,
     required this.level,

+ 14 - 0
lib/data/api/response/order_status_response.dart

@@ -0,0 +1,14 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'order_status_response.g.dart';
+
+@JsonSerializable()
+class OrderStatusResponse {
+  @JsonKey(name: 'payStatus')
+  int payStatus;
+
+  OrderStatusResponse(this.payStatus);
+
+  factory OrderStatusResponse.fromJson(Map<String, dynamic> json) =>
+      _$OrderStatusResponseFromJson(json);
+}

+ 18 - 0
lib/data/api/response/order_status_response.g.dart

@@ -0,0 +1,18 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'order_status_response.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+OrderStatusResponse _$OrderStatusResponseFromJson(Map<String, dynamic> json) =>
+    OrderStatusResponse(
+      (json['payStatus'] as num).toInt(),
+    );
+
+Map<String, dynamic> _$OrderStatusResponseToJson(
+        OrderStatusResponse instance) =>
+    <String, dynamic>{
+      'payStatus': instance.payStatus,
+    };

+ 44 - 0
lib/data/api/response/request_pay_response.dart

@@ -0,0 +1,44 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'request_pay_response.g.dart';
+
+@JsonSerializable()
+class RequestPayResponse {
+  /**
+   *  @SerializedName("outTradeNo")
+      private String orderId;
+      @SerializedName("wechatPayQrcodeUrl")
+      private String wechatPayQrcodeUrl;
+      @SerializedName("wechatPayPrepayJson")
+      private String wechatPayPrepayJson;
+      @SerializedName("alipayQrcodeHtml")
+      private String alipayQrcodeHtml;
+      @SerializedName("alipayOrderString")
+      private String alipayOrderString;
+   */
+
+  @JsonKey(name: 'outTradeNo')
+  String outTradeNo;
+
+  @JsonKey(name: 'wechatPayQrcodeUrl')
+  String? wechatPayQrcodeUrl;
+
+  @JsonKey(name: 'wechatPayPrepayJson')
+  String? wechatPayPrepayJson;
+
+  @JsonKey(name: 'alipayQrcodeHtml')
+  String? alipayQrcodeHtml;
+
+  @JsonKey(name: 'alipayOrderString')
+  String? alipayOrderString;
+
+  RequestPayResponse(
+      {required this.outTradeNo,
+      this.wechatPayQrcodeUrl,
+      this.wechatPayPrepayJson,
+      this.alipayQrcodeHtml,
+      this.alipayOrderString});
+
+  factory RequestPayResponse.fromJson(Map<String, dynamic> json) =>
+      _$RequestPayResponseFromJson(json);
+}

+ 25 - 0
lib/data/api/response/request_pay_response.g.dart

@@ -0,0 +1,25 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'request_pay_response.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+RequestPayResponse _$RequestPayResponseFromJson(Map<String, dynamic> json) =>
+    RequestPayResponse(
+      outTradeNo: json['outTradeNo'] as String,
+      wechatPayQrcodeUrl: json['wechatPayQrcodeUrl'] as String?,
+      wechatPayPrepayJson: json['wechatPayPrepayJson'] as String?,
+      alipayQrcodeHtml: json['alipayQrcodeHtml'] as String?,
+      alipayOrderString: json['alipayOrderString'] as String?,
+    );
+
+Map<String, dynamic> _$RequestPayResponseToJson(RequestPayResponse instance) =>
+    <String, dynamic>{
+      'outTradeNo': instance.outTradeNo,
+      'wechatPayQrcodeUrl': instance.wechatPayQrcodeUrl,
+      'wechatPayPrepayJson': instance.wechatPayPrepayJson,
+      'alipayQrcodeHtml': instance.alipayQrcodeHtml,
+      'alipayOrderString': instance.alipayOrderString,
+    };

+ 47 - 0
lib/data/bean/wechat_payment_sign_bean.dart

@@ -0,0 +1,47 @@
+import 'dart:convert';
+
+import 'package:json_annotation/json_annotation.dart';
+
+part 'wechat_payment_sign_bean.g.dart';
+
+@JsonSerializable()
+class WechatPaymentSignBean {
+  @JsonKey(name: 'appid')
+  String appId;
+
+  @JsonKey(name: 'noncestr')
+  String nonceStr;
+
+  @JsonKey(name: 'package')
+  String package;
+
+  @JsonKey(name: 'partnerid')
+  String partnerId;
+
+  @JsonKey(name: 'prepayid')
+  String prepayId;
+
+  @JsonKey(name: 'sign')
+  String sign;
+
+  @JsonKey(name: 'timestamp')
+  String timeStamp;
+
+  WechatPaymentSignBean({
+    this.appId = '',
+    this.nonceStr = '',
+    this.package = '',
+    this.partnerId = '',
+    this.prepayId = '',
+    this.sign = '',
+    this.timeStamp = '',
+  });
+
+  static WechatPaymentSignBean stringToBean(String jsonStr) {
+    final Map<String, dynamic> json = jsonDecode(jsonStr);
+    return WechatPaymentSignBean.fromJson(json);
+  }
+
+  factory WechatPaymentSignBean.fromJson(Map<String, dynamic> json) =>
+      _$WechatPaymentSignBeanFromJson(json);
+}

+ 31 - 0
lib/data/bean/wechat_payment_sign_bean.g.dart

@@ -0,0 +1,31 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'wechat_payment_sign_bean.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+WechatPaymentSignBean _$WechatPaymentSignBeanFromJson(
+        Map<String, dynamic> json) =>
+    WechatPaymentSignBean(
+      appId: json['appid'] as String? ?? '',
+      nonceStr: json['noncestr'] as String? ?? '',
+      package: json['package'] as String? ?? '',
+      partnerId: json['partnerid'] as String? ?? '',
+      prepayId: json['prepayid'] as String? ?? '',
+      sign: json['sign'] as String? ?? '',
+      timeStamp: json['timestamp'] as String? ?? '',
+    );
+
+Map<String, dynamic> _$WechatPaymentSignBeanToJson(
+        WechatPaymentSignBean instance) =>
+    <String, dynamic>{
+      'appid': instance.appId,
+      'noncestr': instance.nonceStr,
+      'package': instance.package,
+      'partnerid': instance.partnerId,
+      'prepayid': instance.prepayId,
+      'sign': instance.sign,
+      'timestamp': instance.timeStamp,
+    };

+ 57 - 2
lib/data/consts/build_config.dart

@@ -1,3 +1,5 @@
+import 'dart:io';
+
 import 'package:flutter/foundation.dart';
 
 final class BuildConfig {
@@ -5,7 +7,60 @@ final class BuildConfig {
 
   static bool get isDebug => kDebugMode;
 
-  static final String wechatAppId = 'wx64695e2b8346a227';
-
   static const String qiyuKEY = "09ea6e0a6d006e25462906fbf6758c99";
 }
+
+final class WechatConfig {
+  WechatConfig._();
+
+  static const String wechatAppId = 'wxd423dc54a6fd1640';
+}
+
+final class UmengConfig {
+  UmengConfig._();
+
+  //友盟统计配置
+  static const umengAndroidAppKey = "67e11d9b48ac1b4f87f56ea2";
+  static const umengIosAppKey = "67e11e7565c707471a29602e";
+
+  static String get umengAppKey {
+    if (Platform.isAndroid) {
+      return umengAndroidAppKey;
+    } else if (Platform.isIOS) {
+      return umengIosAppKey;
+    } else {
+      return '';
+    }
+  }
+}
+
+final class GravityConfig {
+  GravityConfig._();
+
+  //引力引擎配置
+  static const gravityAndroidAppId = "";
+  static const gravityAndroidAccessToken = "9z0eEcjzcXggkJawmAuqIpypvYsotq7i";
+
+  static const gravityIosAppId = "";
+  static const gravityIosAccessToken = "w9fuUykhmpYsqNrF6Zue8IvCXxmn4vyj";
+
+  static String get gravityAppId {
+    if (Platform.isAndroid) {
+      return gravityAndroidAppId;
+    } else if (Platform.isIOS) {
+      return gravityIosAppId;
+    } else {
+      return '';
+    }
+  }
+
+  static String get gravityAccessToken {
+    if (Platform.isAndroid) {
+      return gravityAndroidAccessToken;
+    } else if (Platform.isIOS) {
+      return gravityIosAccessToken;
+    } else {
+      return '';
+    }
+  }
+}

+ 8 - 0
lib/data/consts/event_id.dart

@@ -0,0 +1,8 @@
+abstract class EventId {
+  static const String eventId = "id";
+  static const String eventGoodsId = "vip";
+  static const String eventFeedbackId = "feedback";
+
+  // 用户反馈-提交	带反馈内容
+  static const String event_8004002 = "z8004002";
+}

+ 17 - 0
lib/data/consts/payment_type.dart

@@ -0,0 +1,17 @@
+class PayPlatform {
+  static const int android = 1;
+  static const int apple = 2;
+}
+
+class PayMethod {
+  static const int alipay = 1;
+  static const int wechat = 2;
+  static const int apple = 3;
+  static const int google = 4;
+  static const int douYin = 5;
+}
+
+class Currency {
+  static const String cny = "CNY";
+  static const String usd = "USD";
+}

+ 11 - 16
lib/data/repositories/account_repository.dart

@@ -24,6 +24,7 @@ import 'package:location/utils/mmkv_util.dart';
 import '../../sdk/map/map_helper.dart';
 import '../../sdk/qiyu/qi_yu_helper.dart';
 import '../api/response/login_response.dart';
+import '../api/response/member_status_response.dart';
 
 @lazySingleton
 class AccountRepository {
@@ -162,35 +163,29 @@ class AccountRepository {
     });
   }
 
-  Future<MemberStatusInfo?> getMemberStatus() {
+  Future<MemberStatusResponse> getMemberStatus() {
     return atmobApi
         .getMemberStatus(AppBaseRequest())
-        .then(HttpHandler.handle(true))
+        .then(HttpHandler.handle(false))
         .then((response) {
       refreshMemberHandler?.cancel();
-      if (response != null) {
-        QiYuHelper.setUserInfo(loginPhoneNum.value, response.userId);
-        KVUtil.putString(keyAccountLoginUserId, response.userId);
-        if (!response.permanent && !response.expired) {
-          refreshMemberHandler = Timer(
-              Duration(
-                  milliseconds:
-                      response.endTimestamp - response.serverTimestamp),
-              () => refreshMemberStatus());
-        }
+      QiYuHelper.setUserInfo(loginPhoneNum.value, response.deviceId);
+      KVUtil.putString(keyAccountLoginUserId, response.deviceId);
+      if (!response.permanent && !response.expired) {
+        refreshMemberHandler = Timer(
+            Duration(
+                milliseconds: response.endTimestamp - response.serverTimestamp),
+            () => refreshMemberStatus());
       }
       return response;
     }).then((response) {
-      if (response == null) {
-        return null;
-      }
       MemberStatusInfo memberStatusInfo = MemberStatusInfo(
           level: response.level,
           endTimestamp: response.endTimestamp,
           expired: response.expired,
           permanent: response.permanent);
       this.memberStatusInfo.value = memberStatusInfo;
-      return memberStatusInfo;
+      return response;
     });
   }
 

+ 22 - 0
lib/data/repositories/member_repository.dart

@@ -3,7 +3,10 @@ import 'package:location/base/app_base_request.dart';
 import 'package:location/data/api/atmob_api.dart';
 import 'package:location/utils/http_handler.dart';
 
+import '../api/request/order_status_request.dart';
+import '../api/request/submit_and_request_pay_request.dart';
 import '../api/response/item_list_response.dart';
+import '../api/response/request_pay_response.dart';
 
 @lazySingleton
 class MemberRepository {
@@ -23,4 +26,23 @@ class MemberRepository {
         .getMemberList(AppBaseRequest())
         .then(HttpHandler.handle(true));
   }
+
+  Future<RequestPayResponse> submitAndRequestPay(
+      {required int goodsId,
+      required int payPlatform,
+      required int payMethod}) {
+    return atmobApi
+        .submitAndRequestPay(SubmitAndRequestPayRequest(
+            goodsId: goodsId, payPlatform: payPlatform, payMethod: payMethod))
+        .then(HttpHandler.handle(false));
+  }
+
+  Future<int> orderStatus(String outTradeNo, {String? receiptData}) {
+    return atmobApi
+        .orderStatus(OrderStatusRequest(outTradeNo, receiptData))
+        .then(HttpHandler.handle(false))
+        .then((data) {
+      return data.payStatus;
+    });
+  }
 }

+ 15 - 8
lib/di/get_it.config.dart

@@ -40,6 +40,7 @@ import '../module/urgent_contact/add_contact/add_urgent_contact_controller.dart'
     as _i955;
 import '../module/urgent_contact/urgent_contact_controller.dart' as _i720;
 import '../socket/atmob_location_client.dart' as _i220;
+import '../utils/payment_status_manager.dart' as _i779;
 import 'network_module.dart' as _i567;
 
 extension GetItInjectableX on _i174.GetIt {
@@ -54,12 +55,12 @@ extension GetItInjectableX on _i174.GetIt {
       environmentFilter,
     );
     final networkModule = _$NetworkModule();
+    gh.factory<_i256.AboutController>(() => _i256.AboutController());
     gh.factory<_i923.BrowserController>(() => _i923.BrowserController());
     gh.factory<_i769.FeedBackController>(() => _i769.FeedBackController());
-    gh.factory<_i973.SplashController>(() => _i973.SplashController());
-    gh.factory<_i256.AboutController>(() => _i256.AboutController());
     gh.factory<_i108.PermissionSettingController>(
         () => _i108.PermissionSettingController());
+    gh.factory<_i973.SplashController>(() => _i973.SplashController());
     gh.singleton<_i361.Dio>(() => networkModule.createDefaultDio());
     gh.lazySingleton<_i220.AtmobLocationClient>(
         () => _i220.AtmobLocationClient());
@@ -71,20 +72,21 @@ extension GetItInjectableX on _i174.GetIt {
         () => _i850.ContactRepository(gh<_i243.AtmobApi>()));
     gh.lazySingleton<_i1053.FriendsRepository>(
         () => _i1053.FriendsRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i814.MemberRepository>(
+        () => _i814.MemberRepository(gh<_i243.AtmobApi>()));
     gh.lazySingleton<_i791.MessageRepository>(
         () => _i791.MessageRepository(gh<_i243.AtmobApi>()));
     gh.lazySingleton<_i240.TrackRepository>(
         () => _i240.TrackRepository(gh<_i243.AtmobApi>()));
     gh.lazySingleton<_i983.UrgentContactRepository>(
         () => _i983.UrgentContactRepository(gh<_i243.AtmobApi>()));
-    gh.lazySingleton<_i814.MemberRepository>(
-        () => _i814.MemberRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i779.PaymentStatusManager>(
+        () => _i779.PaymentStatusManager(
+              gh<_i814.MemberRepository>(),
+              gh<_i20.AccountRepository>(),
+            ));
     gh.factory<_i1008.LoginController>(
         () => _i1008.LoginController(gh<_i20.AccountRepository>()));
-    gh.factory<_i269.MemberController>(() => _i269.MemberController(
-          gh<_i20.AccountRepository>(),
-          gh<_i814.MemberRepository>(),
-        ));
     gh.factory<_i489.NewsController>(
         () => _i489.NewsController(gh<_i791.MessageRepository>()));
     gh.lazySingleton<_i825.ConfigRepository>(() => _i825.ConfigRepository(
@@ -113,6 +115,11 @@ extension GetItInjectableX on _i174.GetIt {
           gh<_i983.UrgentContactRepository>(),
           gh<_i825.ConfigRepository>(),
         ));
+    gh.factory<_i269.MemberController>(() => _i269.MemberController(
+          gh<_i20.AccountRepository>(),
+          gh<_i814.MemberRepository>(),
+          gh<_i779.PaymentStatusManager>(),
+        ));
     gh.factory<_i955.AddUrgentContactController>(
         () => _i955.AddUrgentContactController(
               gh<_i983.UrgentContactRepository>(),

+ 6 - 0
lib/dialog/common_confirm_dialog.dart

@@ -13,10 +13,16 @@ class CommonConfirmDialog {
       required String confirmText,
       String? cancelText,
       VoidCallback? cancelOnTap,
+      VoidCallback? onDismiss,
+      bool? clickMaskDismiss,
+      bool? backDismiss,
       required VoidCallback confirmOnTap,
       String tag = _tag}) {
     SmartDialog.show(
         tag: _tag,
+        backDismiss: backDismiss,
+        clickMaskDismiss: clickMaskDismiss,
+        onDismiss: onDismiss,
         builder: (_) {
           return _CommonConfirmDialog(
               titleWidget: titleWidget,

+ 19 - 0
lib/dialog/common_confirm_dialog_impl.dart

@@ -115,3 +115,22 @@ void showAgreeAddFriendTipDialog({required VoidCallback onConfirm}) {
       confirmText: StringName.dialogSure,
       confirmOnTap: onConfirm);
 }
+
+void showPaymentSuccessDialog(
+    {required VoidCallback onConfirm, required VoidCallback onCancel}) {
+  CommonConfirmDialog.show(
+      backDismiss: false,
+      clickMaskDismiss: false,
+      titleWidget: Text(StringName.paySuccessTitle,
+          style: TextStyle(
+              fontSize: 17.sp,
+              color: '#333333'.color,
+              fontWeight: FontWeight.bold)),
+      descWidget: Text(
+        StringName.paySuccessDesc,
+        style: TextStyle(fontSize: 14.sp, color: '#404040'.color),
+      ),
+      confirmText: StringName.dialogSure,
+      cancelOnTap: onCancel,
+      confirmOnTap: onConfirm);
+}

+ 83 - 0
lib/dialog/member_retain_dialog.dart

@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:location/resource/assets.gen.dart';
+import 'package:location/resource/string.gen.dart';
+import 'package:location/utils/common_style.dart';
+
+class MemberRetainDialog {
+  static final String _tag = 'MemberRetainDialog';
+
+  static void show(
+      {required VoidCallback payClick, required VoidCallback cancelClick}) {
+    SmartDialog.show(
+        tag: _tag,
+        backDismiss: false,
+        clickMaskDismiss: false,
+        builder: (_) {
+          return _MemberRetainDialogWidget(
+              payClick: payClick, cancelClick: cancelClick);
+        });
+  }
+
+  static void dismiss() {
+    SmartDialog.dismiss(tag: _tag);
+  }
+}
+
+class _MemberRetainDialogWidget extends Dialog {
+  final VoidCallback payClick;
+  final VoidCallback cancelClick;
+
+  const _MemberRetainDialogWidget(
+      {required this.payClick, required this.cancelClick});
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      child: IntrinsicHeight(
+        child: Column(
+          children: [
+            Stack(
+              children: [
+                AspectRatio(
+                    aspectRatio: 1080 / 1296,
+                    child: Assets.images.imgMemberRetainContainer
+                        .image(width: 301.w)),
+                Positioned(
+                  top: 324.w,
+                  left: 73.w,
+                  right: 73.w,
+                  child: Container(
+                    height: 40.w,
+                    decoration: getPrimaryBtnDecoration(100.w),
+                    child: Center(
+                      child: Text(StringName.memberContinuePay,
+                          style:
+                              TextStyle(fontSize: 15.sp, color: Colors.white)),
+                    ),
+                  ),
+                ),
+                Positioned(
+                  bottom: 0,
+                  left: 0,
+                  right: 0,
+                  child: Center(
+                    child: GestureDetector(
+                      onTap: () {
+                        MemberRetainDialog.dismiss();
+                        cancelClick();
+                      },
+                      child: Assets.images.iconMemberRetainClose
+                          .image(width: 32.w, height: 32.w),
+                    ),
+                  ),
+                )
+              ],
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 38 - 0
lib/handler/event_handler.dart

@@ -0,0 +1,38 @@
+import '../data/consts/payment_type.dart';
+import '../sdk/gravity/gravity_helper.dart';
+import '../utils/toast_util.dart';
+
+class EventHandler {
+  static const bool isShowToast = false;
+
+  EventHandler._();
+
+  static void report(String eventId, {Map<String, dynamic>? params}) {
+    if (isShowToast) {
+      if (params == null) {
+        ToastUtil.show(eventId);
+      } else {
+        ToastUtil.show('$eventId ${params.toString()}');
+      }
+    }
+    GravityHelper.report(eventId, params: params);
+  }
+
+  static void reportPay(
+      int priceFen, String orderId, String itemName, int payWay) {
+    if (isShowToast) {
+      String? payWayStr;
+      switch (payWay) {
+        case PayMethod.alipay:
+          payWayStr = '支付宝';
+          break;
+        case PayMethod.wechat:
+          payWayStr = '微信';
+          break;
+      }
+      ToastUtil.show(
+          'PAY: 金额:$priceFen 订单号:$orderId 商品名:$itemName 支付方式:$payWayStr');
+    }
+    GravityHelper.reportPay(priceFen, orderId, itemName, payWay);
+  }
+}

+ 4 - 1
lib/main.dart

@@ -10,6 +10,7 @@ import 'package:location/resource/colors.gen.dart';
 import 'package:location/resource/string.gen.dart';
 import 'package:location/resource/string_source.dart';
 import 'package:location/router/app_pages.dart';
+import 'package:location/sdk/gravity/gravity_helper.dart';
 import 'package:location/sdk/map/map_helper.dart';
 import 'package:location/sdk/qiyu/qi_yu_helper.dart';
 import 'package:location/sdk/wechat/wechat_helper.dart';
@@ -68,7 +69,9 @@ class AppInitTask implements EnsurePolicyGrant {
     await deviceInfoUtil.init();
     //初始化其他sdk
     await MapHelper.init();
-
+    //引力引擎
+    GravityHelper.init();
+    //七鱼客服
     QiYuHelper.init();
   }
 }

+ 4 - 0
lib/module/feedback/feed_back_controller.dart

@@ -2,9 +2,11 @@ import 'package:get/get.dart';
 import 'package:get/get_core/src/get_main.dart';
 import 'package:injectable/injectable.dart';
 import 'package:location/base/base_controller.dart';
+import 'package:location/handler/event_handler.dart';
 import 'package:location/resource/string.gen.dart';
 import 'package:location/utils/toast_util.dart';
 
+import '../../data/consts/event_id.dart';
 import '../../sdk/qiyu/qi_yu_helper.dart';
 
 @injectable
@@ -26,6 +28,8 @@ class FeedBackController extends BaseController {
       ToastUtil.show(StringName.feedBackHint);
       return;
     }
+    EventHandler.report(EventId.event_8004002,
+        params: {EventId.eventFeedbackId: content});
     ToastUtil.show(StringName.feedBackSuccess);
     Get.back();
   }

+ 163 - 11
lib/module/member/member_controller.dart

@@ -1,6 +1,7 @@
 import 'dart:async';
 import 'dart:math';
 
+import 'package:agile_pay/flutter_pay.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -16,18 +17,30 @@ import 'package:location/module/login/login_page.dart';
 import 'package:location/resource/assets.gen.dart';
 import 'package:location/utils/async_util.dart';
 import 'package:location/utils/common_expand.dart';
+import 'package:location/utils/toast_util.dart';
+import '../../data/api/response/request_pay_response.dart';
 import '../../data/bean/member_status_info.dart';
+import '../../data/bean/wechat_payment_sign_bean.dart';
+import '../../data/consts/error_code.dart';
+import '../../data/consts/payment_type.dart';
 import '../../data/consts/web_url.dart';
+import '../../dialog/common_confirm_dialog_impl.dart';
+import '../../dialog/loading_dialog.dart';
+import '../../dialog/member_retain_dialog.dart';
+import '../../handler/event_handler.dart';
 import '../../resource/string.gen.dart';
+import '../../utils/http_handler.dart';
+import '../../utils/payment_status_manager.dart';
 import '../../widget/animated_switcher_widget.dart';
 import '../browser/browser_view.dart';
 import 'member_evaluate_bean.dart';
 import 'member_fun_bean.dart';
 
 @injectable
-class MemberController extends BaseController {
+class MemberController extends BaseController implements PaymentStatusCallback {
   final AccountRepository accountRepository;
   final MemberRepository memberRepository;
+  final PaymentStatusManager paymentStatusManager;
 
   final switcherController = SwitcherController();
 
@@ -55,6 +68,10 @@ class MemberController extends BaseController {
 
   GoodsBean? get selectedGoods => _selectedGoods.value;
 
+  final Rxn<PayItemBean> _selectedPayWay = Rxn<PayItemBean>();
+
+  PayItemBean? get selectedPayWay => _selectedPayWay.value;
+
   final RxList<PayItemBean> payItemList = <PayItemBean>[].obs;
 
   CancelableFuture? _memberDataFuture;
@@ -66,7 +83,8 @@ class MemberController extends BaseController {
         StringName.memberFunName2, StringName.memberFunName2Desc),
     MemberFunBean(3, Assets.images.iconMemberFun3.path,
         StringName.memberFunName3, StringName.memberFunName3Desc),
-    // MemberFunBean(4, Assets.images.iconMemberFun4.path, StringName.memberFunName4, StringName.memberFunName4Desc), //该功能还未开发
+    MemberFunBean(4, Assets.images.iconMemberFun4.path,
+        StringName.memberFunName4, StringName.memberFunName4Desc), //该功能还未开发
     MemberFunBean(5, Assets.images.iconMemberFun5.path,
         StringName.memberFunName5, StringName.memberFunName5Desc),
     MemberFunBean(6, Assets.images.iconMemberFun6.path,
@@ -86,7 +104,8 @@ class MemberController extends BaseController {
         "用来遛狗也很方便,再也不用担心狗狗跑丢了,真是个好工具!"),
   ];
 
-  MemberController(this.accountRepository, this.memberRepository);
+  MemberController(
+      this.accountRepository, this.memberRepository, this.paymentStatusManager);
 
   @override
   void onReady() async {
@@ -156,14 +175,6 @@ class MemberController extends BaseController {
     Get.back();
   }
 
-  @override
-  void onClose() {
-    super.onClose();
-    _changeStreamController?.close();
-    _memberDataFuture?.cancel();
-    scrollController.dispose();
-  }
-
   void onLoginClick() {
     if (accountRepository.isLogin.value) {
       return;
@@ -191,6 +202,7 @@ class MemberController extends BaseController {
       }
       if (response.payInfoList?.isNotEmpty == true) {
         payItemList.addAll(response.payInfoList!);
+        _selectedPayWay.value = payItemList.first;
       }
     });
   }
@@ -206,4 +218,144 @@ class MemberController extends BaseController {
   void onTermOfServiceClick() {
     BrowserPage.start(WebUrl.userAgreement);
   }
+
+  void onPayWayItemClick(PayItemBean item) {
+    _selectedPayWay.value = item;
+  }
+
+  void onPopBack() {
+    if (accountRepository.memberIsExpired()) {
+      showRetainDialog();
+    } else {
+      back();
+    }
+  }
+
+  void showRetainDialog() {
+    MemberRetainDialog.show(payClick: () {
+      onBuyClick();
+    }, cancelClick: () {
+      back();
+    });
+  }
+
+  void onBuyClick() async {
+    if (selectedGoods == null) {
+      ToastUtil.show(StringName.memberPleaseChoiceGoods);
+      return;
+    }
+    final buyGoods = selectedGoods!;
+    if (selectedPayWay == null) {
+      ToastUtil.show(StringName.memberPleaseChoicePayment);
+      return;
+    }
+    final buyPayWay = selectedPayWay!;
+
+    int goodsId = buyGoods.id;
+    int payPlatform = buyPayWay.payPlatform;
+    int payMethod = buyPayWay.payMethod;
+    LoadingDialog.show(StringName.payLoading);
+
+    try {
+      RequestPayResponse response = await memberRepository.submitAndRequestPay(
+          goodsId: goodsId, payPlatform: payPlatform, payMethod: payMethod);
+      dynamic payInfo;
+      String outTradeNo = response.outTradeNo;
+      if (payPlatform == PayPlatform.android) {
+        if (payMethod == PayMethod.alipay) {
+          payInfo = AliPayInfo(response.alipayOrderString!);
+        } else if (payMethod == PayMethod.wechat) {
+          WechatPaymentSignBean bean =
+              WechatPaymentSignBean.stringToBean(response.wechatPayPrepayJson!);
+          payInfo = WechatPayInfo(
+              appId: bean.appId,
+              partnerId: bean.partnerId,
+              prepayId: bean.prepayId,
+              package: bean.package,
+              noncestr: bean.nonceStr,
+              timestamp: bean.timeStamp,
+              sign: bean.sign);
+        }
+      }
+      AgilePay.startPay(payInfo, success: (String? result) {
+        LoadingDialog.show(StringName.payQuerypayState);
+        checkPaymentStatus(outTradeNo, buyPayWay, buyGoods,
+            receiptData: result);
+        Future.delayed(const Duration(seconds: 30), () {
+          LoadingDialog.hide();
+        });
+      }, payError: (int error, String? errorMessage) {
+        debugPrint('zk---payError: $error, $errorMessage');
+        errorPayToast(error);
+        errorEventReport(payMethod);
+        LoadingDialog.hide();
+      }, error: (int errno, String? error) {
+        debugPrint('zk---error: $errno, $error');
+        errorPayToast(errno);
+        errorEventReport(payMethod);
+        LoadingDialog.hide();
+      });
+    } catch (error) {
+      LoadingDialog.hide();
+      if (error is ServerErrorException) {
+        if (error.code == ErrorCode.noLoginError) {
+          ToastUtil.show(StringName.accountNoLogin);
+          LoginPage.start();
+        } else {
+          ErrorHandler.toastError(error);
+        }
+      } else {
+        ToastUtil.show(StringName.payError);
+      }
+    }
+  }
+
+  void errorEventReport(int payMethod) {
+    if (payMethod == PayMethod.wechat) {
+      // EventHandler.report();
+    } else if (payMethod == PayMethod.alipay) {
+      // EventHandler.report();
+    } else if (payMethod == PayMethod.apple) {
+      // EventHandler.report();
+    }
+  }
+
+  void errorPayToast(int errno) {
+    if (errno == AgilePayCode.payCodeNotSupport) {
+      ToastUtil.show(StringName.payNotSupport);
+    } else if (errno == AgilePayCode.payCodeCancelError) {
+      ToastUtil.show(StringName.payUserCancel);
+    } else if (errno == AgilePayCode.payCodeWxEnvError) {
+      ToastUtil.show(StringName.payWxEvnError);
+    } else if (errno == AgilePayCode.payCodeNotConnectStore) {
+      ToastUtil.show(StringName.payNotConnectStore);
+    } else {
+      ToastUtil.show(StringName.payError);
+    }
+  }
+
+  void checkPaymentStatus(
+      String orderNo, PayItemBean paymentWay, GoodsBean goodsBean,
+      {String? receiptData}) {
+    paymentStatusManager.registerPaymentSuccessCallback(orderNo, this);
+    paymentStatusManager.checkPaymentStatus(orderNo, paymentWay, goodsBean,
+        receiptData: receiptData);
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+    _changeStreamController?.close();
+    _memberDataFuture?.cancel();
+    scrollController.dispose();
+    paymentStatusManager.unregisterPaymentSuccessCallback(this);
+  }
+
+  @override
+  void onPaymentSuccess(
+      String orderNo, PayItemBean paymentWay, GoodsBean storeItemBean) {
+    LoadingDialog.hide();
+
+    showPaymentSuccessDialog(onConfirm: back, onCancel: back);
+  }
 }

+ 178 - 114
lib/module/member/member_page.dart

@@ -13,9 +13,11 @@ import 'package:location/utils/common_expand.dart';
 import 'package:location/utils/project_expand.dart';
 import 'package:location/widget/auto_scroll_list_view.dart';
 import '../../data/bean/member_status_info.dart';
+import '../../data/bean/pay_item_bean.dart';
 import '../../resource/fonts.gen.dart';
 import '../../resource/string.gen.dart';
 import '../../router/app_pages.dart';
+import '../../utils/common_util.dart';
 import '../../utils/date_util.dart';
 import '../../widget/animated_switcher_widget.dart';
 import 'member_evaluate_bean.dart';
@@ -39,83 +41,90 @@ class MemberPage extends BasePage<MemberController> {
 
   @override
   Widget buildBody(BuildContext context) {
-    return Stack(
-      children: [
-        SingleChildScrollView(
-          controller: controller.scrollController,
-          child: Stack(
-            children: [
-              Assets.images.bgMemberHeader.image(width: double.infinity),
-              SafeArea(
-                child: Column(
-                  children: [
-                    SizedBox(height: 62.w),
-                    buildUserInfoView(),
-                    SizedBox(height: 26.w),
-                    Container(
-                      width: double.infinity,
-                      decoration: BoxDecoration(
-                          borderRadius: BorderRadius.only(
-                              topLeft: Radius.circular(14.w),
-                              topRight: Radius.circular(14.w)),
-                          gradient: LinearGradient(
-                              begin: Alignment.topCenter,
-                              end: Alignment.bottomCenter,
-                              stops: [0.0, 0.1],
-                              colors: ['#EFE9FF'.color, Colors.white])),
-                      child: Column(
-                        crossAxisAlignment: CrossAxisAlignment.start,
-                        children: [
-                          SizedBox(height: 23.w),
-                          buildGoodsList(),
-                          SizedBox(height: 12.w),
-                          buildPrivacyPolicyView(),
-                          SizedBox(height: 30.w),
-                          Padding(
-                            padding: EdgeInsets.only(left: 12.w),
-                            child: Text(StringName.memberEquityIntroduction,
-                                style: TextStyle(
-                                    fontSize: 16.sp,
-                                    color: ColorName.black90,
-                                    fontWeight: FontWeight.bold)),
-                          ),
-                          SizedBox(height: 19.w),
-                          buildFunctionList(),
-                          SizedBox(height: 40.w),
-                          Padding(
-                            padding: EdgeInsets.only(left: 12.w),
-                            child: Text(StringName.memberUserEvaluate,
-                                style: TextStyle(
-                                    fontSize: 16.sp,
-                                    color: ColorName.black90,
-                                    fontWeight: FontWeight.bold)),
-                          ),
-                          SizedBox(height: 8.w),
-                          buildUserEvaluateList(),
-                          SizedBox(height: 20.w),
-                          Container(
-                            padding: EdgeInsets.all(12.w),
-                            decoration: BoxDecoration(
-                                color: '#F7F7F7'.color,
-                                borderRadius: BorderRadius.circular(6.w)),
-                            margin: EdgeInsets.symmetric(horizontal: 12.w),
-                            child: Text(StringName.memberTips,
-                                style: TextStyle(
-                                    fontSize: 12.sp, color: '#A7A7A7'.color)),
-                          ),
-                          SizedBox(height: 100.w)
-                        ],
-                      ),
-                    )
-                  ],
-                ),
-              )
-            ],
+    return PopScope(
+      canPop: false,
+      onPopInvokedWithResult: (bool didPop, dynamic result) async {
+        controller.onPopBack();
+      },
+      child: Stack(
+        children: [
+          SingleChildScrollView(
+            controller: controller.scrollController,
+            child: Stack(
+              children: [
+                Assets.images.bgMemberHeader.image(width: double.infinity),
+                SafeArea(
+                  child: Column(
+                    children: [
+                      SizedBox(height: 62.w),
+                      buildUserInfoView(),
+                      SizedBox(height: 26.w),
+                      Container(
+                        width: double.infinity,
+                        decoration: BoxDecoration(
+                            borderRadius: BorderRadius.only(
+                                topLeft: Radius.circular(14.w),
+                                topRight: Radius.circular(14.w)),
+                            gradient: LinearGradient(
+                                begin: Alignment.topCenter,
+                                end: Alignment.bottomCenter,
+                                stops: [0.0, 0.1],
+                                colors: ['#EFE9FF'.color, Colors.white])),
+                        child: Column(
+                          crossAxisAlignment: CrossAxisAlignment.start,
+                          children: [
+                            SizedBox(height: 23.w),
+                            buildGoodsList(),
+                            SizedBox(height: 12.w),
+                            buildPrivacyPolicyView(),
+                            buildPayWayView(),
+                            SizedBox(height: 30.w),
+                            Padding(
+                              padding: EdgeInsets.only(left: 12.w),
+                              child: Text(StringName.memberEquityIntroduction,
+                                  style: TextStyle(
+                                      fontSize: 16.sp,
+                                      color: ColorName.black90,
+                                      fontWeight: FontWeight.bold)),
+                            ),
+                            SizedBox(height: 19.w),
+                            buildFunctionList(),
+                            SizedBox(height: 40.w),
+                            Padding(
+                              padding: EdgeInsets.only(left: 12.w),
+                              child: Text(StringName.memberUserEvaluate,
+                                  style: TextStyle(
+                                      fontSize: 16.sp,
+                                      color: ColorName.black90,
+                                      fontWeight: FontWeight.bold)),
+                            ),
+                            SizedBox(height: 8.w),
+                            buildUserEvaluateList(),
+                            SizedBox(height: 20.w),
+                            Container(
+                              padding: EdgeInsets.all(12.w),
+                              decoration: BoxDecoration(
+                                  color: '#F7F7F7'.color,
+                                  borderRadius: BorderRadius.circular(6.w)),
+                              margin: EdgeInsets.symmetric(horizontal: 12.w),
+                              child: Text(StringName.memberTips,
+                                  style: TextStyle(
+                                      fontSize: 12.sp, color: '#A7A7A7'.color)),
+                            ),
+                            SizedBox(height: 100.w)
+                          ],
+                        ),
+                      )
+                    ],
+                  ),
+                )
+              ],
+            ),
           ),
-        ),
-        buildHeadBar(),
-        buildMemberBottomView()
-      ],
+          buildHeadBar(),
+          buildMemberBottomView()
+        ],
+      ),
     );
   }
 
@@ -246,7 +255,8 @@ class MemberPage extends BasePage<MemberController> {
         child: Align(
           alignment: Alignment.bottomCenter,
           child: Container(
-            padding: EdgeInsets.only(left: 12.w, right: 12.w, top: 13.w, bottom: 20.w),
+            padding: EdgeInsets.only(
+                left: 12.w, right: 12.w, top: 13.w, bottom: 20.w),
             decoration: BoxDecoration(
               color: Colors.white,
               boxShadow: [
@@ -279,21 +289,24 @@ class MemberPage extends BasePage<MemberController> {
                     style: TextStyle(fontSize: 12.sp, color: '#000000'.color),
                   ),
                   Spacer(),
-                  Container(
-                    decoration: BoxDecoration(
-                        color: ColorName.colorPrimary,
-                        borderRadius: BorderRadius.circular(100.w)),
-                    width: 164.w,
-                    height: 44.w,
-                    child: Center(
-                      child: Text(
-                        controller.memberStatusInfo?.expired == false
-                            ? StringName.memberVipRenew
-                            : StringName.memberVipUnlock,
-                        style: TextStyle(
-                            fontSize: 15.sp,
-                            color: Colors.white,
-                            fontWeight: FontWeight.bold),
+                  GestureDetector(
+                    onTap: controller.onBuyClick,
+                    child: Container(
+                      decoration: BoxDecoration(
+                          color: ColorName.colorPrimary,
+                          borderRadius: BorderRadius.circular(100.w)),
+                      width: 164.w,
+                      height: 44.w,
+                      child: Center(
+                        child: Text(
+                          controller.memberStatusInfo?.expired == false
+                              ? StringName.memberVipRenew
+                              : StringName.memberVipUnlock,
+                          style: TextStyle(
+                              fontSize: 15.sp,
+                              color: Colors.white,
+                              fontWeight: FontWeight.bold),
+                        ),
                       ),
                     ),
                   )
@@ -381,7 +394,7 @@ class MemberPage extends BasePage<MemberController> {
               Positioned(
                   left: 12.w,
                   child: GestureDetector(
-                    onTap: controller.back,
+                    onTap: () => controller.onPopBack(),
                     child: Assets.images.iconWhiteBack
                         .image(width: 24.w, height: 24.w),
                   )),
@@ -414,27 +427,27 @@ class MemberPage extends BasePage<MemberController> {
           text: TextSpan(
               style: TextStyle(fontSize: 12.sp, color: ColorName.black40),
               children: [
-                TextSpan(text: '购买前请先阅读'),
-                TextSpan(
-                    recognizer: TapGestureRecognizer()
-                      ..onTap = () {
-                        controller.onPrivacyPolicyClick();
-                      },
-                    text: '隐私政策',
-                    style: TextStyle(
-                        color: ColorName.black60,
-                        decoration: TextDecoration.underline)),
-                TextSpan(text: '&'),
-                TextSpan(
-                    recognizer: TapGestureRecognizer()
-                      ..onTap = () {
-                        controller.onTermOfServiceClick();
-                      },
-                    text: '服务条款',
-                    style: TextStyle(
-                        color: ColorName.black60,
-                        decoration: TextDecoration.underline)),
-              ])),
+            TextSpan(text: '购买前请先阅读'),
+            TextSpan(
+                recognizer: TapGestureRecognizer()
+                  ..onTap = () {
+                    controller.onPrivacyPolicyClick();
+                  },
+                text: '隐私政策',
+                style: TextStyle(
+                    color: ColorName.black60,
+                    decoration: TextDecoration.underline)),
+            TextSpan(text: '&'),
+            TextSpan(
+                recognizer: TapGestureRecognizer()
+                  ..onTap = () {
+                    controller.onTermOfServiceClick();
+                  },
+                text: '服务条款',
+                style: TextStyle(
+                    color: ColorName.black60,
+                    decoration: TextDecoration.underline)),
+          ])),
     );
   }
 
@@ -513,4 +526,55 @@ class MemberPage extends BasePage<MemberController> {
       ],
     );
   }
+
+  Widget buildPayWayView() {
+    return Obx(() {
+      return Visibility(
+        visible: controller.payItemList.isNotEmpty,
+        child: Container(
+          margin: EdgeInsets.only(top: 7.w),
+          child: Column(
+            children: [
+              for (PayItemBean item in controller.payItemList)
+                buildPayWayItem(item)
+            ],
+          ),
+        ),
+      );
+    });
+  }
+
+  Widget buildPayWayItem(PayItemBean item) {
+    return GestureDetector(
+      behavior: HitTestBehavior.translucent,
+      onTap: () => controller.onPayWayItemClick(item),
+      child: Obx(() {
+        bool isSelected = controller.selectedPayWay?.id == item.id;
+        return Container(
+          padding: EdgeInsets.symmetric(vertical: 10.w),
+          child: Row(
+            children: [
+              SizedBox(width: 12.w),
+              Image.asset(
+                  getPaymentIconPath(
+                      payMethod: item.payMethod, payPlatform: item.payPlatform),
+                  width: 24.w,
+                  height: 24.w),
+              SizedBox(width: 6.w),
+              Text(item.title,
+                  style: TextStyle(fontSize: 14.sp, color: ColorName.black90)),
+              Spacer(),
+              Image.asset(
+                  isSelected
+                      ? Assets.images.iconCbSelected.path
+                      : Assets.images.iconCbUnSelect.path,
+                  width: 20.w,
+                  height: 20.w),
+              SizedBox(width: 20.w),
+            ],
+          ),
+        );
+      }),
+    );
+  }
 }

+ 41 - 1
lib/resource/assets.gen.dart

@@ -68,10 +68,26 @@ class $AssetsImagesGen {
   AssetGenImage get iconAgreementClose =>
       const AssetGenImage('assets/images/icon_agreement_close.webp');
 
+  /// File path: assets/images/icon_alipay_payment.webp
+  AssetGenImage get iconAlipayPayment =>
+      const AssetGenImage('assets/images/icon_alipay_payment.webp');
+
+  /// File path: assets/images/icon_alipay_scan_payment.webp
+  AssetGenImage get iconAlipayScanPayment =>
+      const AssetGenImage('assets/images/icon_alipay_scan_payment.webp');
+
   /// File path: assets/images/icon_black_back.webp
   AssetGenImage get iconBlackBack =>
       const AssetGenImage('assets/images/icon_black_back.webp');
 
+  /// File path: assets/images/icon_cb_selected.webp
+  AssetGenImage get iconCbSelected =>
+      const AssetGenImage('assets/images/icon_cb_selected.webp');
+
+  /// File path: assets/images/icon_cb_un_select.webp
+  AssetGenImage get iconCbUnSelect =>
+      const AssetGenImage('assets/images/icon_cb_un_select.webp');
+
   /// File path: assets/images/icon_checkbox_selected.webp
   AssetGenImage get iconCheckboxSelected =>
       const AssetGenImage('assets/images/icon_checkbox_selected.webp');
@@ -228,6 +244,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconMemberFun6 =>
       const AssetGenImage('assets/images/icon_member_fun_6.webp');
 
+  /// File path: assets/images/icon_member_retain_close.webp
+  AssetGenImage get iconMemberRetainClose =>
+      const AssetGenImage('assets/images/icon_member_retain_close.webp');
+
   /// File path: assets/images/icon_member_vip_receive_arrow.webp
   AssetGenImage get iconMemberVipReceiveArrow =>
       const AssetGenImage('assets/images/icon_member_vip_receive_arrow.webp');
@@ -328,6 +348,14 @@ class $AssetsImagesGen {
   AssetGenImage get iconVip =>
       const AssetGenImage('assets/images/icon_vip.webp');
 
+  /// File path: assets/images/icon_wechat_payment.webp
+  AssetGenImage get iconWechatPayment =>
+      const AssetGenImage('assets/images/icon_wechat_payment.webp');
+
+  /// File path: assets/images/icon_wechat_scan_payment.webp
+  AssetGenImage get iconWechatScanPayment =>
+      const AssetGenImage('assets/images/icon_wechat_scan_payment.webp');
+
   /// File path: assets/images/icon_white_back.webp
   AssetGenImage get iconWhiteBack =>
       const AssetGenImage('assets/images/icon_white_back.webp');
@@ -344,6 +372,10 @@ class $AssetsImagesGen {
   AssetGenImage get imgDialogLocationAlwaysTip3 => const AssetGenImage(
       'assets/images/img_dialog_location_always_tip_3.webp');
 
+  /// File path: assets/images/img_member_retain_container.webp
+  AssetGenImage get imgMemberRetainContainer =>
+      const AssetGenImage('assets/images/img_member_retain_container.webp');
+
   /// List of all assets
   List<AssetGenImage> get values => [
         bgAddFriendDialog,
@@ -360,7 +392,11 @@ class $AssetsImagesGen {
         bgUrgentContactLogo,
         bgUrgentContactPopup,
         iconAgreementClose,
+        iconAlipayPayment,
+        iconAlipayScanPayment,
         iconBlackBack,
+        iconCbSelected,
+        iconCbUnSelect,
         iconCheckboxSelected,
         iconCheckboxUnSelect,
         iconDefaultFriendAvatar,
@@ -400,6 +436,7 @@ class $AssetsImagesGen {
         iconMemberFun3,
         iconMemberFun4,
         iconMemberFun6,
+        iconMemberRetainClose,
         iconMemberVipReceiveArrow,
         iconMessageFriendHelp,
         iconMineFunAbout,
@@ -425,10 +462,13 @@ class $AssetsImagesGen {
         iconUrgentContactDialPhone,
         iconUrgentContactMore,
         iconVip,
+        iconWechatPayment,
+        iconWechatScanPayment,
         iconWhiteBack,
         imgDialogLocationAlwaysTip1,
         imgDialogLocationAlwaysTip2,
-        imgDialogLocationAlwaysTip3
+        imgDialogLocationAlwaysTip3,
+        imgMemberRetainContainer
       ];
 }
 

+ 30 - 2
lib/resource/string.gen.dart

@@ -194,7 +194,8 @@ class StringName {
   static final String logoutAccountContent = 'logout_account_content'.tr; // 1.删除账号所有账号信息数据和定位记录;\n2.删除并放弃账号下的会员权益;\n3.点击“确认注销”即开始注销流程不可撤回,请慎重考虑。
   static final String logoutAccount = 'logout_account'.tr; // 注销账号
   static final String accountLogoutSuccess = 'account_logout_success'.tr; // 注销成功
-  static final String recordNumber = 'record_number'.tr; // 备案号:皖ICP备2023011908号-2A
+  static final String recordNumber =
+      'record_number'.tr; // 备案号:皖ICP备2024057362号-19A
   static final String permissionSetting = 'permission_setting'.tr; // 快速设置
   static final String permissionLocationSetting = 'permission_location_setting'.tr; // 定位权限开启
   static final String permissionLocationSettingSubtitle = 'permission_location_setting_subtitle'.tr; // 定位权限需要设置为本应用选择“始终允许”,才可以正常查看轨迹。
@@ -220,6 +221,21 @@ class StringName {
   static final String memberFunName6Desc = 'member_fun_name_6_desc'.tr; // 一对一服务
   static final String memberUserEvaluate = 'member_user_evaluate'.tr; // 用户评价
   static final String memberTips = 'member_tips'.tr; // 本应用功能仅限于家庭成员和亲人朋友之间使用,根据相关法规和隐私协议规定,共享位置功能需要对方下载得到好友授权同意才能正常使用。
+  static final String memberContinuePay = 'member_continue_pay'.tr; // 继续支付
+  static final String memberPleaseChoiceGoods =
+      'member_please_choice_goods'.tr; // 请选择支付商品
+  static final String memberPleaseChoicePayment =
+      'member_please_choice_payment'.tr; // 请选择支付方式
+  static final String payLoading = 'pay_loading'.tr; // 请求中...
+  static final String payUserCancel = 'pay_user_cancel'.tr; // 用户取消支付
+  static final String payNotSupport = 'pay_not_support'.tr; // 不支持该支付类型
+  static final String payWxEvnError = 'pay_wx_evn_error'.tr; // 微信未安装或微信版本不支持
+  static final String payError = 'pay_error'.tr; // 支付失败,请稍后重试
+  static final String payQuerypayState = 'pay_query_pay_state'.tr; // 正在查询订单状态..
+  static final String payNotConnectStore =
+      'pay_not_connect_store'.tr; // 无法连接到商店
+  static final String paySuccessTitle = 'pay_success_title'.tr; // 支付成功
+  static final String paySuccessDesc = 'pay_success_desc'.tr; // 您的订单已成功支付
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -417,7 +433,7 @@ class StringMultiSource {
       'logout_account_content': '1.删除账号所有账号信息数据和定位记录;\n2.删除并放弃账号下的会员权益;\n3.点击“确认注销”即开始注销流程不可撤回,请慎重考虑。',
       'logout_account': '注销账号',
       'account_logout_success': '注销成功',
-      'record_number': '备案号:皖ICP备2023011908号-2A',
+      'record_number': '备案号:皖ICP备2024057362号-19A',
       'permission_setting': '快速设置',
       'permission_location_setting': '定位权限开启',
       'permission_location_setting_subtitle': '定位权限需要设置为本应用选择“始终允许”,才可以正常查看轨迹。',
@@ -443,6 +459,18 @@ class StringMultiSource {
       'member_fun_name_6_desc': '一对一服务',
       'member_user_evaluate': '用户评价',
       'member_tips': '本应用功能仅限于家庭成员和亲人朋友之间使用,根据相关法规和隐私协议规定,共享位置功能需要对方下载得到好友授权同意才能正常使用。',
+      'member_continue_pay': '继续支付',
+      'member_please_choice_goods': '请选择支付商品',
+      'member_please_choice_payment': '请选择支付方式',
+      'pay_loading': '请求中...',
+      'pay_user_cancel': '用户取消支付',
+      'pay_not_support': '不支持该支付类型',
+      'pay_wx_evn_error': '微信未安装或微信版本不支持',
+      'pay_error': '支付失败,请稍后重试',
+      'pay_query_pay_state': '正在查询订单状态..',
+      'pay_not_connect_store': '无法连接到商店',
+      'pay_success_title': '支付成功',
+      'pay_success_desc': '您的订单已成功支付',
     },
   };
 }

+ 120 - 0
lib/sdk/gravity/gravity_helper.dart

@@ -0,0 +1,120 @@
+import 'dart:async';
+
+import 'package:flutter/widgets.dart';
+import 'package:gravity_engine/gravity_engine.dart';
+import 'package:gravity_engine/gravity_engine_method_channel.dart';
+import 'package:location/data/repositories/account_repository.dart';
+import '../../data/api/response/member_status_response.dart';
+import '../../data/bean/member_status_info.dart';
+import '../../data/consts/build_config.dart';
+import '../../data/consts/payment_type.dart';
+import '../../device/atmob_platform_info.dart';
+import '../../utils/async_util.dart';
+import '../../utils/mmkv_util.dart';
+
+typedef GravitySuccessCallback = void Function();
+
+class GravityHelper {
+  static const String _keyCurrentSsid = "current_ssid";
+  static CancelableFuture<bool>? _initFuture;
+  static String? _currentClientId;
+
+  static bool? _isFromPromote;
+
+  static init() async {
+    _initialize(false);
+  }
+
+  static _initialize(bool refreshSSID,
+      {GravitySuccessCallback? callback}) async {
+    if (_initFuture != null) {
+      _initFuture?.cancel();
+    }
+    _initFuture = AsyncUtil.retryWithExponentialBackoff<bool>(() async {
+      String ssid = await _getSSID(refreshSSID);
+      CancelableFuture<bool> initFuture = _gravityInitialize(ssid);
+      _initFuture = initFuture;
+      return await initFuture;
+    }, 5);
+    _initFuture?.then((isPromote) {
+      _isFromPromote = isPromote;
+      callback?.call();
+    }).catchError((error) {
+      _initialize(refreshSSID, callback: callback);
+    });
+  }
+
+  static Future<String> _getSSID(bool refreshSSID) async {
+    String? currentSSID = _getCurrentSSID();
+    if (currentSSID == null || refreshSSID) {
+      MemberStatusResponse response =
+          await AccountRepository.getInstance().getMemberStatus();
+      KVUtil.putString(_keyCurrentSsid, response.deviceId);
+      return response.deviceId;
+    }
+    return currentSSID;
+  }
+
+  static String? _getCurrentSSID() {
+    return KVUtil.getString(_keyCurrentSsid, null);
+  }
+
+  static CancelableFuture<bool> _gravityInitialize(String ssid) {
+    return AsyncUtil.retryWithExponentialBackoff<bool>(() {
+      return GravityEngine.initialize(
+              GravityConfig.gravityAppId,
+              GravityConfig.gravityAccessToken,
+              ssid,
+              atmobPlatformInfo.channelName ?? '',
+              BuildConfig.isDebug)
+          .then((data) {
+        debugPrint('gravity initialize($ssid) success');
+        GravityHelper._currentClientId = ssid;
+        return data;
+      });
+    }, 5);
+  }
+
+  static void onLogin() {
+    _clearCurrentSSID();
+    _initialize(true, callback: () {
+      if (_currentClientId != null) {
+        GravityEngine.login(_currentClientId!);
+      }
+    });
+  }
+
+  static void onLogout() async {
+    _currentClientId = null;
+    await GravityEngine.logout();
+    _initialize(false, callback: null);
+  }
+
+  static void _clearCurrentSSID() {
+    KVUtil.putString(_keyCurrentSsid, null);
+  }
+
+  static bool? getIsFromPromote() {
+    return _isFromPromote;
+  }
+
+  static void report(String eventId, {Map<String, dynamic>? params}) {
+    GravityEngine.trackEvent(eventId, eventProperties: params);
+  }
+
+  static void reportPay(
+      int payAmount, String orderNo, String productName, int payWay) {
+    PayType payType;
+    if (payWay == PayMethod.alipay) {
+      payType = PayType.alipay;
+    } else if (payWay == PayMethod.wechat) {
+      payType = PayType.wechat;
+    } else if (payWay == PayMethod.apple) {
+      payType = PayType.apple;
+    } else {
+      payType = PayType.unknown;
+    }
+    GravityEngine.trackPay(
+        orderNo, productName, payAmount, Currency.cny, payType);
+  }
+}

+ 1 - 1
lib/sdk/wechat/wechat_helper.dart

@@ -7,7 +7,7 @@ import '../../data/consts/build_config.dart';
 class WechatHelper {
   WechatHelper._();
 
-  static final appId = BuildConfig.wechatAppId;
+  static final appId = WechatConfig.wechatAppId;
   static final String universalLink = WebUrl.shareUrl;
 
   static Future<void> registerApp() {

+ 1 - 2
lib/sdk/wechat/wechat_share_util.dart

@@ -18,8 +18,7 @@ class WechatShareUtil {
         webpageUrl: WebUrl.shareUrl,
         title: StringName.shareFriendTitle,
         description: StringName.shareFriendDesc,
-        thumbData:
-            await assetImageToBytes(Assets.images.iconMainMapClock.path));
+        thumbData: await assetImageToBytes(Assets.images.iconLogoMax.path));
   }
 
   static Future<Uint8List> assetImageToBytes(String assetPath) async {

+ 16 - 0
lib/utils/common_util.dart

@@ -1,4 +1,7 @@
 import 'package:flutter/services.dart';
+import 'package:location/resource/assets.gen.dart';
+
+import '../data/consts/payment_type.dart';
 
 /// 截取后几位
 String stringExpand(String phone) {
@@ -66,3 +69,16 @@ String addressCheck(String? address) {
 void copyToClipboard(String content) {
   Clipboard.setData(ClipboardData(text: content));
 }
+
+String getPaymentIconPath({required int payMethod, required int payPlatform}) {
+  if (payPlatform == 1 && payMethod == 2) {
+    return Assets.images.iconWechatPayment.path;
+  } else if (payPlatform == 4 && payMethod == 2) {
+    return Assets.images.iconWechatScanPayment.path;
+  } else if (payPlatform == 1 && payMethod == 1) {
+    return Assets.images.iconAlipayPayment.path;
+  } else if (payPlatform == 4 && payMethod == 1) {
+    return Assets.images.iconAlipayScanPayment.path;
+  }
+  return '';
+}

+ 119 - 0
lib/utils/payment_status_manager.dart

@@ -0,0 +1,119 @@
+import 'package:flutter/cupertino.dart';
+import 'package:injectable/injectable.dart';
+import 'package:location/data/bean/goods_bean.dart';
+import 'package:location/data/bean/pay_item_bean.dart';
+import 'package:location/data/repositories/account_repository.dart';
+import 'package:location/data/repositories/member_repository.dart';
+import 'package:synchronized/synchronized.dart';
+
+import '../handler/event_handler.dart';
+import 'async_util.dart';
+
+@lazySingleton
+class PaymentStatusManager {
+  final MemberRepository memberRepository;
+  final AccountRepository accountRepository;
+
+  PaymentStatusManager(this.memberRepository, this.accountRepository);
+
+  //订单状态
+  //0-查询失败,继续轮询
+  //1-未支付,继续轮询
+  //2-支付成功
+  //3-支付关闭
+  //4-已退款
+  static const int payStatusFail = 0;
+  static const int payStatusUnpaid = 1;
+  static const int payStatusSuccess = 2;
+  static const int payStatusClose = 3;
+  static const int payStatusRefund = 4;
+
+  final Map<String, PaymentStatusCallback> callbackMap = {};
+  final Map<String, CancelableFuture> pollingSubscriptionMap = {};
+  final _lock = Lock();
+
+  void _startCheckPolling(
+      String orderNo, PayItemBean paymentWay, GoodsBean storeItemBean,
+      {String? receiptData}) async {
+    await _lock.synchronized(() async {
+      pollingSubscriptionMap[orderNo]?.cancel();
+      debugPrint('开始轮询支付状态: orderNo = $orderNo');
+      CancelableFuture orderFuture = AsyncUtil.retryWithExponentialBackoff(
+          () {
+            return memberRepository
+                .orderStatus(orderNo, receiptData: receiptData)
+                .then((status) {
+              if (status == payStatusSuccess) {
+                return true;
+              } else {
+                throw PaymentStatusException(status);
+              }
+            });
+          },
+          10,
+          predicate: (error) {
+            if (error is PaymentStatusException) {
+              return error.status == payStatusFail ||
+                  error.status == payStatusUnpaid;
+            }
+            return true;
+          });
+      orderFuture.then((data) async {
+        debugPrint('支付成功: orderNo = $orderNo');
+        accountRepository.refreshMemberStatus();
+        await _lock.synchronized(() {
+          callbackMap[orderNo]
+              ?.onPaymentSuccess(orderNo, paymentWay, storeItemBean);
+          callbackMap.remove(orderNo);
+        });
+        reportPaySuccess(storeItemBean.amount, orderNo, storeItemBean.name,
+            paymentWay.payMethod);
+      }).catchError((error) {
+        debugPrint('支付失败: orderNo = $orderNo, error = $error');
+      });
+      pollingSubscriptionMap[orderNo] = orderFuture;
+    });
+  }
+
+  void reportPaySuccess(
+      int price, String orderId, String itemName, int paymentWay) {
+    EventHandler.reportPay(price, orderId, itemName, paymentWay);
+  }
+
+  void checkPaymentStatus(
+      String orderNo, PayItemBean paymentWay, GoodsBean storeItemBean,
+      {String? receiptData}) {
+    // recordKeyInfoToDisk(orderNo, paymentWay, storeItemBean);
+    _startCheckPolling(orderNo, paymentWay, storeItemBean,
+        receiptData: receiptData);
+  }
+
+  void registerPaymentSuccessCallback(
+      String orderNo, PaymentStatusCallback callback) async {
+    await _lock.synchronized(() {
+      callbackMap[orderNo] = callback;
+    });
+  }
+
+  void unregisterPaymentSuccessCallback(PaymentStatusCallback callback) async {
+    await _lock.synchronized(() {
+      callbackMap.removeWhere((key, value) => value == callback);
+    });
+  }
+}
+
+class PaymentStatusException implements Exception {
+  final int status;
+
+  PaymentStatusException(this.status);
+
+  @override
+  String toString() {
+    return '支付状态异常: status = $status';
+  }
+}
+
+abstract class PaymentStatusCallback {
+  void onPaymentSuccess(
+      String orderNo, PayItemBean paymentWay, GoodsBean storeItemBean);
+}

+ 29 - 0
plugins/agile_pay/.gitignore

@@ -0,0 +1,29 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+build/

+ 10 - 0
plugins/agile_pay/.metadata

@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: "80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819"
+  channel: "stable"
+
+project_type: package

+ 3 - 0
plugins/agile_pay/CHANGELOG.md

@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.

+ 1 - 0
plugins/agile_pay/LICENSE

@@ -0,0 +1 @@
+TODO: Add your license here.

+ 39 - 0
plugins/agile_pay/README.md

@@ -0,0 +1,39 @@
+<!--
+This README describes the package. If you publish this package to pub.dev,
+this README's contents appear on the landing page for your package.
+
+For information about how to write a good package README, see the guide for
+[writing package pages](https://dart.dev/tools/pub/writing-package-pages).
+
+For general information about developing packages, see the Dart guide for
+[creating packages](https://dart.dev/guides/libraries/create-packages)
+and the Flutter guide for
+[developing packages and plugins](https://flutter.dev/to/develop-packages).
+-->
+
+TODO: Put a short description of the package here that helps potential users
+know whether this package might be useful for them.
+
+## Features
+
+TODO: List what your package can do. Maybe include images, gifs, or videos.
+
+## Getting started
+
+TODO: List prerequisites and provide or point to information on how to
+start using the package.
+
+## Usage
+
+TODO: Include short and useful examples for package users. Add longer examples
+to `/example` folder.
+
+```dart
+const like = 'sample';
+```
+
+## Additional information
+
+TODO: Tell users more about the package: where to find more information, how to
+contribute to the package, how to file issues, what response they can expect
+from the package authors, and more.

+ 4 - 0
plugins/agile_pay/analysis_options.yaml

@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options

+ 4 - 0
plugins/agile_pay/lib/flutter_pay.dart

@@ -0,0 +1,4 @@
+export 'package:agile_pay/src/alipay/ali_pay_info.dart';
+export 'package:agile_pay/src/wxpay/wechat_pay_info.dart';
+export 'package:agile_pay/src/agile_pay.dart';
+export 'package:agile_pay/src/code/agile_pay_code.dart';

+ 46 - 0
plugins/agile_pay/lib/src/agile_pay.dart

@@ -0,0 +1,46 @@
+import 'package:agile_pay/src/listener/i_agile_pay.dart';
+import 'package:agile_pay/src/wxpay/wechat_pay.dart';
+import 'package:agile_pay/src/wxpay/wechat_pay_info.dart';
+
+import 'alipay/ali_pay_info.dart';
+import 'alipay/alipay.dart';
+import 'applepay/apple_pay.dart';
+import 'applepay/apple_pay_info.dart';
+import 'code/agile_pay_code.dart';
+import 'googlepay/google_pay.dart';
+import 'googlepay/google_pay_info.dart';
+import 'listener/agile_pay_state.dart';
+
+class AgilePay {
+  static IAgilePay? realPay;
+
+  static void startPay(dynamic payInfo,
+      {required AgilePaySuccess success,
+      required AgilePayError payError,
+      required AgileError error,
+      AgilePayBefore? before}) {
+    IAgilePay? iAgilePay;
+
+    if (payInfo is AliPayInfo) {
+      iAgilePay = Alipay(payInfo);
+    } else if (payInfo is WechatPayInfo) {
+      iAgilePay = WechatPay(payInfo);
+    } else if (payInfo is ApplePayInfo) {
+      iAgilePay = ApplePay(payInfo);
+    } else if (payInfo is GooglePayInfo) {
+      iAgilePay = GooglePay(payInfo);
+    }
+    realPay = iAgilePay;
+    if (iAgilePay != null) {
+      iAgilePay.setPayListener(AgilePayStateImpl(
+          paySuccessListener: success,
+          payErrorListener: payError,
+          errorListener: error,
+          payBeforeListener: before));
+      iAgilePay.pay();
+    } else {
+      payError(AgilePayCode.payCodeNotSupport,
+          AgilePayCode.getMessageByCode(AgilePayCode.payCodeNotSupport));
+    }
+  }
+}

+ 7 - 0
plugins/agile_pay/lib/src/alipay/ali_pay_info.dart

@@ -0,0 +1,7 @@
+class AliPayInfo {
+  final String _payInfo;
+
+  AliPayInfo(this._payInfo);
+
+  String get payInfo => _payInfo;
+}

+ 58 - 0
plugins/agile_pay/lib/src/alipay/alipay.dart

@@ -0,0 +1,58 @@
+import 'dart:async';
+
+import 'package:flutter/widgets.dart';
+
+import '../assist/agile_pay_state_info.dart';
+import '../code/agile_pay_code.dart';
+import '../listener/i_agile_pay.dart';
+import 'ali_pay_info.dart';
+import 'package:alipay_kit/alipay_kit.dart';
+
+class Alipay extends AgilePayStateInfo implements IAgilePay {
+  final AliPayInfo _aliPayInfo;
+
+  late final StreamSubscription<AlipayResp> _paySubs;
+
+  Alipay(this._aliPayInfo) {
+    _paySubs = AlipayKitPlatform.instance.payResp().listen(_listenPay);
+  }
+
+  void _listenPay(AlipayResp resp) {
+    final String content = 'pay: ${resp.resultStatus} - ${resp.result}';
+    debugPrint('agilePay-alipay---> $content');
+    try {
+      int code = AgilePayCode.payCodeOtherError;
+      if (resp.resultStatus == AgilePayCode.payCodeAlipaySuccess) {
+        sendPaySuccess(resp.result);
+      } else {
+        if (resp.resultStatus != null &&
+            AgilePayCode.resultStatus.containsKey(resp.resultStatus)) {
+          code = resp.resultStatus!;
+        }
+        sendPayError(code, AgilePayCode.getMessageByCode(code));
+      }
+    } catch (e) {
+      sendError(AgilePayCode.payCodeOtherError,
+          AgilePayCode.getMessageByCode(AgilePayCode.payCodeOtherError));
+    } finally {
+      dispose();
+    }
+  }
+
+  @override
+  void pay() {
+    sendPayBefore();
+    try {
+      AlipayKitPlatform.instance.pay(
+        orderInfo: _aliPayInfo.payInfo,
+      );
+    } catch (e) {
+      sendError(AgilePayCode.payCodePayError,
+          AgilePayCode.getMessageByCode(AgilePayCode.payCodePayError));
+    }
+  }
+
+  void dispose() {
+    _paySubs.cancel();
+  }
+}

+ 6 - 0
plugins/agile_pay/lib/src/applepay/apple_pay.dart

@@ -0,0 +1,6 @@
+import '../assist/apple_or_google_pay.dart';
+import '../listener/i_agile_pay.dart';
+
+class ApplePay extends AppleOrGooglePay implements IAgilePay {
+  ApplePay(super.payInfo);
+}

+ 5 - 0
plugins/agile_pay/lib/src/applepay/apple_pay_info.dart

@@ -0,0 +1,5 @@
+import '../assist/apple_or_google_pay_info.dart';
+
+class ApplePayInfo extends AppleOrGooglePayInfo {
+  ApplePayInfo(super.productId, super.type, super.accountToken);
+}

+ 33 - 0
plugins/agile_pay/lib/src/assist/agile_pay_state_info.dart

@@ -0,0 +1,33 @@
+import '../listener/agile_pay_state.dart';
+
+abstract class AgilePayStateInfo {
+  AgilePayState? mPay;
+
+  void sendError(int errno, String? error) {
+    if (mPay != null) {
+      mPay?.error(errno, error);
+    }
+  }
+
+  void sendPaySuccess(String? result) {
+    if (mPay != null) {
+      mPay?.paySuccess(result);
+    }
+  }
+
+  void sendPayError(int errno, String? error) {
+    if (mPay != null) {
+      mPay?.payError(errno, error);
+    }
+  }
+
+  void sendPayBefore() {
+    if (mPay != null) {
+      mPay?.payBefore();
+    }
+  }
+
+  void setPayListener(AgilePayState pay) {
+    mPay = pay;
+  }
+}

+ 149 - 0
plugins/agile_pay/lib/src/assist/apple_or_google_pay.dart

@@ -0,0 +1,149 @@
+import 'dart:async';
+
+import 'package:agile_pay/src/assist/product_type.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:in_app_purchase/in_app_purchase.dart';
+
+import '../code/agile_pay_code.dart';
+import 'agile_pay_state_info.dart';
+import 'apple_or_google_pay_info.dart';
+
+abstract class AppleOrGooglePay extends AgilePayStateInfo {
+  final AppleOrGooglePayInfo _payInfo;
+
+  late final StreamSubscription<List<PurchaseDetails>>
+      _purchaseUpdatedSubscription;
+  late final InAppPurchase _iap;
+
+  final Duration timeout = const Duration(seconds: 10);
+
+  AppleOrGooglePay(this._payInfo) {
+    _iap = InAppPurchase.instance;
+    _purchaseUpdatedSubscription =
+        _iap.purchaseStream.listen((purchaseDetailsList) {
+      listenToPurchaseUpdated(purchaseDetailsList);
+    }, onDone: () {
+      dispose();
+    }, onError: (error) {});
+  }
+
+  void pay() async {
+    sendPayBefore();
+    try {
+      final bool isAe = await isAvailable();
+      if (!isAe) {
+        sendError(AgilePayCode.payCodeNotConnectStore,
+            AgilePayCode.getMessageByCode(AgilePayCode.payCodeNotConnectStore));
+        return;
+      }
+      final ProductDetailsResponse response =
+          await queryProductDetails({_payInfo.productId});
+      if (response.notFoundIDs.isNotEmpty || response.productDetails.isEmpty) {
+        sendError(
+            AgilePayCode.payCodeProductNotFindStore,
+            AgilePayCode.getMessageByCode(
+                AgilePayCode.payCodeProductNotFindStore));
+        return;
+      }
+      List<ProductDetails> products = response.productDetails;
+      for (var element in products) {
+        bool isSend;
+        if (_payInfo.type == ProductType.consumable) {
+          isSend = await buyConsumable(
+              purchaseParam: PurchaseParam(
+                  productDetails: element,
+                  applicationUserName: _payInfo.accountToken));
+        } else {
+          isSend = await buyNonConsumable(
+              purchaseParam: PurchaseParam(
+                  productDetails: element,
+                  applicationUserName: _payInfo.accountToken));
+        }
+        if (!isSend) {
+          sendError(
+              AgilePayCode.payCodeRequestSendError,
+              AgilePayCode.getMessageByCode(
+                  AgilePayCode.payCodeRequestSendError));
+          return;
+        }
+        return;
+      }
+    } catch (e) {
+      if (e is TimeoutException) {
+        sendError(AgilePayCode.payCodeNotConnectStore,
+            AgilePayCode.getMessageByCode(AgilePayCode.payCodeNotConnectStore));
+        return;
+      }
+      sendError(AgilePayCode.payCodeOtherError,
+          AgilePayCode.getMessageByCode(AgilePayCode.payCodeOtherError));
+      return;
+    }
+  }
+
+  Future<bool> isAvailable() {
+    return _iap.isAvailable().timeout(timeout);
+  }
+
+  Future<ProductDetailsResponse> queryProductDetails(Set<String> identifiers) {
+    return InAppPurchase.instance.queryProductDetails(identifiers);
+  }
+
+  Future<bool> buyConsumable({required PurchaseParam purchaseParam}) {
+    return _iap.buyConsumable(purchaseParam: purchaseParam);
+  }
+
+  Future<bool> buyNonConsumable({required PurchaseParam purchaseParam}) {
+    return _iap.buyNonConsumable(purchaseParam: purchaseParam);
+  }
+
+  void dispose() {
+    _purchaseUpdatedSubscription.cancel();
+  }
+
+  void listenToPurchaseUpdated(
+      List<PurchaseDetails> purchaseDetailsList) async {
+    // try {
+    for (var purchaseDetails in purchaseDetailsList) {
+      debugPrint(
+          'agilePay-purchasePay--PurchaseUpdated-> ${purchaseDetails.toString()}');
+      if (purchaseDetails.status == PurchaseStatus.pending) {
+        verifyPendingPurchase(purchaseDetails);
+      } else {
+        if (purchaseDetails.status == PurchaseStatus.error) {
+          verifyErrorPurchase(purchaseDetails);
+        } else if (purchaseDetails.status == PurchaseStatus.purchased) {
+          verifySuccessPurchase(purchaseDetails);
+        } else if (purchaseDetails.status == PurchaseStatus.canceled) {
+          verifyCancelPurchase(purchaseDetails);
+        }
+
+        if (purchaseDetails.pendingCompletePurchase) {
+          await InAppPurchase.instance.completePurchase(purchaseDetails);
+          dispose();
+        }
+      }
+    }
+    // } catch (e) {
+    //   sendError(AgilePayCode.payCodeOtherError,
+    //       AgilePayCode.getMessageByCode(AgilePayCode.payCodeOtherError));
+    // } finally {
+    //   dispose();
+    // }
+  }
+
+  void verifyPendingPurchase(PurchaseDetails purchaseDetails) {}
+
+  void verifyCancelPurchase(PurchaseDetails purchaseDetails) {
+    sendError(AgilePayCode.payCodeCancelError,
+        AgilePayCode.getMessageByCode(AgilePayCode.payCodeCancelError));
+  }
+
+  void verifyErrorPurchase(PurchaseDetails purchaseDetails) {
+    sendError(AgilePayCode.payCodePayError,
+        AgilePayCode.getMessageByCode(AgilePayCode.payCodePayError));
+  }
+
+  void verifySuccessPurchase(PurchaseDetails purchaseDetails) {
+    sendPaySuccess(purchaseDetails.verificationData.serverVerificationData);
+  }
+}

+ 17 - 0
plugins/agile_pay/lib/src/assist/apple_or_google_pay_info.dart

@@ -0,0 +1,17 @@
+import 'package:agile_pay/src/assist/product_type.dart';
+
+class AppleOrGooglePayInfo {
+  final String _productId;
+
+  final ProductType _type;
+
+  final String? _accountToken;
+
+  AppleOrGooglePayInfo(this._productId, this._type, this._accountToken);
+
+  String get productId => _productId;
+
+  ProductType get type => _type;
+
+  String? get accountToken => _accountToken;
+}

+ 4 - 0
plugins/agile_pay/lib/src/assist/product_type.dart

@@ -0,0 +1,4 @@
+enum ProductType {
+  consumable,
+  nonConsumable,
+}

+ 52 - 0
plugins/agile_pay/lib/src/code/agile_pay_code.dart

@@ -0,0 +1,52 @@
+class AgilePayCode {
+  AgilePayCode._();
+
+  static const int payCodeQqwalletNotSupport = 50000;
+  static const int payCodeAlipaySuccess = 9000;
+  static const int payCodeOtherError = 99999;
+  static const int payCodeTokenError = 80000;
+  static const int payCodeTokenFormatError = 80001;
+  static const int payCodeWxEnvError = 80002;
+  static const int payCodeWxNoResultError = 80003;
+  static const int payCodeWxNoTokenError = 80004;
+  static const int payCodeWxNoPayperidError = 80005;
+  static const int payCodeParamsError = 80006;
+  static const int payCodeSystemBusy = 4000;
+  static const int payCodeOrderInfoError = 4001;
+  static const int payCodeCancelError = 6001;
+  static const int payCodeNetError = 6002;
+  static const int payCodeShengKeyNotMatch = 70000;
+  static const int payCodePayError = 70001;
+  static const int payCodeSystemError = 70002;
+  static const int payCodeNotSupport = 70003;
+  static const int payCodeNotConnectStore = 70004;
+  static const int payCodeProductNotFindStore = 70005;
+  static const int payCodeRequestSendError = 70006;
+
+  static final Map<int, String> resultStatus = {
+    payCodeSystemError: "系统异常",
+    payCodeNotSupport: "不支持该支付类型",
+    payCodeSystemBusy: "系统繁忙,请稍候再试",
+    payCodeOrderInfoError: "订单参数错误",
+    payCodeCancelError: "取消支付",
+    payCodeNetError: "网络连接异常",
+    payCodeTokenError: "Token获取失败",
+    payCodeTokenFormatError: "Token格式错误",
+    payCodeWxEnvError: "微信未安装或微信版本不支持",
+    payCodeWxNoResultError: "微信支付无返回值",
+    payCodeWxNoTokenError: "微信支付获取access_token错误",
+    payCodeWxNoPayperidError: "微信支付获取payperid错误",
+    payCodeParamsError: "参数错误",
+    payCodePayError: "支付失败",
+    payCodeShengKeyNotMatch: "证书不匹配",
+    payCodeQqwalletNotSupport: "QQ未安装或QQ版本不支持",
+    payCodeOtherError: "其他问题",
+    payCodeNotConnectStore: "无法连接到商店",
+    payCodeProductNotFindStore: "商品未找到",
+    payCodeRequestSendError: "请求支付发送失败",
+  };
+
+  static String getMessageByCode(int code) {
+    return resultStatus[code] ?? "Unknown code";
+  }
+}

+ 6 - 0
plugins/agile_pay/lib/src/googlepay/google_pay.dart

@@ -0,0 +1,6 @@
+import '../assist/apple_or_google_pay.dart';
+import '../listener/i_agile_pay.dart';
+
+class GooglePay extends AppleOrGooglePay implements IAgilePay {
+  GooglePay(super.payInfo);
+}

+ 5 - 0
plugins/agile_pay/lib/src/googlepay/google_pay_info.dart

@@ -0,0 +1,5 @@
+import '../assist/apple_or_google_pay_info.dart';
+
+class GooglePayInfo extends AppleOrGooglePayInfo {
+  GooglePayInfo(super.productId, super.type, super.accountToken);
+}

+ 50 - 0
plugins/agile_pay/lib/src/listener/agile_pay_state.dart

@@ -0,0 +1,50 @@
+abstract class AgilePayState {
+  void error(int errno, String? error);
+
+  void payError(int error, String? errorMessage);
+
+  void paySuccess(String? result);
+
+  void payBefore();
+}
+
+typedef AgilePayBefore = void Function();
+typedef AgilePaySuccess = void Function(String? result);
+typedef AgilePayError = void Function(int error, String? errorMessage);
+typedef AgileError = void Function(int errno, String? error);
+
+class AgilePayStateImpl implements AgilePayState {
+  AgileError errorListener;
+
+  AgilePayError payErrorListener;
+
+  AgilePaySuccess paySuccessListener;
+
+  AgilePayBefore? payBeforeListener;
+
+  AgilePayStateImpl(
+      {required this.errorListener,
+      required this.payErrorListener,
+      required this.paySuccessListener,
+      this.payBeforeListener});
+
+  @override
+  void error(int errno, String? error) {
+    errorListener.call(errno, error);
+  }
+
+  @override
+  void payError(int error, String? errorMessage) {
+    payErrorListener.call(error, errorMessage);
+  }
+
+  @override
+  void paySuccess(String? result) {
+    paySuccessListener.call(result);
+  }
+
+  @override
+  void payBefore() {
+    payBeforeListener?.call();
+  }
+}

+ 7 - 0
plugins/agile_pay/lib/src/listener/i_agile_pay.dart

@@ -0,0 +1,7 @@
+import 'agile_pay_state.dart';
+
+abstract class IAgilePay {
+  void pay();
+
+  void setPayListener(AgilePayState agilePayState);
+}

+ 88 - 0
plugins/agile_pay/lib/src/wxpay/wechat_pay.dart

@@ -0,0 +1,88 @@
+import 'dart:async';
+
+import 'package:agile_pay/src/wxpay/wechat_pay_info.dart';
+import 'package:flutter/widgets.dart';
+import 'package:wechat_kit/wechat_kit.dart';
+
+import '../assist/agile_pay_state_info.dart';
+import '../code/agile_pay_code.dart';
+import '../listener/i_agile_pay.dart';
+
+class WechatPay extends AgilePayStateInfo implements IAgilePay {
+  late final StreamSubscription<WechatResp> _respSubs;
+
+  final WechatPayInfo _payInfo;
+
+  WechatPay(this._payInfo) {
+    _respSubs = WechatKitPlatform.instance.respStream().listen(_listenResp);
+  }
+
+  void _listenResp(WechatResp resp) {
+    try {
+      if (resp is WechatPayResp) {
+        final String content = 'pay: ${resp.errorCode} ${resp.errorMsg}';
+        debugPrint('agilePay-wechat---> $content');
+        if (resp.errorCode == WechatResp.kErrorCodeSuccess) {
+          sendPaySuccess(resp.returnKey);
+        } else if (resp.errorCode == WechatResp.kErrorCodeUserCancel) {
+          sendError(AgilePayCode.payCodeCancelError,
+              AgilePayCode.getMessageByCode(AgilePayCode.payCodeCancelError));
+        } else {
+          sendError(resp.errorCode, resp.errorMsg);
+        }
+      }
+    } catch (e) {
+      sendError(AgilePayCode.payCodeOtherError,
+          AgilePayCode.getMessageByCode(AgilePayCode.payCodeOtherError));
+    } finally {
+      dispose();
+    }
+  }
+
+  Future<bool> isInstalled() {
+    return WechatKitPlatform.instance.isInstalled();
+  }
+
+  @override
+  void pay() async {
+    sendPayBefore();
+    try {
+      await WechatKitPlatform.instance.registerApp(
+          appId: _payInfo.appId, universalLink: _payInfo.universalLink);
+      if (await check()) {
+        if (await isInstalled()) {
+          try {
+            WechatKitPlatform.instance.pay(
+                appId: _payInfo.appId,
+                partnerId: _payInfo.partnerId,
+                prepayId: _payInfo.prepayId,
+                package: _payInfo.package,
+                nonceStr: _payInfo.noncestr,
+                timeStamp: _payInfo.timestamp,
+                sign: _payInfo.sign);
+          } catch (e) {
+            sendError(AgilePayCode.payCodePayError,
+                AgilePayCode.getMessageByCode(AgilePayCode.payCodePayError));
+          }
+        } else {
+          sendError(AgilePayCode.payCodeWxEnvError,
+              AgilePayCode.getMessageByCode(AgilePayCode.payCodeWxEnvError));
+        }
+      } else {
+        sendError(AgilePayCode.payCodeWxEnvError,
+            AgilePayCode.getMessageByCode(AgilePayCode.payCodeWxEnvError));
+      }
+    } catch (e) {
+      sendError(AgilePayCode.payCodeOtherError,
+          AgilePayCode.getMessageByCode(AgilePayCode.payCodeOtherError));
+    }
+  }
+
+  Future<bool> check() async {
+    return WechatKitPlatform.instance.isSupportApi();
+  }
+
+  void dispose() {
+    _respSubs.cancel();
+  }
+}

+ 41 - 0
plugins/agile_pay/lib/src/wxpay/wechat_pay_info.dart

@@ -0,0 +1,41 @@
+class WechatPayInfo {
+  String _appId; // 应用ID
+  String _partnerId; // 商户号
+  String _prepayId; // 预支付交易会话ID
+  String _package; // 扩展字段
+  String _noncestr; // 随机字符串
+  String _timestamp; // 时间戳
+  String _sign; // 签名
+  String? universalLink;
+
+  WechatPayInfo({
+    required String appId,
+    required String partnerId,
+    required String prepayId,
+    required String package,
+    required String noncestr,
+    required String timestamp,
+    required String sign,
+    this.universalLink,
+  })  : _appId = appId,
+        _partnerId = partnerId,
+        _prepayId = prepayId,
+        _package = package,
+        _noncestr = noncestr,
+        _timestamp = timestamp,
+        _sign = sign;
+
+  String get appId => _appId;
+
+  String get partnerId => _partnerId;
+
+  String get prepayId => _prepayId;
+
+  String get package => _package;
+
+  String get noncestr => _noncestr;
+
+  String get timestamp => _timestamp;
+
+  String get sign => _sign;
+}

+ 61 - 0
plugins/agile_pay/pubspec.yaml

@@ -0,0 +1,61 @@
+name: agile_pay
+description: "整合支付"
+version: 0.0.1
+homepage:
+
+environment:
+  sdk: ^3.5.0
+  flutter: ">=1.17.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+
+  #支付
+  alipay_kit: ^6.0.0
+  #微信支付
+  wechat_kit: ^6.0.1
+  #goodle & apple
+  in_app_purchase: ^3.2.0
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  flutter_lints: ^4.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+# To add assets to your package, add an assets section, like this:
+# assets:
+#   - images/a_dot_burr.jpeg
+#   - images/a_dot_ham.jpeg
+#
+# For details regarding assets in packages, see
+# https://flutter.dev/to/asset-from-package
+#
+# An image asset can refer to one or more resolution-specific "variants", see
+# https://flutter.dev/to/resolution-aware-images
+
+# To add custom fonts to your package, add a fonts section here,
+# in this "flutter" section. Each entry in this list should have a
+# "family" key with the font family name, and a "fonts" key with a
+# list giving the asset and other descriptors for the font. For
+# example:
+# fonts:
+#   - family: Schyler
+#     fonts:
+#       - asset: fonts/Schyler-Regular.ttf
+#       - asset: fonts/Schyler-Italic.ttf
+#         style: italic
+#   - family: Trajan Pro
+#     fonts:
+#       - asset: fonts/TrajanPro.ttf
+#       - asset: fonts/TrajanPro_Bold.ttf
+#         weight: 700
+#
+# For details regarding fonts in packages, see
+# https://flutter.dev/to/font-from-package

+ 72 - 0
pubspec.lock

@@ -14,6 +14,29 @@ packages:
     description: dart
     source: sdk
     version: "0.3.2"
+  agile_pay:
+    dependency: "direct main"
+    description:
+      path: "plugins/agile_pay"
+      relative: true
+    source: path
+    version: "0.0.1"
+  alipay_kit:
+    dependency: transitive
+    description:
+      name: alipay_kit
+      sha256: "6d6086b4cda1e0cd9b29b6dfe8d0ce88b9efc1c9f2c8d6fb39ac24c4e0f79b06"
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.0.0"
+  alipay_kit_android:
+    dependency: transitive
+    description:
+      name: alipay_kit_android
+      sha256: "402917c30e5a1c1bb36cab7c99e355f65a98da96c2666657805a985b00937f6f"
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.0.0"
   analyzer:
     dependency: transitive
     description:
@@ -440,6 +463,15 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.3.2"
+  gravity_engine:
+    dependency: "direct main"
+    description:
+      path: "."
+      ref: "v0.0.2"
+      resolved-ref: c82b1570eb8f80b3746df1c6e60e52a233fc7e5e
+      url: "http://git.atmob.com:28999/Atmob-Flutter/gravity_engine.git"
+    source: git
+    version: "0.0.2"
   hashcodes:
     dependency: transitive
     description:
@@ -480,6 +512,38 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.4.0"
+  in_app_purchase:
+    dependency: transitive
+    description:
+      name: in_app_purchase
+      sha256: "11a40f148eeb4f681a0572003e2b33432e110c90c1bbb4f9ef83b81ec0c4f737"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.1"
+  in_app_purchase_android:
+    dependency: transitive
+    description:
+      name: in_app_purchase_android
+      sha256: "45ae4fe253f85b4fcc58b421fe137f6e48aca16bf8a618cd760cb0542e7f854e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.4.0"
+  in_app_purchase_platform_interface:
+    dependency: transitive
+    description:
+      name: in_app_purchase_platform_interface
+      sha256: "1d353d38251da5b9fea6635c0ebfc6bb17a2d28d0e86ea5e083bf64244f1fb4c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.4.0"
+  in_app_purchase_storekit:
+    dependency: transitive
+    description:
+      name: in_app_purchase_storekit
+      sha256: "276831961023055b55a2156c1fc043f50f6215ff49fb0f5f2273da6eeb510ecf"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.3.21"
   injectable:
     dependency: "direct main"
     description:
@@ -1005,6 +1069,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.2.0"
+  synchronized:
+    dependency: "direct main"
+    description:
+      name: synchronized
+      sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.3.0+3"
   term_glyph:
     dependency: transitive
     description:

+ 13 - 0
pubspec.yaml

@@ -110,6 +110,13 @@ dependencies:
   flutter_qiyu:
     path: plugins/flutter_qiyu
 
+  #并发
+  synchronized: ^3.3.0+2
+
+  #支付
+  agile_pay:
+    path: plugins/agile_pay
+
   ######################地图########################
   flutter_map:
     path: plugins/map
@@ -138,6 +145,12 @@ dependencies:
       name: atmob_channel_reader
       url: http://pub.v8dashen.com/
 
+  #引力引擎
+  gravity_engine:
+    git:
+      url: http://git.atmob.com:28999/Atmob-Flutter/gravity_engine.git
+      ref: v0.0.2
+
   #oaid
 #  oaid:
 #    git: