import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'dart:ui'; 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/consts/channel_id.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/goods_evaluate_info.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 '../../device/atmob_platform_info.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/payment_status_manager.dart'; import '../../widget/animated_switcher_widget.dart'; import '../browser/browser_view.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'; @injectable class MemberController extends BaseController implements PaymentStatusCallback { final AccountRepository accountRepository; final MemberRepository memberRepository; final PaymentStatusManager paymentStatusManager; final RxBool _isShowMemberSubscribeTxt = false.obs; bool get isShowMemberSubscribeTxt => _isShowMemberSubscribeTxt.value; 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; ///评论 final RxList evaluateList = [].obs; CancelableFuture? _memberDataFuture; Duration? get activityDuration => memberRepository.activityDuration.value; ///检查续订的状态 final Rx _checkResponse = Rx(SubscriptionCheckResponse(outTradeNo: "")); SubscriptionCheckResponse? get requestCheckResponse => _checkResponse.value; bool _isShowPayFailedDialog = false; // 是否已经展示过支付失败弹窗 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', "用来遛狗也很方便,再也不用担心狗狗跑丢了,真是个好工具!"), ];*/ 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(); precacheImage( Assets.images.iconMemberSpecialProductsSelect.provider(), Get.context!); precacheImage( Assets.images.iconMemberSpecialProductsNormal.provider(), Get.context!); } ///是否弹窗提示 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(); evaluateList.clear(); _selectedGoods.value = null; _checkMemberHasSubscribe(response.goodsList); 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; } if (response.evaluateList?.isNotEmpty == true) { evaluateList.addAll(response.evaluateList!); } }); } void _checkMemberHasSubscribe(List? goodsList) { if (goodsList == null) { _isShowMemberSubscribeTxt.value = false; return; } for (var goods in goodsList) { if (goods.subscribable == 1) { _isShowMemberSubscribeTxt.value = true; return; } } _isShowMemberSubscribeTxt.value = false; } void onGoodsItemClick(GoodsBean item) { _selectedGoods.value = item; onBuyClick(); } void onPrivacyPolicyClick() { BrowserPage.start(WebUrl.privacyPolicy); } void onTermOfServiceClick() { BrowserPage.start(WebUrl.userAgreement); } void onRenewalAgreementClick() { BrowserPage.start(WebUrl.renewalAgreement); } void onPayWayItemClick(PayItemBean item) { _selectedPayWay.value = item; } void onPopBack({bool iosIsBack = true}) { if (Platform.isIOS) { if (iosIsBack) { back(); return; } if (accountRepository.memberIsExpired()) { FocusScope.of(Get.context!).unfocus(); userCancelsPaymentDisplay(); } } else { if (accountRepository.memberIsExpired()) { userCancelsPaymentDisplay(); } else { back(); } } } ///用户取消支付展示 void userCancelsPaymentDisplay() { if (_isShowPayFailedDialog) { if (Platform.isAndroid) back(); return; } if (Platform.isAndroid) { showRetainDialog(); } else { Future.delayed(Duration(milliseconds: 500), () { showRetainDialog(); }); } } //支付错误的时候调用 void showBackPaymentFailureDialog({int? errno}) { if (Platform.isIOS) { errorPayToast(errno); return; } _isShowPayFailedDialog = true; MemberRetainDialog.show(payClick: () { onBuyClick(); }, cancelClick: () { errorPayToast(errno); }); } //退出挽留弹窗 void showRetainDialog({int? errno}) { MemberRetainDialog.show(payClick: () { onBuyClick(); }, cancelClick: () { errorPayToast(errno); if (Platform.isAndroid) back(); }); } void onBuyClick() async { final buyGoods = selectedGoods; final buyPayWay = selectedPayWay; if (buyGoods == null) { ToastUtil.show(StringName.memberPleaseChoiceGoods); return; } if (buyPayWay == null) { ToastUtil.show(StringName.memberPleaseChoicePayment); return; } memberRepository.setLastSelectedMember(buyGoods, buyPayWay); //增加渠道登录判断 if (Platform.isAndroid && atmobPlatformInfo.tgPlatform == ChannelId.sd) { if (!accountRepository.isLogin.value) { ToastUtil.show(StringName.accountNoLogin); final isLogin = await LoginPage.start(); if (!isLogin) { return; } } } int goodsId = buyGoods.id; int payPlatform = buyPayWay.payPlatform; int payMethod = buyPayWay.payMethod; int payWayType = getPayWayType(payMethod: payMethod, payPlatform: payPlatform); LoadingDialog.show(StringName.payLoading, backDismiss: true, clickMaskDismiss: true); 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 { LoadingDialog.hide(); ToastUtil.show(StringName.payNotSupport); } }).catchError((error) { LoadingDialog.hide(); //处理购买失败 showBackPaymentFailureDialog(); 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); } }); } ///发起购买请求 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(); print('购买失败: ${result['error']}'); showBackPaymentFailureDialog(); } } void _onAliScanPay(String outTradeNo, String qrHtml, PayItemBean paymentWay, GoodsBean goodsBean) { LoadingDialog.hide(); 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}) { LoadingDialog.show(StringName.payQuerypayState); 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) { LoadingDialog.hide(); 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) { checkPaymentStatus(outTradeNo, buyPayWay, buyGoods, receiptData: result); }, payError: (int errno, String? errorMessage) { LoadingDialog.hide(); debugPrint('zk---payError: $errno, $errorMessage'); errorEventReport(payMethod); showBackPaymentFailureDialog(errno: errno); }, error: (int errno, String? error) { LoadingDialog.hide(); debugPrint('zk---error: $errno, $error'); errorEventReport(payMethod); showBackPaymentFailureDialog(errno: errno); }); } 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 == null) { return; } 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) { ///购买成功消失 LoadingDialog.hide(); ///购买成功之后弹出 afterTheFirstPurchasePromptSharingBoxPops(); } @override void onPaymentFail() { ///购买成功消失 LoadingDialog.hide(); } ///第一次购买之后弹出提示分享框 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); } }