import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'package:agile_pay/flutter_pay.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:injectable/injectable.dart'; import 'package:location/base/base_controller.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:location/dialog/alipay_qr_code_dialog.dart'; import 'package:location/handler/error_handler.dart'; 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/subscription_check_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 '../../dialog/wechat_qr_code_dialog.dart'; import '../../resource/string.gen.dart'; import '../../sdk/wechat/wechat_share_util.dart'; import '../../utils/app_review_service.dart'; import '../../utils/http_handler.dart'; import '../../utils/mmkv_util.dart'; import '../../utils/payment_status_manager.dart'; import '../../widget/animated_switcher_widget.dart'; import '../browser/browser_view.dart'; import '../mine/mine_controller.dart'; import 'member_evaluate_bean.dart'; import 'member_evaluation_pop_up_dialog.dart'; import 'member_first_week_discount_dialog.dart'; import 'member_fun_bean.dart'; import 'package:apple_pay/apple_pay.dart'; import 'member_page.dart'; import 'member_payment_completed_dialog.dart'; import 'member_user_cancel_pay_dialog.dart'; @injectable class MemberController extends BaseController implements PaymentStatusCallback { final AccountRepository accountRepository; final MemberRepository memberRepository; final PaymentStatusManager paymentStatusManager; final switcherController = SwitcherController(); final ScrollController scrollController = ScrollController(); StreamController? _changeStreamController; final random = Random(); final RxDouble _toolBarOpacity = 0.0.obs; double get toolBarOpacity => _toolBarOpacity.value; final List _storeTypes = ['终身会员', '年度会员', '月度会员']; String? get phone => accountRepository.loginPhoneNum.value; bool get isLogin => accountRepository.isLogin.value; MemberStatusInfo? get memberStatusInfo => accountRepository.memberStatusInfo.value; final RxList goodsList = [].obs; final Rxn _selectedGoods = Rxn(); GoodsBean? get selectedGoods => _selectedGoods.value; final Rxn _selectedPayWay = Rxn(); PayItemBean? get selectedPayWay => _selectedPayWay.value; final RxList payItemList = [].obs; CancelableFuture? _memberDataFuture; ///广告弹窗防抖 bool _isPopBackInProgress = false; // 操作进行中标志 ///检查续订的状态 final Rx _checkResponse = Rx(SubscriptionCheckResponse(outTradeNo: "")); SubscriptionCheckResponse? get requestCheckResponse => _checkResponse.value; final List funList = [ MemberFunBean(1, Assets.images.iconMemberFun1.path, StringName.memberFunName1, StringName.memberFunName1Desc), MemberFunBean(2, Assets.images.iconMemberFun2.path, 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(5, Assets.images.iconMemberFun5.path, StringName.memberFunName5, StringName.memberFunName5Desc), MemberFunBean(6, Assets.images.iconMemberFun6.path, StringName.memberFunName6, StringName.memberFunName6Desc), ]; final List evaluateList = [ MemberEvaluateBean(1, Assets.images.iconEvaluate1.path, '用户189****7913', "上班没时间,远程遛娃,非常方便很好用。"), MemberEvaluateBean(2, Assets.images.iconEvaluate2.path, '用户177****2345', "这个功能太棒了!尤其是夜间出行时,一键报警让我感觉特别安心。"), MemberEvaluateBean(3, Assets.images.iconEvaluate3.path, '用户138****6789', "强烈推荐!我和家人经常用这个功能来共享位置,尤其是旅游时,走散了也不怕。"), MemberEvaluateBean(4, Assets.images.iconEvaluate4.path, '用户159****3456', "实时定位非常精准,用来监控孩子的行踪特别方便,再也不用担心他们乱跑了。"), MemberEvaluateBean(5, Assets.images.iconEvaluate5.path, '用户182****9012', "用来遛狗也很方便,再也不用担心狗狗跑丢了,真是个好工具!"), ]; static const String _keMemberPageShowKey = "member_page_show_key"; MemberController( this.accountRepository, this.memberRepository, this.paymentStatusManager); @override void onReady() async { super.onReady(); _isTherePopUpPrompt(); _startAnimationSwitcher(); scrollController.addListener(() { double offset = scrollController.offset; double opacity = offset / 100; if (opacity > 1) { opacity = 1; } else if (opacity < 0) { opacity = 0; } _toolBarOpacity.value = opacity; }); refreshMemberData(); } ///是否弹窗提示 void _isTherePopUpPrompt() { MemberPageType pageType = Get.arguments ?? MemberPageType.universalAccessEnter; if (pageType == MemberPageType.afeterTrialMemberEnter) { MemberFirstWeekDiscountDialog.show( confirmOnTap: () { onBuyClick(); }, onPrivacyPolicyClick: () { onPrivacyPolicyClick(); }, onTermOfServiceClick: () { onTermOfServiceClick(); } ); } } void _startAnimationSwitcher() { _changeStreamController = AsyncUtil.interval( (time) => changeSwitcherContent(), Duration(seconds: 3), -1); } Future changeSwitcherContent() async { int userId = random.nextInt(10000); String userIdStr = userId.toString(); int padLength = 4 - userIdStr.length; for (int i = 0; i < padLength; i++) { userIdStr = random.nextInt(10).toString() + userIdStr; } bool isHour = random.nextBool(); String secondsOrHour; if (isHour) { secondsOrHour = "${1 + random.nextInt(8)}小时"; } else { secondsOrHour = "${1 + random.nextInt(59)}分钟"; } int index = random.nextInt(_storeTypes.length); switcherController.updateWidget(Row( mainAxisAlignment: MainAxisAlignment.center, children: [ RichText( text: TextSpan( style: TextStyle(fontSize: 11.sp, color: Colors.white,fontWeight: FontWeight.w400), children: [ TextSpan(text: userIdStr), TextSpan(text: '用户 '), TextSpan(text: secondsOrHour), TextSpan(text: '前购买了'), TextSpan( text: _storeTypes[index], style: TextStyle(color: '#FFC95D'.color)), ])) ], )); } String getUserName(String phone) { if (phone.length > 4) { phone = phone.substring(phone.length - 4); } return '${StringName.mineAccountLoggedDesc}$phone'; } void back() { Get.back(); } void onLoginClick() { if (accountRepository.isLogin.value) { return; } LoginPage.start(); } void refreshMemberData() { _memberDataFuture?.cancel(); _memberDataFuture = AsyncUtil.retryWithExponentialBackoff(() => _requestMemberData(), 4); _memberDataFuture?.catchError((error) { ErrorHandler.toastError(error); }); } Future _requestMemberData() { return memberRepository .getMemberList(itemListType: Platform.isIOS ? 2 : 0) .then((response) { goodsList.clear(); payItemList.clear(); _selectedGoods.value = null; if (response.goodsList?.isNotEmpty == true) { goodsList.addAll(response.goodsList!); _selectedGoods.value = goodsList.first; } if (response.payInfoList?.isNotEmpty == true) { payItemList.addAll(response.payInfoList!); _selectedPayWay.value = payItemList.first; } }); } void onGoodsItemClick(GoodsBean item) { _selectedGoods.value = item; } void onPrivacyPolicyClick() { BrowserPage.start(WebUrl.privacyPolicy); } void onTermOfServiceClick() { BrowserPage.start(WebUrl.userAgreement); } void onPayWayItemClick(PayItemBean item) { _selectedPayWay.value = item; } void onPopBack() { if (Platform.isIOS) { back(); if (accountRepository.memberIsExpired()) { FocusScope.of(Get.context!).unfocus(); userCancelsPaymentDisplay(); } } else { if (accountRepository.memberIsExpired()) { userCancelsPaymentDisplay(); } else { back(); } } } ///用户取消支付展示 void userCancelsPaymentDisplay() { if (_isPopBackInProgress) { // 如果正在处理返回逻辑,直接跳过 return; } _isPopBackInProgress = true; // 锁定状态 //_keMemberPageShowKey String? memberPageKeyStr = KVUtil.getString(_keMemberPageShowKey, ''); if ((memberPageKeyStr ?? '').isEmpty && (accountRepository.memberStatusInfo.value?.trialed == false)) { ///永久化存储 KVUtil.putString(_keMemberPageShowKey, _keMemberPageShowKey); MemberUserCancelPayDialog.show(payClick: () { //获取会员 MineController attributionController = Get.find(); attributionController.onMemberTryOutClick(); _isPopBackInProgress = false; }, cancelClick: () { _isPopBackInProgress = false; if (!Platform.isIOS) { back(); } }); } else { _isPopBackInProgress = false; } } ///支付错误的时候调用 void showRetainDialog() { if (_isPopBackInProgress) { // 如果正在处理返回逻辑,直接跳过 return; } _isPopBackInProgress = true; // 锁定状态 MemberRetainDialog.show(payClick: () { onBuyClick(); _isPopBackInProgress = false; }, cancelClick: () { _isPopBackInProgress = false; if (!Platform.isIOS) { back(); } }); } void onBuyClick() { if (selectedGoods == null) { ToastUtil.show(StringName.memberPleaseChoiceGoods); return; } if (selectedPayWay == null && !Platform.isIOS) { ToastUtil.show(StringName.memberPleaseChoicePayment); return; } final buyGoods = selectedGoods!; final buyPayWay = selectedPayWay!; int goodsId = buyGoods.id; int payPlatform = buyPayWay.payPlatform; int payMethod = buyPayWay.payMethod; int payWayType = getPayWayType(payMethod: payMethod, payPlatform: payPlatform); LoadingDialog.show(StringName.payLoading); memberRepository .submitAndRequestPay( goodsId: goodsId, payPlatform: payPlatform, payMethod: payMethod) .then((response) { if (payWayType == PayWayType.paymentWayWechat) { _onWeChatPay(response.outTradeNo, response.wechatPayPrepayJson!, payMethod, buyGoods, buyPayWay); } else if (payWayType == PayWayType.paymentWayWechatScan) { _onWechatScanPay(response.outTradeNo, response.wechatPayQrcodeUrl!, buyPayWay, buyGoods); } else if (payWayType == PayWayType.paymentWayAlipay) { _onAliPay(response.outTradeNo, response.alipayOrderString!, payMethod, buyGoods, buyPayWay); } else if (payWayType == PayWayType.paymentWayAlipayScan) { _onAliScanPay(response.outTradeNo, response.alipayQrcodeHtml!, buyPayWay, buyGoods); } else if (payWayType == PayWayType.paymentWayApple) { _onApplePay(response.outTradeNo, response.appAccountToken ?? "", buyPayWay, buyGoods); } else { ToastUtil.show(StringName.payNotSupport); } }).catchError((error) { //处理购买失败 showRetainDialog(); if (error is ServerErrorException) { if (error.code == ErrorCode.payOrderError) { refreshMemberData(); ToastUtil.show(error.message); } else if (error.code == ErrorCode.noLoginError) { ToastUtil.show(StringName.accountNoLogin); LoginPage.start(); } else { ToastUtil.show(error.message); } } else { ToastUtil.show(StringName.memberPaymentFailed); } }).whenComplete(() { LoadingDialog.hide(); }); } ///发起购买请求 Future _onApplePay(String outTradeNo, String appAccountToken, PayItemBean payWayInfo, GoodsBean goodsInfo,) async { final result = await ApplePay().purchase( productId: goodsInfo.appleGoodsId ?? "", appAccountToken: appAccountToken, ); if (result["success"] == true) { var receipt = result['receipt']; print('购买成功: ${result['receipt']}'); checkPaymentStatus( outTradeNo, payWayInfo, goodsInfo, receiptData: receipt, ); } else { LoadingDialog.hide(); ToastUtil.show("支付失败,请稍后重试"); print('购买失败: ${result['error']}'); } } void _onAliScanPay(String outTradeNo, String qrHtml, PayItemBean paymentWay, GoodsBean goodsBean) { AlipayQrCodeDialog.show( qrCodeHtml: qrHtml, loadSuccessCallback: () { checkPaymentStatus(outTradeNo, paymentWay, goodsBean); }, onCloseCallback: () async { //关闭后再持续查询几秒 CustomLoadingDialog.show(); await Future.delayed(Duration(seconds: 4)); paymentStatusManager.removePollingSubscription(outTradeNo); CustomLoadingDialog.hide(); }); } void checkPaymentStatus( String orderNo, PayItemBean paymentWay, GoodsBean goodsBean, {String? receiptData}) { paymentStatusManager.registerPaymentSuccessCallback(orderNo, this); paymentStatusManager.checkPaymentStatus(orderNo, paymentWay, goodsBean, receiptData: receiptData); } void _onWeChatPay(String outTradeNo, String payJson, int payMethod, GoodsBean buyGoods, PayItemBean buyPayWay) { final bean = WechatPaymentSignBean.stringToBean(payJson); final payInfo = WechatPayInfo( appId: bean.appId, partnerId: bean.partnerId, prepayId: bean.prepayId, package: bean.package, noncestr: bean.nonceStr, timestamp: bean.timeStamp, sign: bean.sign); requestSdkPay(payInfo, outTradeNo, payMethod, buyGoods, buyPayWay); } void _onAliPay(String outTradeNo, String payJson, int payMethod, GoodsBean buyGoods, PayItemBean buyPayWay) { final payInfo = AliPayInfo(payJson); requestSdkPay(payInfo, outTradeNo, payMethod, buyGoods, buyPayWay); } void _onWechatScanPay(String outTradeNo, String qrCodeUrl, PayItemBean paymentWay, GoodsBean goodsBean) { WechatQrCodeDialog.show( qrCodeUrl: qrCodeUrl, loadSuccessCallback: () { checkPaymentStatus(outTradeNo, paymentWay, goodsBean); }, onCloseCallback: () async { //关闭后再持续查询几秒 CustomLoadingDialog.show(); await Future.delayed(Duration(seconds: 4)); paymentStatusManager.removePollingSubscription(outTradeNo); CustomLoadingDialog.hide(); }); } ///查询订阅状态 Future _requestCheckRestoreStatus(String? receiptData) async { memberRepository.subscriptionCheck(3, receiptData ?? "").then((value) { _checkResponse.value = value; }).catchError((error) {}); } /// 点击了恢复订阅 Future clickRecoverSubscribe() async { PayItemBean? paymentWay = _selectedPayWay.value; if (paymentWay == null) { return; } int payPlatform = paymentWay.payPlatform; int payMethod = paymentWay.payMethod; CustomLoadingDialog.show(); Future.delayed(const Duration(seconds: 20), () { CustomLoadingDialog.hide(); //ToastUtil.show("没有发现可恢复的记录"); }); final result = await ApplePay().restore(); if (result["success"] == true) { // CustomLoadingDialog.hide(); var receipt = result['receipt']; print('查找恢复记录成功: ${result['receipt']}'); checkRestoreStatus(receipt); } else { CustomLoadingDialog.hide(); ToastUtil.show("恢复失败"); print('恢复失败: ${result['error']}'); } // 显示恢复订阅弹窗 // RecoverSubscribeDialog.show("周会员2025年3月6日到期。", () { // AtmobLog.d(tag, "恢复订阅弹窗 => 点击确认"); // }); } /// 检查恢复订阅结果 Future checkRestoreStatus(String? receiptData) async { PayItemBean? paymentWay = _selectedPayWay.value; if (paymentWay == null) { // ToastUtil.showToast(StringName.storeChoicePayment.tr); return; } if (receiptData == null) { return; } int payPlatform = paymentWay.payPlatform; int payMethod = paymentWay.payMethod; // var code = await storeRepository.resume(payPlatform, payMethod, receiptData); memberRepository.subscriptionResume(3, receiptData).then((data) async { CustomLoadingDialog.hide(); ToastUtil.show("恢复成功"); await AccountRepository.getInstance().getMemberStatus(); //accountRepository.refreshMemberStatus(); Get.back(); }).catchError((error) { CustomLoadingDialog.hide(); ToastUtil.show("恢复失败"); }); // if (code == 0) { // CustomLoadingDialog.hide(); // ToastUtil.show("Restore success"); // userRepository.getUserInfo(); // Get.back(); // } else { // CustomLoadingDialog.hide(); // ToastUtil.show("Restore fail"); // } } void requestSdkPay(dynamic payInfo, String outTradeNo, int payMethod, GoodsBean buyGoods, PayItemBean buyPayWay) { AgilePay.startPay(payInfo, success: (String? result) { LoadingDialog.show(StringName.payQuerypayState); checkPaymentStatus(outTradeNo, buyPayWay, buyGoods, receiptData: result); }, payError: (int error, String? errorMessage) { debugPrint('zk---payError: $error, $errorMessage'); errorPayToast(error); errorEventReport(payMethod); }, error: (int errno, String? error) { debugPrint('zk---error: $errno, $error'); errorPayToast(errno); errorEventReport(payMethod); }); } 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); } } @override void onClose() { super.onClose(); _changeStreamController?.close(); _memberDataFuture?.cancel(); scrollController.dispose(); paymentStatusManager.unregisterPaymentSuccessCallback(this); } @override void onPaymentSuccess( String orderNo, PayItemBean paymentWay, GoodsBean storeItemBean) { ///购买成功之后弹出 afterTheFirstPurchasePromptSharingBoxPops(); } ///第一次购买之后弹出提示分享框 void afterTheFirstPurchasePromptSharingBoxPops() { paymentStatusManager.onOrderFirstCheck().then((checkReponse) { //是否展示邀请弹框 if (checkReponse.showInvite || checkReponse.showEvaluate) { MemberPageType pageType = Get.arguments ?? MemberPageType.universalAccessEnter; //去分享 if (checkReponse.showInvite && pageType == MemberPageType.addFriendToEnter) { MemberPaymentCompletedDialog.show(confirmOnTap: () { WechatShareUtil.shareWebPage().catchError((error) { ToastUtil.show(error); }); if (checkReponse.showEvaluate) { ///是否展示好评领会员弹框 MemberEvaluationPopUpDialog.show(confirmOnTap: () { AppReviewService.requestAppReview(Get.context!); onPaySucessShow(); },cancelOnTap: () { onPaySucessShow(); },days: 1); } else { onPaySucessShow(); } },cancelOnTap: () { if (checkReponse.showEvaluate) { ///是否展示好评领会员弹框 MemberEvaluationPopUpDialog.show(confirmOnTap: () { AppReviewService.requestAppReview(Get.context!); ///领取会员后请求 memberRepository.memberEvaluate().then((_){ }).catchError((error) { });;//memberEvaluate onPaySucessShow(); },cancelOnTap: () { onPaySucessShow(); },days: 1); } else { onPaySucessShow(); } }); } else if (checkReponse.showEvaluate) { ///是否展示好评领会员弹框 MemberEvaluationPopUpDialog.show(confirmOnTap: () { AppReviewService.requestAppReview(Get.context!); onPaySucessShow(); },cancelOnTap: () { onPaySucessShow(); },days: 1); } } else { onPaySucessShow(); } }).catchError((erro) { onPaySucessShow(); }); } ///支付成功之后展示 void onPaySucessShow() { try { WechatQrCodeDialog.dismiss(); AlipayQrCodeDialog.dismiss(); CustomLoadingDialog.hide(); LoadingDialog.hide(); } catch (e) { debugPrint('zk---onPaymentSuccess error: $e'); } showPaymentSuccessDialog(onConfirm: back, onCancel: back); } }