From ad37b6538c6ae4c2ea19c971845f4baa4cebd8d9 Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Thu, 25 Apr 2024 00:52:12 +0800 Subject: [PATCH] feat:1、完成微信支付;2、商品详情页ui优化 --- lib/common/request/apis.dart | 3 +++ lib/common/request/dao/shop_dao.dart | 8 +++++++- lib/pages/reading/bloc/reading_bloc.dart | 4 ++-- lib/pages/shopping/bloc.dart | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- lib/pages/shopping/view.dart | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------ pubspec.yaml | 12 ++++++++++++ 6 files changed, 196 insertions(+), 95 deletions(-) diff --git a/lib/common/request/apis.dart b/lib/common/request/apis.dart index a0e61fb..3e40030 100644 --- a/lib/common/request/apis.dart +++ b/lib/common/request/apis.dart @@ -92,4 +92,7 @@ class Apis { /// 获取阿里支付token static const String getAliPayToken = 'pay/alipay/token'; + /// 获取微信支付token + static const String getWxPayToken = 'pay/wxPay/token'; + } diff --git a/lib/common/request/dao/shop_dao.dart b/lib/common/request/dao/shop_dao.dart index 73c1850..ef8cd26 100644 --- a/lib/common/request/dao/shop_dao.dart +++ b/lib/common/request/dao/shop_dao.dart @@ -13,9 +13,15 @@ class ShopDao { .post>(Apis.createOrder, data: {'courseComboId': productEntity.id}); } - ///获取ali支付订单信息 + ///获取alipay支付订单信息 static Future getAliPayToken(String orderNo) async { return await requestClient .post>(Apis.getAliPayToken, data: {'orderNo': orderNo}); } + + ///获取weixin支付订单信息 + static Future getWxPayToken(String orderNo) async { + return await requestClient + .post>(Apis.getWxPayToken, data: {'orderNo': orderNo}); + } } diff --git a/lib/pages/reading/bloc/reading_bloc.dart b/lib/pages/reading/bloc/reading_bloc.dart index 91fb812..e80812a 100644 --- a/lib/pages/reading/bloc/reading_bloc.dart +++ b/lib/pages/reading/bloc/reading_bloc.dart @@ -247,7 +247,7 @@ class ReadingPageBloc extends Bloc { final recordAudioUrl = currentPageData()?.recordUrl; _playAudio(recordAudioUrl); } - // emit(VoicePlayStateChange()); + // emitter(VoicePlayStateChange()); } void _playAudio(String? audioUrl) async { @@ -358,7 +358,7 @@ class ReadingPageBloc extends Bloc { OnXSVoiceStateChangeEvent event, Emitter emitter ) async { - emit(XSVoiceTestState()); + emitter(XSVoiceTestState()); } } diff --git a/lib/pages/shopping/bloc.dart b/lib/pages/shopping/bloc.dart index c929830..be856c5 100644 --- a/lib/pages/shopping/bloc.dart +++ b/lib/pages/shopping/bloc.dart @@ -1,4 +1,7 @@ +import 'dart:io'; + import 'package:bloc/bloc.dart'; +import 'package:flutter/cupertino.dart'; import 'package:fluwx/fluwx.dart'; import 'package:tobias/tobias.dart'; import 'package:wow_english/generated/json/base/json_convert_content.dart'; @@ -22,6 +25,11 @@ class ShoppingBloc extends Bloc { PaymentChannel get curPaymentChannel => _curPaymentChannel; + Fluwx? fluwx; + Function(WeChatResponse)? wxPayResponseListener; + bool _isWxPayListenerInitialized = false; + + ShoppingBloc(this._productData) : super(ShoppingState().init()) { //页面初始化时刻 @@ -66,19 +74,53 @@ class ShoppingBloc extends Bloc { Log.d("orderNo $orderNo"); if (event.paymentChannel == PaymentChannel.wechatPay) { - Fluwx fluwx = Fluwx(); - fluwx.registerApi(appId: "wxd930ea5d5a228f5f", - universalLink: "https://app-api.wowenglish.com.cn/.well-known/apple-app-site-association"); - // fluwx.pay( - // which: Payment( - // appId: _orderInfo['appid'].toString(), - // partnerId: _orderInfo['partnerid'].toString(), - // prepayId: _orderInfo['prepayid'].toString(), - // packageValue: _orderInfo['package'].toString(), - // nonceStr: _orderInfo['noncestr'].toString(), - // timestamp: _orderInfo['timestamp'], - // sign: _orderInfo['sign'].toString(), - // )); + if (_isWxPayListenerInitialized == false) { + _isWxPayListenerInitialized = true; + fluwx = Fluwx(); + fluwx?.registerApi(appId: "wx365e5a79956a450a", + universalLink: "https://app-api.wowenglish.com.cn/app/"); + wxPayResponseListener = (WeChatResponse response) { + Log.d("wxPayResponseListener $response"); + if (response is WeChatPaymentResponse) { + if (response.errCode == 0) { + Log.d("wxPayResponseListener response=${response.errCode}"); + showToast("支付成功"); + // Log.d("emitter isDone=${emitter.isDone}"); + // emitter(PaySuccessState()); + } else { + showToast("支付失败"); + } + } + }; + fluwx?.addSubscriber(wxPayResponseListener!); + } + + bool installed = await fluwx?.isWeChatInstalled == true; + if (!installed) { + // 未安装微信,请前去下载 + String name = Platform.isIOS ? "AppStore" : "应用商店"; + showToast("您未安装微信,请前往$name下载~"); + return; + } + + final Map wxPayOrderInfo = await ShopDao.getWxPayToken(orderNo); + Log.d("wxPayOrderInfo=$wxPayOrderInfo type=${wxPayOrderInfo.runtimeType}"); + final String? wxPayInfo = wxPayOrderInfo.getOrNull("appId"); + if (wxPayInfo == null) { + showToast("微信订单创建失败"); + return; + } + + await fluwx?.pay( + which: Payment( + appId: wxPayOrderInfo['appId'].toString(), + partnerId: wxPayOrderInfo['partnerId'].toString(), + prepayId: wxPayOrderInfo['prepayId'].toString(), + packageValue: wxPayOrderInfo['packageValue'].toString(), + nonceStr: wxPayOrderInfo['nonceStr'].toString(), + timestamp: int.parse(wxPayOrderInfo['timeStamp']), + sign: wxPayOrderInfo['sign'].toString(), + )); } else { final Map aliPayOrderInfo = await ShopDao.getAliPayToken(orderNo); Log.d("aliPayOrderInfo=$aliPayOrderInfo type=${aliPayOrderInfo.runtimeType}"); @@ -95,7 +137,7 @@ class ShoppingBloc extends Bloc { // 判断resultStatus 为9000则代表支付成功 if (aliPayResult.getOrNull("resultStatus") == "9000") { showToast("支付成功"); - emit(PaySuccessState()); + emitter(PaySuccessState()); } else { showToast("支付失败"); } @@ -107,6 +149,12 @@ class ShoppingBloc extends Bloc { } } } + + void dispose() { + fluwx?.clearSubscribers(); + // 释放资源的逻辑 + debugPrint('BLoC disposed'); + } } enum PaymentChannel { diff --git a/lib/pages/shopping/view.dart b/lib/pages/shopping/view.dart index 32cdcfe..2ad5add 100644 --- a/lib/pages/shopping/view.dart +++ b/lib/pages/shopping/view.dart @@ -8,7 +8,6 @@ import 'package:wow_english/models/product_entity.dart'; import '../../common/core/assets_const.dart'; import '../../common/widgets/we_app_bar.dart'; import '../../utils/image_util.dart'; -import '../../utils/log_util.dart'; import 'bloc.dart'; import 'event.dart'; import 'state.dart'; @@ -17,33 +16,36 @@ Widget buildRadioOption({ required int value, required ImageProvider icon, required String text, - required int groupValue, - required ValueChanged onChanged, + required bool isSelected, + required ValueChanged onSelect, }) { - return Row( - children: [ - Image(image: icon, - width: 20.0.w, - height: 20.0.h, - fit: BoxFit.contain), - const SizedBox(width: 10.0), - // Expanded( - // child: Text( - // text, - // style: TextStyle(color: Color(0xFF333333), fontSize: 12.5.sp), - // ), - // ), - Text( - text, - style: TextStyle(color: Color(0xFF333333), fontSize: 12.5.sp), - ), - Radio( - value: value, - groupValue: groupValue, - onChanged: onChanged + return + GestureDetector( + onTap: () { + onSelect(value); + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image(image: icon, width: 25.0.w, height: 25.0.h, fit: BoxFit.contain), + const SizedBox(width: 10.0), + Expanded( + child: Text( + text, + style: TextStyle(color: const Color(0xFF333333), fontSize: 12.5.sp), + ), + ), + Image( + image: isSelected + ? AssetImage('checked'.assetPng) + : AssetImage('unchecked'.assetPng), + width: 22.0.w, + height: 22.0.h, + ), + ], ), - ], - ); + ); + } class ShoppingPage extends StatelessWidget { @@ -55,30 +57,26 @@ class ShoppingPage extends StatelessWidget { Widget build(BuildContext context) { return BlocProvider( create: (BuildContext context) => - ShoppingBloc(productEntity) - ..add(InitEvent()), + ShoppingBloc(productEntity)..add(InitEvent()), child: _ShoppingView(), ); } } class _ShoppingView extends StatelessWidget { - @override Widget build(BuildContext context) { final bloc = BlocProvider.of(context); - // var title1 = bloc.productData?.name ?? ''; return BlocListener( listener: (context, state) { - Log.d("wqf state=$state"); if (state is PaySuccessState) { Navigator.pop(context); } }, child: Scaffold( - appBar: const WEAppBar( + appBar: WEAppBar( //标题传进来的 - titleText: "商品详情", + titleText: bloc.productData?.name ?? '', ), body: Container( margin: const EdgeInsets.only(left: 80.0, top: 28.0, right: 56.0), @@ -87,49 +85,63 @@ class _ShoppingView extends StatelessWidget { children: [ CachedNetworkImage( imageUrl: bloc.productData?.detailPicUrl ?? '', - imageBuilder: (context, imageProvider) => - Container( - decoration: BoxDecoration( - image: DecorationImage( - image: imageProvider, - fit: BoxFit.cover, - ), - borderRadius: BorderRadius.circular(5.0), - ), + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, ), - placeholder: (context, url) => const CircularProgressIndicator(), + borderRadius: BorderRadius.circular(5.0), + ), + ), + placeholder: (context, url) => + const CircularProgressIndicator(), // errorWidget: (context, url, error) => const Icon(Icons.error), height: 210.0.h, width: 210.0.w, ), const SizedBox(width: 35.5), - _paymentWidget(), + Expanded(child: _paymentWidget()) ], ), ), - ),); + ), + ); } } -Widget _paymentWidget() => - BlocBuilder - ( +Widget _paymentWidget() => BlocBuilder( builder: (context, state) { final bloc = BlocProvider.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('套餐价格:${bloc.productData?.price ?? ''}', - style: TextStyle( - color: const Color(0xFF333333), fontSize: 16.sp)), - const SizedBox(height: 15.0), - Text('套餐名称:${bloc.productData?.name ?? ''}', - style: TextStyle( - color: const Color(0xFF333333), fontSize: 16.sp)), - const SizedBox(height: 15.0), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: '套餐价格:', + style: + TextStyle(color: const Color(0xFF333333), fontSize: 16.5.sp) + ), + TextSpan( + text: '¥${bloc.productData?.price ?? ''}', + style: + TextStyle(color: const Color(0xFFE5262A), fontSize: 16.5.sp) + ), + ] + ), + ), + const SizedBox(height: 14.5), + Text( + '套餐名称:${bloc.productData?.name}', + style: + TextStyle(color: const Color(0xFF333333), fontSize: 16.sp), + maxLines: 2, + ), + const SizedBox(height: 14.5), Container( - padding: - EdgeInsets.symmetric(horizontal: 6.w, vertical: 10.h), + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 10.h), decoration: BoxDecoration( image: DecorationImage( image: ImageUtil.getImageProviderOnDefault( @@ -138,41 +150,61 @@ Widget _paymentWidget() => child: const Text('支付方式选择', style: TextStyle( color: Color(0xFF333333), - fontSize: 12, + fontSize: 12.5, fontFamily: 'PingFangSC-Regular')), ), - const SizedBox(height: 15.0), + const SizedBox(height: 18.0), buildRadioOption( value: PaymentChannel.wechatPay.payChannelType, icon: AssetImage('weixin'.assetPng), text: PaymentChannel.wechatPay.payChannelName, - groupValue: bloc.curPaymentChannel.payChannelType, - onChanged: (newValue) { - bloc.add( - ChangePaymentChannelEvent(PaymentChannel.wechatPay)); + isSelected: bloc.curPaymentChannel.payChannelType == PaymentChannel.wechatPay.payChannelType, + onSelect: (newValue) { + bloc.add(ChangePaymentChannelEvent(PaymentChannel.wechatPay)); }, ), + const SizedBox(height: 15.0), buildRadioOption( value: PaymentChannel.aliPay.payChannelType, icon: AssetImage('zhifubao'.assetPng), text: PaymentChannel.aliPay.payChannelName, - groupValue: bloc.curPaymentChannel.payChannelType, - onChanged: (newValue) { + isSelected: bloc.curPaymentChannel.payChannelType == PaymentChannel.aliPay.payChannelType, + onSelect: (newValue) { bloc.add(ChangePaymentChannelEvent(PaymentChannel.aliPay)); }, ), - const SizedBox(height: 15.0), - // 确认支付按钮 - InkWell( - onTap: () { - Log.d('点击支付按钮 ${bloc.productData}'); - bloc.add(DoPayEvent(bloc.productData, bloc.curPaymentChannel)); - }, - child: Image( - width: 125.w, - height: 45.h, - image: AssetImage('btn_pay'.assetPng), - ), + const SizedBox(height: 20.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RichText( + text: TextSpan( + children: [ + const TextSpan( + text: '需支付:', + style: + TextStyle(color: Color(0xFF333333), fontSize: 12.5, fontFamily: 'PingFangSC-Regular') + ), + TextSpan( + text: '¥${bloc.productData?.price ?? ''}', + style: + TextStyle(color: const Color(0xFFE7383B), fontSize: 41.sp, fontFamily: 'PingFangSC-Regular') + ), + ] + ), + ), + // 确认支付按钮 + GestureDetector( + onTap: () { + bloc.add(DoPayEvent(bloc.productData, bloc.curPaymentChannel)); + }, + child: Image( + width: 105.w, + height: 45.h, + image: AssetImage('btn_pay'.assetPng), + ), + ) + ], ), ], ); diff --git a/pubspec.yaml b/pubspec.yaml index 17c9b01..613e9a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -142,3 +142,15 @@ tobias: url_scheme: ishowwoweng ios: ignore_security: true + +fluwx: + app_id: 'wx365e5a79956a450a' + debug_logging: true # Logging in debug mode. + android: + # interrupt_wx_request: true # Defaults to true. + # flutter_activity: 'MainActivity' # Defaults to app's launcher + ios: + universal_link: https://app-api.wowenglish.com.cn/app/ + # scene_delegate: true # Defaults to false. + # no_pay: false # Set to false to disable payment. + # ignore_security: true # Set to true to disable security seetings. -- libgit2 0.22.2