import 'package:agile_pay/flutter_pay.dart'; import 'package:carousel_slider/carousel_controller.dart'; import 'package:carousel_slider/carousel_options.dart'; import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:injectable/injectable.dart'; import 'package:keyboard/base/base_controller.dart'; import 'package:keyboard/data/bean/pay_way_info.dart'; import 'package:keyboard/data/bean/goods_info.dart'; import 'package:keyboard/data/repository/account_repository.dart'; import 'package:keyboard/data/repository/store_repository.dart'; import 'package:keyboard/dialog/payment_fail_dialog.dart'; import 'package:keyboard/dialog/payment_success_dialog.dart'; import 'package:keyboard/module/store/store_banner_bean.dart'; import 'package:keyboard/module/store/store_user_reviews_bean.dart'; import 'package:keyboard/utils/async_util.dart'; import 'package:keyboard/utils/atmob_log.dart'; import '../../data/api/response/user_info_response.dart'; import '../../data/bean/member_info.dart'; import '../../data/bean/wechat_payment_sign_bean.dart'; import '../../data/consts/error_code.dart'; import '../../data/consts/payment_type.dart'; import '../../dialog/alipay_qr_code_dialog.dart'; import '../../dialog/loading_dialog.dart'; import '../../dialog/member_agreement_dialog.dart'; import '../../dialog/wechat_qr_code_dialog.dart'; import '../../resource/assets.gen.dart'; import '../../resource/string.gen.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 '../login/login_page.dart'; @injectable class StoreController extends BaseController implements PaymentStatusCallback { final tag = 'StoreController'; final StoreRepository storeRepository; final AccountRepository accountRepository; final PaymentStatusManager paymentStatusManager; final RxList goodsInfoList = [].obs; final Rxn _selectedGoodsInfoItem = Rxn(); GoodsInfo? get selectedGoodsInfoItem => _selectedGoodsInfoItem.value; final Rxn _selectedPayWay = Rxn(); PayWayInfo? get selectedPayWay => _selectedPayWay.value; final RxList payWayList = [].obs; CancelableFuture? _storeDataFuture; String? get phone => accountRepository.loginPhoneNum.value; 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; final List bannerList = [ StoreBannerBean( banner: Assets.images.iconStoreBanner1, indicatorImg: Assets.images.iconStoreIndicator1, unSelectedDesc: "助攻追爱", ), StoreBannerBean( banner: Assets.images.iconStoreBanner2, indicatorImg: Assets.images.iconStoreIndicator2, unSelectedDesc: "定制人设", ), StoreBannerBean( banner: Assets.images.iconStoreBanner3, indicatorImg: Assets.images.iconStoreIndicator3, unSelectedDesc: "解锁人设", ), ]; final List userReviewsList = [ StoreUserReviewsBean( avatar: Assets.images.iconStoreUserReview1, userName: "执****念", userReviews: "恋爱键盘超绝!多样人设主题,快捷情话输入,敲出专属浪漫,爱意满屏。1", ), StoreUserReviewsBean( avatar: Assets.images.iconStoreUserReview2, userName: "过****人", userReviews: "恋爱键盘超绝!多样人设主题,快捷情话输入,敲出专属浪漫,爱意满屏。2", ), StoreUserReviewsBean( avatar: Assets.images.iconStoreUserReview3, userName: "不当****solo", userReviews: "恋爱键盘超绝!多样人设主题,快捷情话输入,敲出专属浪漫,爱意满屏。3", ), StoreUserReviewsBean( avatar: Assets.images.iconStoreUserReview4, userName: "用户*****1234", userReviews: "很好,很满意", ), ]; final CarouselSliderController carouselSliderController = CarouselSliderController(); final RxInt _currentBanner = 0.obs; int get currentBannerIndex => _currentBanner.value; StoreController( 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); }); } Future _requestGoodsInfoList() async { try { final response = await storeRepository.getGoodsInfoList(); goodsInfoList.clear(); payWayList.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(); } } 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(), ); } 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); LoginPage.start(); } 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 onClose() { super.onClose(); _storeDataFuture?.cancel(); paymentStatusManager.unregisterPaymentSuccessCallback(this); } @override void onPaymentSuccess( String orderNo, PayWayInfo payWayInfo, GoodsInfo goodsInfo, ) { 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()}'); }, ); } }