import 'dart:io'; import 'package:agile_pay/flutter_pay.dart'; import 'package:apple_pay/apple_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/api/response/config_response.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/characters_repository.dart'; import 'package:keyboard/data/repository/store_repository.dart'; import 'package:keyboard/dialog/login/login_dialog.dart'; import 'package:keyboard/dialog/payment_fail_dialog.dart'; import 'package:keyboard/dialog/payment_success_dialog.dart'; import 'package:keyboard/module/store/new_discount/new_discount_page.dart'; import 'package:keyboard/module/store/store_banner_bean.dart'; import 'package:keyboard/module/store/store_user_reviews_bean.dart'; import 'package:keyboard/module/store/subscribe/recover_subscribe_dialog.dart'; import 'package:keyboard/module/store/suprise/surprise_dialog.dart'; import 'package:keyboard/module/store/ticket/discount_ticket_dialog.dart'; import 'package:keyboard/module/store/util/store_goods_countdown_manager.dart'; import 'package:keyboard/utils/async_util.dart'; import 'package:keyboard/utils/atmob_log.dart'; import 'package:keyboard/utils/method_chanel_ios_util.dart'; import 'package:keyboard/widget/platform_util.dart'; import 'package:lottie/lottie.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/event_report.dart'; import '../../data/consts/payment_type.dart'; import '../../data/repository/config_repository.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 '../../handler/event_handler.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 ConfigRepository configRepository; final PaymentStatusManager paymentStatusManager; /// 倒计时管理器 final StoreGoodsCountdownManager storeGoodsCountdownManager; 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; late final RxBool isDiscount = false.obs; // 过滤不同支付的商品 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: "用了3天,crush居然主动约我吃饭!AI比我还会撩!", ), StoreUserReviewsBean( avatar: Assets.images.iconStoreUserReview2, userName: "过****人", userReviews: "之前总把天聊死,现在TA说我‘突然变会说话’了😂", ), StoreUserReviewsBean( avatar: Assets.images.iconStoreUserReview3, userName: "不当****solo", userReviews: "星座分析超准!按攻略撩天蝎座,他真的一步步上钩了!", ), StoreUserReviewsBean( avatar: Assets.images.iconStoreUserReview4, userName: "用户****5476", userReviews: "社恐救星!键盘帮我回的消息比我自己发可爱100倍🥰", ), ]; final CarouselSliderController carouselSliderController = CarouselSliderController(); final RxInt _currentBanner = 0.obs; int get currentBannerIndex => _currentBanner.value; /// 商品倒计时,假的倒计时,只会第一个商品会显示 get goodsCountdown => storeGoodsCountdownManager.currentDuration; StoreController( this.storeRepository, this.accountRepository, this.configRepository, this.paymentStatusManager, this.storeGoodsCountdownManager, ); 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; CustomLoadingDialog.show(); var isHasDiscount = await MethodChanelIOSUtil.isHasDiscount(goodsInfoList.first.appleGoodsId); isDiscount.value = isHasDiscount; CustomLoadingDialog.hide(); } if (response.payInfoList?.isNotEmpty == true) { payWayList.addAll(response.payInfoList!); _selectedPayWay.value = payWayList.last; 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(), ); 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() { EventHandler.report(EventId.event_02005); Get.back(); } void clickPayNow() { EventHandler.report(EventId.event_02004); 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!; selectedGoodsInfoItem?.realAmount = selectedGoodsInfoItem?.amount; if (selectedGoodsInfoItem?.discountPriceDesc != null && selectedGoodsInfoItem?.firstAmount != null) { if (isDiscount.value) { selectedGoodsInfoItem?.realAmount = selectedGoodsInfoItem?.firstAmount; } } 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 if (payWayType == PayWayType.paymentWayApple) { _onApplePay( response.outTradeNo, response.appAccountToken ?? "", buyPayWay, buyGoods, ); } else { ToastUtil.show(StringName.payNotSupport); } }) .catchError((error) { LoadingDialog.hide(); if (error is ServerErrorException) { if (error.code == ErrorCode.payOrderError) { refreshStoreData(); ToastUtil.show(error.message); } else if (error.code == ErrorCode.noLoginError) { ToastUtil.show(StringName.accountNoLogin); if (PlatformUtil.isIOS) { LoginPage.start(); } else { LoginDialog.show(); } } else { ToastUtil.show(error.message); paymentFail(); } } else { ToastUtil.show(StringName.memberPaymentFailed); paymentFail(); } }) .whenComplete(() { if (PlatformUtil.isAndroid) { 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(); }, ); } Future _onApplePay( String outTradeNo, String appAccountToken, PayWayInfo payWayInfo, GoodsInfo 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 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(); EventHandler.report(EventId.event_02003); refreshStoreData(); } @override void onInit() async { super.onInit(); } @override void onClose() async { super.onClose(); _storeDataFuture?.cancel(); paymentStatusManager.unregisterPaymentSuccessCallback(this); if (memberStatusInfo != null && memberStatusInfo!.isMember) { return; } if (PlatformUtil.isAndroid) { SurpriseDialog.show( clickConfirm: () { NewDiscountPage.start(); }, clickCancel: () {}, ); } if (PlatformUtil.isIOS) { DiscountTicketDialog.show( clickConfirm: () { NewDiscountPage.start(); }, clickCancel: () {}, ); } } @override void onPaymentSuccess( String orderNo, PayWayInfo payWayInfo, GoodsInfo goodsInfo, ) { if (PlatformUtil.isIOS) { accountRepository.refreshUserInfo(); // 300ms后关闭弹窗 Future.delayed(Duration(seconds: 3), () { LoadingDialog.hide(); Get.back(); PaymentSuccessDialog.show( goodsInfo: goodsInfo, btnConfirm: () { AtmobLog.d(tag, 'onGoodsItemClick: ${goodsInfo.toJson()}'); }, ); }); } else { LoadingDialog.hide(); Get.back(); PaymentSuccessDialog.show( goodsInfo: goodsInfo, btnConfirm: () { AtmobLog.d(tag, 'onGoodsItemClick: ${goodsInfo.toJson()}'); }, ); } } @override void onPaymentError(Error error) { // TODO: implement onPaymentError if (PlatformUtil.isIOS) { LoadingDialog.hide(); ErrorHandler.toastError(error); } } String getUserName() { if (isLogin && phone != null && phone!.length > 4) { return '${StringName.mineAccountLoggedDesc}${phone!.substring(phone!.length - 4)}'; } else { return ""; } } /// 点击了恢复订阅 Future clickRecoverSubscribe() async { PayWayInfo? 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 { PayWayInfo? 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); storeRepository .subscribeResume(payPlatform, payMethod, receiptData) .then((data) { CustomLoadingDialog.hide(); ToastUtil.show("恢复成功"); accountRepository.getUserInfo(); 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"); // } } }