import 'package:agile_pay/flutter_pay.dart'; import 'package:carousel_slider/carousel_controller.dart'; import 'package:carousel_slider/carousel_options.dart'; import 'package:flutter/material.dart'; import 'package:injectable/injectable.dart'; import 'package:keyboard/base/base_controller.dart'; import 'package:get/get.dart'; import 'package:keyboard/data/bean/character_info.dart'; import 'package:keyboard/module/store/new_discount/member_card_bean.dart'; import '../../../data/api/response/user_info_response.dart'; import '../../../data/bean/goods_info.dart'; import '../../../data/bean/member_info.dart'; import '../../../data/bean/pay_way_info.dart'; import '../../../data/bean/wechat_payment_sign_bean.dart'; import '../../../data/consts/error_code.dart'; import '../../../data/consts/payment_type.dart'; import '../../../data/repository/account_repository.dart'; import '../../../data/repository/store_repository.dart'; import '../../../dialog/alipay_qr_code_dialog.dart'; import '../../../dialog/loading_dialog.dart'; import '../../../dialog/login/login_dialog.dart'; import '../../../dialog/member_agreement_dialog.dart'; import '../../../dialog/payment_fail_dialog.dart'; import '../../../dialog/payment_success_dialog.dart'; import '../../../dialog/wechat_qr_code_dialog.dart'; import '../../../resource/assets.gen.dart'; import '../../../resource/string.gen.dart'; import '../../../utils/async_util.dart'; import '../../../utils/atmob_log.dart'; import '../../../utils/date_util.dart'; import '../../../utils/error_handler.dart'; import '../../../utils/http_handler.dart'; import '../../../utils/payment_status_manager.dart'; import '../../../utils/toast_util.dart'; import '../store_banner_bean.dart'; @injectable class NewDiscountController extends BaseController implements PaymentStatusCallback { final String tag = "NewDiscountController"; final StoreRepository storeRepository; final AccountRepository accountRepository; final PaymentStatusManager paymentStatusManager; RxList get goodsInfoList => storeRepository.newDiscountGoodsInfoList; RxList get payWayList => storeRepository.newDiscountPayWayList; final Rxn _selectedGoodsInfoItem = Rxn(); GoodsInfo? get selectedGoodsInfoItem => _selectedGoodsInfoItem.value; final Rxn _selectedPayWay = Rxn(); PayWayInfo? get selectedPayWay => _selectedPayWay.value; CancelableFuture? _storeDataFuture; bool get isLogin => accountRepository.isLogin.value; final isAgree = false.obs; MemberInfo? get memberStatusInfo => accountRepository.memberStatusInfo.value; UserInfoResponse? get userInfo => accountRepository.userInfo.value; final RxList filteredGoodsList = [].obs; RxList get _charactersList => storeRepository.charactersList; List get charactersList => _charactersList; final CarouselSliderController carouselSliderController = CarouselSliderController(); final RxInt _currentBanner = 0.obs; int get currentBannerIndex => _currentBanner.value; /// 轮播图控制器 final List bannerList = [ Assets.images.iconNewDiscountBanner1, Assets.images.iconNewDiscountBanner2, Assets.images.iconNewDiscountBanner3, ]; final List memberCardList = [ MemberCardBean( imageUrl: Assets.images.iconNewDiscountMembershipCard1, title: StringName.newDiscountMemberCard1Title, desc: StringName.newDiscountMemberCard1Desc, gradient: LinearGradient( begin: Alignment(0.50, -0.00), end: Alignment(0.50, 1.00), colors: [const Color(0xFFF1EDFF), const Color(0xFFFAF8FF)], ), ), MemberCardBean( imageUrl: Assets.images.iconNewDiscountMembershipCard2, title: StringName.newDiscountMemberCard2Title, desc: StringName.newDiscountMemberCard2Desc, gradient: LinearGradient( begin: Alignment(0.50, -0.00), end: Alignment(0.50, 1.00), colors: [const Color(0xFFDCF4C0), const Color(0xFFFAFFF3)], ), ), MemberCardBean( imageUrl: Assets.images.iconNewDiscountMembershipCard3, title: StringName.newDiscountMemberCard3Title, desc: StringName.newDiscountMemberCard3Desc, gradient: LinearGradient( begin: Alignment(0.50, -0.00), end: Alignment(0.50, 1.00), colors: [const Color(0xFFF9DBFF), const Color(0xFFFFFBFE)], ), ), MemberCardBean( imageUrl: Assets.images.iconNewDiscountMembershipCard4, title: StringName.newDiscountMemberCard4Title, desc: StringName.newDiscountMemberCard4Desc, gradient: LinearGradient( begin: Alignment(0.50, -0.00), end: Alignment(0.50, 1.00), colors: [const Color(0xFFDBECFF), const Color(0xFFFBFBFF)], ), ), MemberCardBean( imageUrl: Assets.images.iconNewDiscountMembershipCard5, title: StringName.newDiscountMemberCard5Title, desc: StringName.newDiscountMemberCard5Desc, gradient: LinearGradient( begin: Alignment(0.50, -0.00), end: Alignment(0.50, 1.00), colors: [const Color(0xFFFFF7DB), const Color(0xFFFFFDFB)], ), ), ]; NewDiscountController( this.storeRepository, this.accountRepository, this.paymentStatusManager, ); onBannerChanged(int index, CarouselPageChangedReason reason) { _currentBanner.value = index; } void refreshStoreData() { // _storeDataFuture?.cancel(); // _storeDataFuture = AsyncUtil.retryWithExponentialBackoff( // () => _requestGoodsInfoList(), // 4, // ); // _storeDataFuture?.catchError((error) { // ErrorHandler.toastError(error); // }); storeRepository.refreshNewDiscountGoodsInfoList(); } Future _requestGoodsInfoList() async { try { final response = await storeRepository.getMemberNewUserGoodsList(); goodsInfoList.clear(); payWayList.clear(); _charactersList.clear(); _selectedGoodsInfoItem.value = null; if (response.goodsInfoList?.isNotEmpty == true) { goodsInfoList.addAll(response.goodsInfoList!); _selectedGoodsInfoItem.value = goodsInfoList.first; } if (response.payInfoList?.isNotEmpty == true) { payWayList.addAll(response.payInfoList!); _selectedPayWay.value = payWayList.first; updateFilteredGoodsList(); } if (response.characterInfos?.isNotEmpty == true) { _charactersList.addAll(response.characterInfos!); } } catch (e) { AtmobLog.e(tag, e.toString()); } } void updateFilteredGoodsList() { if (_selectedPayWay.value == null) { return; } _selectedGoodsInfoItem.value = null; int selectedPayWayId = _selectedPayWay.value!.id; filteredGoodsList.assignAll( goodsInfoList.where((goods) { return goods.payOptionIds == null || goods.payOptionIds!.contains(selectedPayWayId); }).toList(), ); if (filteredGoodsList.isNotEmpty) { _selectedGoodsInfoItem.value = filteredGoodsList.first; } } void onGoodsItemClick(GoodsInfo goodsInfo) { _selectedGoodsInfoItem.value = goodsInfo; debugPrint("'item: ${goodsInfo.toJson()}"); } void clickPayWaySwitch() { if (payWayList.isNotEmpty) { int currentIndex = payWayList.indexOf( _selectedPayWay.value ?? payWayList.first, ); int nextIndex = (currentIndex + 1) % payWayList.length; _selectedPayWay.value = payWayList[nextIndex]; updateFilteredGoodsList(); // 切换支付方式后,更新商品列表 } } void clickBack() { Get.back(); } void clickPayNow() { if (selectedGoodsInfoItem == null) { ToastUtil.show(StringName.memberPleaseChoiceGoods); return; } if (selectedPayWay == null) { ToastUtil.show(StringName.memberPleaseChoicePayment); return; } if (!isAgree.value) { MemberAgreementDialog.show( btnConfirm: () { isAgree.value = true; clickPayNow(); }, ); return; } AtmobLog.d(tag, 'clickPayNow: ${selectedGoodsInfoItem!.toJson()}'); final buyGoods = selectedGoodsInfoItem!; 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); storeRepository .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) { refreshStoreData(); ToastUtil.show(error.message); } else if (error.code == ErrorCode.noLoginError) { ToastUtil.show(StringName.accountNoLogin); LoginDialog.show(); } else { ToastUtil.show(error.message); paymentFail(); } } else { ToastUtil.show(StringName.memberPaymentFailed); paymentFail(); } }) .whenComplete(() { LoadingDialog.hide(); }); } void paymentFail() { PaymentFailDialog.show( btnConfirm: () { clickPayNow(); }, ); } void _onAliScanPay( String outTradeNo, String qrHtml, PayWayInfo payWayInfo, GoodsInfo goodsInfo, ) { AlipayQrCodeDialog.show( qrCodeHtml: qrHtml, loadSuccessCallback: () { checkPaymentStatus(outTradeNo, payWayInfo, goodsInfo); }, onCloseCallback: () async { //关闭后再持续查询几秒 CustomLoadingDialog.show(); await Future.delayed(Duration(seconds: 4)); paymentStatusManager.removePollingSubscription(outTradeNo); CustomLoadingDialog.hide(); }, ); } void _onAliPay( String outTradeNo, String payJson, int payMethod, GoodsInfo buyGoods, PayWayInfo buyPayWay, ) { final payInfo = AliPayInfo(payJson); requestSdkPay(payInfo, outTradeNo, payMethod, buyGoods, buyPayWay); } void _onWeChatPay( String outTradeNo, String payJson, int payMethod, GoodsInfo buyGoods, PayWayInfo 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 _onWechatScanPay( String outTradeNo, String qrCodeUrl, PayWayInfo payWayInfo, GoodsInfo goodsInfo, ) { WechatQrCodeDialog.show( qrCodeUrl: qrCodeUrl, loadSuccessCallback: () { checkPaymentStatus(outTradeNo, payWayInfo, goodsInfo); }, onCloseCallback: () async { //关闭后再持续查询几秒 CustomLoadingDialog.show(); await Future.delayed(Duration(seconds: 4)); paymentStatusManager.removePollingSubscription(outTradeNo); CustomLoadingDialog.hide(); }, ); } void checkPaymentStatus( String orderNo, PayWayInfo payWayInfo, GoodsInfo goodsInfo, { String? receiptData, }) { paymentStatusManager.registerPaymentSuccessCallback(orderNo, this); paymentStatusManager.checkPaymentStatus( orderNo, payWayInfo, goodsInfo, receiptData: receiptData, ); } void requestSdkPay( dynamic payInfo, String outTradeNo, int payMethod, GoodsInfo buyGoods, PayWayInfo 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'); paymentFail(); 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 onReady() { super.onReady(); // refreshStoreData(); } @override void onInit() { super.onInit(); if (goodsInfoList.isNotEmpty) { _selectedGoodsInfoItem.value = goodsInfoList.first; } if (payWayList.isNotEmpty) { _selectedPayWay.value = payWayList.first; updateFilteredGoodsList(); } ever>(goodsInfoList, (list) { if (list.isNotEmpty) { _selectedGoodsInfoItem.value = list.first; } }); ever>(payWayList, (list) { if (list.isNotEmpty) { _selectedPayWay.value = list.first; updateFilteredGoodsList(); } }); } @override void onClose() { super.onClose(); _storeDataFuture?.cancel(); paymentStatusManager.unregisterPaymentSuccessCallback(this); } @override void onPaymentSuccess( String orderNo, PayWayInfo payWayInfo, GoodsInfo goodsInfo, ) { LoadingDialog.hide(); PaymentSuccessDialog.show( infoText: memberStatusInfo?.permanent == true ? StringName.paySuccessDialogPermanent : "${goodsInfo.name}${DateUtil.fromMillisecondsSinceEpoch('yyyy年MM月dd日', memberStatusInfo?.endTimestamp ?? 0)}${StringName.paySuccessDialogDesc}", btnConfirm: () { AtmobLog.d(tag, 'onGoodsItemClick: ${goodsInfo.toJson()}'); }, ); } }