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'; 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/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 '../../dialog/wechat_qr_code_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 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; 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(); _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 _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: 12.sp, color: Colors.white), 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().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 (accountRepository.memberIsExpired()) { showRetainDialog(); } else { back(); } } void showRetainDialog() { MemberRetainDialog.show(payClick: () { onBuyClick(); }, cancelClick: () { back(); }); } void onBuyClick() { if (selectedGoods == null) { ToastUtil.show(StringName.memberPleaseChoiceGoods); return; } if (selectedPayWay == null) { 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 { ToastUtil.show(StringName.payNotSupport); } }).catchError((error) { 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(); }); } 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(); }); } 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) { LoadingDialog.hide(); showPaymentSuccessDialog(onConfirm: back, onCancel: back); } }