Commit ed253529c1c68d6c40a310f2158584b352b80a3a
Merge remote-tracking branch 'origin/feat-wqf-payment' into xiaoyu_cocossteve
Showing
6 changed files
with
196 additions
and
95 deletions
lib/common/request/apis.dart
lib/common/request/dao/shop_dao.dart
| ... | ... | @@ -13,9 +13,15 @@ class ShopDao { |
| 13 | 13 | .post<Map<String, dynamic>>(Apis.createOrder, data: {'courseComboId': productEntity.id}); |
| 14 | 14 | } |
| 15 | 15 | |
| 16 | - ///获取ali支付订单信息 | |
| 16 | + ///获取alipay支付订单信息 | |
| 17 | 17 | static Future getAliPayToken(String orderNo) async { |
| 18 | 18 | return await requestClient |
| 19 | 19 | .post<Map<String, dynamic>>(Apis.getAliPayToken, data: {'orderNo': orderNo}); |
| 20 | 20 | } |
| 21 | + | |
| 22 | + ///获取weixin支付订单信息 | |
| 23 | + static Future getWxPayToken(String orderNo) async { | |
| 24 | + return await requestClient | |
| 25 | + .post<Map<String, dynamic>>(Apis.getWxPayToken, data: {'orderNo': orderNo}); | |
| 26 | + } | |
| 21 | 27 | } | ... | ... |
lib/pages/reading/bloc/reading_bloc.dart
| ... | ... | @@ -247,7 +247,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
| 247 | 247 | final recordAudioUrl = currentPageData()?.recordUrl; |
| 248 | 248 | _playAudio(recordAudioUrl); |
| 249 | 249 | } |
| 250 | - // emit(VoicePlayStateChange()); | |
| 250 | + // emitter(VoicePlayStateChange()); | |
| 251 | 251 | } |
| 252 | 252 | |
| 253 | 253 | void _playAudio(String? audioUrl) async { |
| ... | ... | @@ -358,7 +358,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
| 358 | 358 | OnXSVoiceStateChangeEvent event, |
| 359 | 359 | Emitter<ReadingPageState> emitter |
| 360 | 360 | ) async { |
| 361 | - emit(XSVoiceTestState()); | |
| 361 | + emitter(XSVoiceTestState()); | |
| 362 | 362 | } |
| 363 | 363 | } |
| 364 | 364 | ... | ... |
lib/pages/shopping/bloc.dart
| 1 | +import 'dart:io'; | |
| 2 | + | |
| 1 | 3 | import 'package:bloc/bloc.dart'; |
| 4 | +import 'package:flutter/cupertino.dart'; | |
| 2 | 5 | import 'package:fluwx/fluwx.dart'; |
| 3 | 6 | import 'package:tobias/tobias.dart'; |
| 4 | 7 | import 'package:wow_english/generated/json/base/json_convert_content.dart'; |
| ... | ... | @@ -22,6 +25,11 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
| 22 | 25 | |
| 23 | 26 | PaymentChannel get curPaymentChannel => _curPaymentChannel; |
| 24 | 27 | |
| 28 | + Fluwx? fluwx; | |
| 29 | + Function(WeChatResponse)? wxPayResponseListener; | |
| 30 | + bool _isWxPayListenerInitialized = false; | |
| 31 | + | |
| 32 | + | |
| 25 | 33 | |
| 26 | 34 | ShoppingBloc(this._productData) : super(ShoppingState().init()) { |
| 27 | 35 | //页面初始化时刻 |
| ... | ... | @@ -66,19 +74,53 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
| 66 | 74 | Log.d("orderNo $orderNo"); |
| 67 | 75 | |
| 68 | 76 | if (event.paymentChannel == PaymentChannel.wechatPay) { |
| 69 | - Fluwx fluwx = Fluwx(); | |
| 70 | - fluwx.registerApi(appId: "wxd930ea5d5a228f5f", | |
| 71 | - universalLink: "https://app-api.wowenglish.com.cn/.well-known/apple-app-site-association"); | |
| 72 | - // fluwx.pay( | |
| 73 | - // which: Payment( | |
| 74 | - // appId: _orderInfo['appid'].toString(), | |
| 75 | - // partnerId: _orderInfo['partnerid'].toString(), | |
| 76 | - // prepayId: _orderInfo['prepayid'].toString(), | |
| 77 | - // packageValue: _orderInfo['package'].toString(), | |
| 78 | - // nonceStr: _orderInfo['noncestr'].toString(), | |
| 79 | - // timestamp: _orderInfo['timestamp'], | |
| 80 | - // sign: _orderInfo['sign'].toString(), | |
| 81 | - // )); | |
| 77 | + if (_isWxPayListenerInitialized == false) { | |
| 78 | + _isWxPayListenerInitialized = true; | |
| 79 | + fluwx = Fluwx(); | |
| 80 | + fluwx?.registerApi(appId: "wx365e5a79956a450a", | |
| 81 | + universalLink: "https://app-api.wowenglish.com.cn/app/"); | |
| 82 | + wxPayResponseListener = (WeChatResponse response) { | |
| 83 | + Log.d("wxPayResponseListener $response"); | |
| 84 | + if (response is WeChatPaymentResponse) { | |
| 85 | + if (response.errCode == 0) { | |
| 86 | + Log.d("wxPayResponseListener response=${response.errCode}"); | |
| 87 | + showToast("支付成功"); | |
| 88 | + // Log.d("emitter isDone=${emitter.isDone}"); | |
| 89 | + // emitter(PaySuccessState()); | |
| 90 | + } else { | |
| 91 | + showToast("支付失败"); | |
| 92 | + } | |
| 93 | + } | |
| 94 | + }; | |
| 95 | + fluwx?.addSubscriber(wxPayResponseListener!); | |
| 96 | + } | |
| 97 | + | |
| 98 | + bool installed = await fluwx?.isWeChatInstalled == true; | |
| 99 | + if (!installed) { | |
| 100 | + // 未安装微信,请前去下载 | |
| 101 | + String name = Platform.isIOS ? "AppStore" : "应用商店"; | |
| 102 | + showToast("您未安装微信,请前往$name下载~"); | |
| 103 | + return; | |
| 104 | + } | |
| 105 | + | |
| 106 | + final Map<String, dynamic> wxPayOrderInfo = await ShopDao.getWxPayToken(orderNo); | |
| 107 | + Log.d("wxPayOrderInfo=$wxPayOrderInfo type=${wxPayOrderInfo.runtimeType}"); | |
| 108 | + final String? wxPayInfo = wxPayOrderInfo.getOrNull("appId"); | |
| 109 | + if (wxPayInfo == null) { | |
| 110 | + showToast("微信订单创建失败"); | |
| 111 | + return; | |
| 112 | + } | |
| 113 | + | |
| 114 | + await fluwx?.pay( | |
| 115 | + which: Payment( | |
| 116 | + appId: wxPayOrderInfo['appId'].toString(), | |
| 117 | + partnerId: wxPayOrderInfo['partnerId'].toString(), | |
| 118 | + prepayId: wxPayOrderInfo['prepayId'].toString(), | |
| 119 | + packageValue: wxPayOrderInfo['packageValue'].toString(), | |
| 120 | + nonceStr: wxPayOrderInfo['nonceStr'].toString(), | |
| 121 | + timestamp: int.parse(wxPayOrderInfo['timeStamp']), | |
| 122 | + sign: wxPayOrderInfo['sign'].toString(), | |
| 123 | + )); | |
| 82 | 124 | } else { |
| 83 | 125 | final Map<String, dynamic> aliPayOrderInfo = await ShopDao.getAliPayToken(orderNo); |
| 84 | 126 | Log.d("aliPayOrderInfo=$aliPayOrderInfo type=${aliPayOrderInfo.runtimeType}"); |
| ... | ... | @@ -95,7 +137,7 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
| 95 | 137 | // 判断resultStatus 为9000则代表支付成功 |
| 96 | 138 | if (aliPayResult.getOrNull("resultStatus") == "9000") { |
| 97 | 139 | showToast("支付成功"); |
| 98 | - emit(PaySuccessState()); | |
| 140 | + emitter(PaySuccessState()); | |
| 99 | 141 | } else { |
| 100 | 142 | showToast("支付失败"); |
| 101 | 143 | } |
| ... | ... | @@ -107,6 +149,12 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
| 107 | 149 | } |
| 108 | 150 | } |
| 109 | 151 | } |
| 152 | + | |
| 153 | + void dispose() { | |
| 154 | + fluwx?.clearSubscribers(); | |
| 155 | + // 释放资源的逻辑 | |
| 156 | + debugPrint('BLoC disposed'); | |
| 157 | + } | |
| 110 | 158 | } |
| 111 | 159 | |
| 112 | 160 | enum PaymentChannel { | ... | ... |
lib/pages/shopping/view.dart
| ... | ... | @@ -8,7 +8,6 @@ import 'package:wow_english/models/product_entity.dart'; |
| 8 | 8 | import '../../common/core/assets_const.dart'; |
| 9 | 9 | import '../../common/widgets/we_app_bar.dart'; |
| 10 | 10 | import '../../utils/image_util.dart'; |
| 11 | -import '../../utils/log_util.dart'; | |
| 12 | 11 | import 'bloc.dart'; |
| 13 | 12 | import 'event.dart'; |
| 14 | 13 | import 'state.dart'; |
| ... | ... | @@ -17,33 +16,36 @@ Widget buildRadioOption({ |
| 17 | 16 | required int value, |
| 18 | 17 | required ImageProvider icon, |
| 19 | 18 | required String text, |
| 20 | - required int groupValue, | |
| 21 | - required ValueChanged onChanged, | |
| 19 | + required bool isSelected, | |
| 20 | + required ValueChanged onSelect, | |
| 22 | 21 | }) { |
| 23 | - return Row( | |
| 24 | - children: [ | |
| 25 | - Image(image: icon, | |
| 26 | - width: 20.0.w, | |
| 27 | - height: 20.0.h, | |
| 28 | - fit: BoxFit.contain), | |
| 29 | - const SizedBox(width: 10.0), | |
| 30 | - // Expanded( | |
| 31 | - // child: Text( | |
| 32 | - // text, | |
| 33 | - // style: TextStyle(color: Color(0xFF333333), fontSize: 12.5.sp), | |
| 34 | - // ), | |
| 35 | - // ), | |
| 36 | - Text( | |
| 37 | - text, | |
| 38 | - style: TextStyle(color: Color(0xFF333333), fontSize: 12.5.sp), | |
| 39 | - ), | |
| 40 | - Radio( | |
| 41 | - value: value, | |
| 42 | - groupValue: groupValue, | |
| 43 | - onChanged: onChanged | |
| 22 | + return | |
| 23 | + GestureDetector( | |
| 24 | + onTap: () { | |
| 25 | + onSelect(value); | |
| 26 | + }, | |
| 27 | + child: Row( | |
| 28 | + crossAxisAlignment: CrossAxisAlignment.center, | |
| 29 | + children: [ | |
| 30 | + Image(image: icon, width: 25.0.w, height: 25.0.h, fit: BoxFit.contain), | |
| 31 | + const SizedBox(width: 10.0), | |
| 32 | + Expanded( | |
| 33 | + child: Text( | |
| 34 | + text, | |
| 35 | + style: TextStyle(color: const Color(0xFF333333), fontSize: 12.5.sp), | |
| 36 | + ), | |
| 37 | + ), | |
| 38 | + Image( | |
| 39 | + image: isSelected | |
| 40 | + ? AssetImage('checked'.assetPng) | |
| 41 | + : AssetImage('unchecked'.assetPng), | |
| 42 | + width: 22.0.w, | |
| 43 | + height: 22.0.h, | |
| 44 | + ), | |
| 45 | + ], | |
| 44 | 46 | ), |
| 45 | - ], | |
| 46 | - ); | |
| 47 | + ); | |
| 48 | + | |
| 47 | 49 | } |
| 48 | 50 | |
| 49 | 51 | class ShoppingPage extends StatelessWidget { |
| ... | ... | @@ -55,30 +57,26 @@ class ShoppingPage extends StatelessWidget { |
| 55 | 57 | Widget build(BuildContext context) { |
| 56 | 58 | return BlocProvider( |
| 57 | 59 | create: (BuildContext context) => |
| 58 | - ShoppingBloc(productEntity) | |
| 59 | - ..add(InitEvent()), | |
| 60 | + ShoppingBloc(productEntity)..add(InitEvent()), | |
| 60 | 61 | child: _ShoppingView(), |
| 61 | 62 | ); |
| 62 | 63 | } |
| 63 | 64 | } |
| 64 | 65 | |
| 65 | 66 | class _ShoppingView extends StatelessWidget { |
| 66 | - | |
| 67 | 67 | @override |
| 68 | 68 | Widget build(BuildContext context) { |
| 69 | 69 | final bloc = BlocProvider.of<ShoppingBloc>(context); |
| 70 | - // var title1 = bloc.productData?.name ?? ''; | |
| 71 | 70 | return BlocListener<ShoppingBloc, ShoppingState>( |
| 72 | 71 | listener: (context, state) { |
| 73 | - Log.d("wqf state=$state"); | |
| 74 | 72 | if (state is PaySuccessState) { |
| 75 | 73 | Navigator.pop(context); |
| 76 | 74 | } |
| 77 | 75 | }, |
| 78 | 76 | child: Scaffold( |
| 79 | - appBar: const WEAppBar( | |
| 77 | + appBar: WEAppBar( | |
| 80 | 78 | //标题传进来的 |
| 81 | - titleText: "商品详情", | |
| 79 | + titleText: bloc.productData?.name ?? '', | |
| 82 | 80 | ), |
| 83 | 81 | body: Container( |
| 84 | 82 | margin: const EdgeInsets.only(left: 80.0, top: 28.0, right: 56.0), |
| ... | ... | @@ -87,49 +85,63 @@ class _ShoppingView extends StatelessWidget { |
| 87 | 85 | children: [ |
| 88 | 86 | CachedNetworkImage( |
| 89 | 87 | imageUrl: bloc.productData?.detailPicUrl ?? '', |
| 90 | - imageBuilder: (context, imageProvider) => | |
| 91 | - Container( | |
| 92 | - decoration: BoxDecoration( | |
| 93 | - image: DecorationImage( | |
| 94 | - image: imageProvider, | |
| 95 | - fit: BoxFit.cover, | |
| 96 | - ), | |
| 97 | - borderRadius: BorderRadius.circular(5.0), | |
| 98 | - ), | |
| 88 | + imageBuilder: (context, imageProvider) => Container( | |
| 89 | + decoration: BoxDecoration( | |
| 90 | + image: DecorationImage( | |
| 91 | + image: imageProvider, | |
| 92 | + fit: BoxFit.cover, | |
| 99 | 93 | ), |
| 100 | - placeholder: (context, url) => const CircularProgressIndicator(), | |
| 94 | + borderRadius: BorderRadius.circular(5.0), | |
| 95 | + ), | |
| 96 | + ), | |
| 97 | + placeholder: (context, url) => | |
| 98 | + const CircularProgressIndicator(), | |
| 101 | 99 | // errorWidget: (context, url, error) => const Icon(Icons.error), |
| 102 | 100 | height: 210.0.h, |
| 103 | 101 | width: 210.0.w, |
| 104 | 102 | ), |
| 105 | 103 | const SizedBox(width: 35.5), |
| 106 | - _paymentWidget(), | |
| 104 | + Expanded(child: _paymentWidget()) | |
| 107 | 105 | ], |
| 108 | 106 | ), |
| 109 | 107 | ), |
| 110 | - ),); | |
| 108 | + ), | |
| 109 | + ); | |
| 111 | 110 | } |
| 112 | 111 | } |
| 113 | 112 | |
| 114 | -Widget _paymentWidget() => | |
| 115 | - BlocBuilder<ShoppingBloc, ShoppingState> | |
| 116 | - ( | |
| 113 | +Widget _paymentWidget() => BlocBuilder<ShoppingBloc, ShoppingState>( | |
| 117 | 114 | builder: (context, state) { |
| 118 | 115 | final bloc = BlocProvider.of<ShoppingBloc>(context); |
| 119 | 116 | return Column( |
| 120 | 117 | crossAxisAlignment: CrossAxisAlignment.start, |
| 121 | 118 | children: [ |
| 122 | - Text('套餐价格:${bloc.productData?.price ?? ''}', | |
| 123 | - style: TextStyle( | |
| 124 | - color: const Color(0xFF333333), fontSize: 16.sp)), | |
| 125 | - const SizedBox(height: 15.0), | |
| 126 | - Text('套餐名称:${bloc.productData?.name ?? ''}', | |
| 127 | - style: TextStyle( | |
| 128 | - color: const Color(0xFF333333), fontSize: 16.sp)), | |
| 129 | - const SizedBox(height: 15.0), | |
| 119 | + RichText( | |
| 120 | + text: TextSpan( | |
| 121 | + children: [ | |
| 122 | + TextSpan( | |
| 123 | + text: '套餐价格:', | |
| 124 | + style: | |
| 125 | + TextStyle(color: const Color(0xFF333333), fontSize: 16.5.sp) | |
| 126 | + ), | |
| 127 | + TextSpan( | |
| 128 | + text: '¥${bloc.productData?.price ?? ''}', | |
| 129 | + style: | |
| 130 | + TextStyle(color: const Color(0xFFE5262A), fontSize: 16.5.sp) | |
| 131 | + ), | |
| 132 | + ] | |
| 133 | + ), | |
| 134 | + ), | |
| 135 | + const SizedBox(height: 14.5), | |
| 136 | + Text( | |
| 137 | + '套餐名称:${bloc.productData?.name}', | |
| 138 | + style: | |
| 139 | + TextStyle(color: const Color(0xFF333333), fontSize: 16.sp), | |
| 140 | + maxLines: 2, | |
| 141 | + ), | |
| 142 | + const SizedBox(height: 14.5), | |
| 130 | 143 | Container( |
| 131 | - padding: | |
| 132 | - EdgeInsets.symmetric(horizontal: 6.w, vertical: 10.h), | |
| 144 | + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 10.h), | |
| 133 | 145 | decoration: BoxDecoration( |
| 134 | 146 | image: DecorationImage( |
| 135 | 147 | image: ImageUtil.getImageProviderOnDefault( |
| ... | ... | @@ -138,41 +150,61 @@ Widget _paymentWidget() => |
| 138 | 150 | child: const Text('支付方式选择', |
| 139 | 151 | style: TextStyle( |
| 140 | 152 | color: Color(0xFF333333), |
| 141 | - fontSize: 12, | |
| 153 | + fontSize: 12.5, | |
| 142 | 154 | fontFamily: 'PingFangSC-Regular')), |
| 143 | 155 | ), |
| 144 | - const SizedBox(height: 15.0), | |
| 156 | + const SizedBox(height: 18.0), | |
| 145 | 157 | buildRadioOption( |
| 146 | 158 | value: PaymentChannel.wechatPay.payChannelType, |
| 147 | 159 | icon: AssetImage('weixin'.assetPng), |
| 148 | 160 | text: PaymentChannel.wechatPay.payChannelName, |
| 149 | - groupValue: bloc.curPaymentChannel.payChannelType, | |
| 150 | - onChanged: (newValue) { | |
| 151 | - bloc.add( | |
| 152 | - ChangePaymentChannelEvent(PaymentChannel.wechatPay)); | |
| 161 | + isSelected: bloc.curPaymentChannel.payChannelType == PaymentChannel.wechatPay.payChannelType, | |
| 162 | + onSelect: (newValue) { | |
| 163 | + bloc.add(ChangePaymentChannelEvent(PaymentChannel.wechatPay)); | |
| 153 | 164 | }, |
| 154 | 165 | ), |
| 166 | + const SizedBox(height: 15.0), | |
| 155 | 167 | buildRadioOption( |
| 156 | 168 | value: PaymentChannel.aliPay.payChannelType, |
| 157 | 169 | icon: AssetImage('zhifubao'.assetPng), |
| 158 | 170 | text: PaymentChannel.aliPay.payChannelName, |
| 159 | - groupValue: bloc.curPaymentChannel.payChannelType, | |
| 160 | - onChanged: (newValue) { | |
| 171 | + isSelected: bloc.curPaymentChannel.payChannelType == PaymentChannel.aliPay.payChannelType, | |
| 172 | + onSelect: (newValue) { | |
| 161 | 173 | bloc.add(ChangePaymentChannelEvent(PaymentChannel.aliPay)); |
| 162 | 174 | }, |
| 163 | 175 | ), |
| 164 | - const SizedBox(height: 15.0), | |
| 165 | - // 确认支付按钮 | |
| 166 | - InkWell( | |
| 167 | - onTap: () { | |
| 168 | - Log.d('点击支付按钮 ${bloc.productData}'); | |
| 169 | - bloc.add(DoPayEvent(bloc.productData, bloc.curPaymentChannel)); | |
| 170 | - }, | |
| 171 | - child: Image( | |
| 172 | - width: 125.w, | |
| 173 | - height: 45.h, | |
| 174 | - image: AssetImage('btn_pay'.assetPng), | |
| 175 | - ), | |
| 176 | + const SizedBox(height: 20.0), | |
| 177 | + Row( | |
| 178 | + mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| 179 | + children: [ | |
| 180 | + RichText( | |
| 181 | + text: TextSpan( | |
| 182 | + children: [ | |
| 183 | + const TextSpan( | |
| 184 | + text: '需支付:', | |
| 185 | + style: | |
| 186 | + TextStyle(color: Color(0xFF333333), fontSize: 12.5, fontFamily: 'PingFangSC-Regular') | |
| 187 | + ), | |
| 188 | + TextSpan( | |
| 189 | + text: '¥${bloc.productData?.price ?? ''}', | |
| 190 | + style: | |
| 191 | + TextStyle(color: const Color(0xFFE7383B), fontSize: 41.sp, fontFamily: 'PingFangSC-Regular') | |
| 192 | + ), | |
| 193 | + ] | |
| 194 | + ), | |
| 195 | + ), | |
| 196 | + // 确认支付按钮 | |
| 197 | + GestureDetector( | |
| 198 | + onTap: () { | |
| 199 | + bloc.add(DoPayEvent(bloc.productData, bloc.curPaymentChannel)); | |
| 200 | + }, | |
| 201 | + child: Image( | |
| 202 | + width: 105.w, | |
| 203 | + height: 45.h, | |
| 204 | + image: AssetImage('btn_pay'.assetPng), | |
| 205 | + ), | |
| 206 | + ) | |
| 207 | + ], | |
| 176 | 208 | ), |
| 177 | 209 | ], |
| 178 | 210 | ); | ... | ... |
pubspec.yaml
| ... | ... | @@ -142,3 +142,15 @@ tobias: |
| 142 | 142 | url_scheme: ishowwoweng |
| 143 | 143 | ios: |
| 144 | 144 | ignore_security: true |
| 145 | + | |
| 146 | +fluwx: | |
| 147 | + app_id: 'wx365e5a79956a450a' | |
| 148 | + debug_logging: true # Logging in debug mode. | |
| 149 | + android: | |
| 150 | + # interrupt_wx_request: true # Defaults to true. | |
| 151 | + # flutter_activity: 'MainActivity' # Defaults to app's launcher | |
| 152 | + ios: | |
| 153 | + universal_link: https://app-api.wowenglish.com.cn/app/ | |
| 154 | + # scene_delegate: true # Defaults to false. | |
| 155 | + # no_pay: false # Set to false to disable payment. | |
| 156 | + # ignore_security: true # Set to true to disable security seetings. | ... | ... |