Commit ad37b6538c6ae4c2ea19c971845f4baa4cebd8d9

Authored by 吴启风
1 parent e62c1a97

feat:1、完成微信支付;2、商品详情页ui优化

lib/common/request/apis.dart
@@ -92,4 +92,7 @@ class Apis { @@ -92,4 +92,7 @@ class Apis {
92 /// 获取阿里支付token 92 /// 获取阿里支付token
93 static const String getAliPayToken = 'pay/alipay/token'; 93 static const String getAliPayToken = 'pay/alipay/token';
94 94
  95 + /// 获取微信支付token
  96 + static const String getWxPayToken = 'pay/wxPay/token';
  97 +
95 } 98 }
lib/common/request/dao/shop_dao.dart
@@ -13,9 +13,15 @@ class ShopDao { @@ -13,9 +13,15 @@ class ShopDao {
13 .post<Map<String, dynamic>>(Apis.createOrder, data: {'courseComboId': productEntity.id}); 13 .post<Map<String, dynamic>>(Apis.createOrder, data: {'courseComboId': productEntity.id});
14 } 14 }
15 15
16 - ///获取ali支付订单信息 16 + ///获取alipay支付订单信息
17 static Future getAliPayToken(String orderNo) async { 17 static Future getAliPayToken(String orderNo) async {
18 return await requestClient 18 return await requestClient
19 .post<Map<String, dynamic>>(Apis.getAliPayToken, data: {'orderNo': orderNo}); 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&lt;ReadingPageEvent, ReadingPageState&gt; { @@ -247,7 +247,7 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; {
247 final recordAudioUrl = currentPageData()?.recordUrl; 247 final recordAudioUrl = currentPageData()?.recordUrl;
248 _playAudio(recordAudioUrl); 248 _playAudio(recordAudioUrl);
249 } 249 }
250 - // emit(VoicePlayStateChange()); 250 + // emitter(VoicePlayStateChange());
251 } 251 }
252 252
253 void _playAudio(String? audioUrl) async { 253 void _playAudio(String? audioUrl) async {
@@ -358,7 +358,7 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; { @@ -358,7 +358,7 @@ class ReadingPageBloc extends Bloc&lt;ReadingPageEvent, ReadingPageState&gt; {
358 OnXSVoiceStateChangeEvent event, 358 OnXSVoiceStateChangeEvent event,
359 Emitter<ReadingPageState> emitter 359 Emitter<ReadingPageState> emitter
360 ) async { 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 import 'package:bloc/bloc.dart'; 3 import 'package:bloc/bloc.dart';
  4 +import 'package:flutter/cupertino.dart';
2 import 'package:fluwx/fluwx.dart'; 5 import 'package:fluwx/fluwx.dart';
3 import 'package:tobias/tobias.dart'; 6 import 'package:tobias/tobias.dart';
4 import 'package:wow_english/generated/json/base/json_convert_content.dart'; 7 import 'package:wow_english/generated/json/base/json_convert_content.dart';
@@ -22,6 +25,11 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; { @@ -22,6 +25,11 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; {
22 25
23 PaymentChannel get curPaymentChannel => _curPaymentChannel; 26 PaymentChannel get curPaymentChannel => _curPaymentChannel;
24 27
  28 + Fluwx? fluwx;
  29 + Function(WeChatResponse)? wxPayResponseListener;
  30 + bool _isWxPayListenerInitialized = false;
  31 +
  32 +
25 33
26 ShoppingBloc(this._productData) : super(ShoppingState().init()) { 34 ShoppingBloc(this._productData) : super(ShoppingState().init()) {
27 //页面初始化时刻 35 //页面初始化时刻
@@ -66,19 +74,53 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; { @@ -66,19 +74,53 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; {
66 Log.d("orderNo $orderNo"); 74 Log.d("orderNo $orderNo");
67 75
68 if (event.paymentChannel == PaymentChannel.wechatPay) { 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 } else { 124 } else {
83 final Map<String, dynamic> aliPayOrderInfo = await ShopDao.getAliPayToken(orderNo); 125 final Map<String, dynamic> aliPayOrderInfo = await ShopDao.getAliPayToken(orderNo);
84 Log.d("aliPayOrderInfo=$aliPayOrderInfo type=${aliPayOrderInfo.runtimeType}"); 126 Log.d("aliPayOrderInfo=$aliPayOrderInfo type=${aliPayOrderInfo.runtimeType}");
@@ -95,7 +137,7 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; { @@ -95,7 +137,7 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; {
95 // 判断resultStatus 为9000则代表支付成功 137 // 判断resultStatus 为9000则代表支付成功
96 if (aliPayResult.getOrNull("resultStatus") == "9000") { 138 if (aliPayResult.getOrNull("resultStatus") == "9000") {
97 showToast("支付成功"); 139 showToast("支付成功");
98 - emit(PaySuccessState()); 140 + emitter(PaySuccessState());
99 } else { 141 } else {
100 showToast("支付失败"); 142 showToast("支付失败");
101 } 143 }
@@ -107,6 +149,12 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; { @@ -107,6 +149,12 @@ class ShoppingBloc extends Bloc&lt;ShoppingEvent, ShoppingState&gt; {
107 } 149 }
108 } 150 }
109 } 151 }
  152 +
  153 + void dispose() {
  154 + fluwx?.clearSubscribers();
  155 + // 释放资源的逻辑
  156 + debugPrint('BLoC disposed');
  157 + }
110 } 158 }
111 159
112 enum PaymentChannel { 160 enum PaymentChannel {
lib/pages/shopping/view.dart
@@ -8,7 +8,6 @@ import &#39;package:wow_english/models/product_entity.dart&#39;; @@ -8,7 +8,6 @@ import &#39;package:wow_english/models/product_entity.dart&#39;;
8 import '../../common/core/assets_const.dart'; 8 import '../../common/core/assets_const.dart';
9 import '../../common/widgets/we_app_bar.dart'; 9 import '../../common/widgets/we_app_bar.dart';
10 import '../../utils/image_util.dart'; 10 import '../../utils/image_util.dart';
11 -import '../../utils/log_util.dart';  
12 import 'bloc.dart'; 11 import 'bloc.dart';
13 import 'event.dart'; 12 import 'event.dart';
14 import 'state.dart'; 13 import 'state.dart';
@@ -17,33 +16,36 @@ Widget buildRadioOption({ @@ -17,33 +16,36 @@ Widget buildRadioOption({
17 required int value, 16 required int value,
18 required ImageProvider icon, 17 required ImageProvider icon,
19 required String text, 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 class ShoppingPage extends StatelessWidget { 51 class ShoppingPage extends StatelessWidget {
@@ -55,30 +57,26 @@ class ShoppingPage extends StatelessWidget { @@ -55,30 +57,26 @@ class ShoppingPage extends StatelessWidget {
55 Widget build(BuildContext context) { 57 Widget build(BuildContext context) {
56 return BlocProvider( 58 return BlocProvider(
57 create: (BuildContext context) => 59 create: (BuildContext context) =>
58 - ShoppingBloc(productEntity)  
59 - ..add(InitEvent()), 60 + ShoppingBloc(productEntity)..add(InitEvent()),
60 child: _ShoppingView(), 61 child: _ShoppingView(),
61 ); 62 );
62 } 63 }
63 } 64 }
64 65
65 class _ShoppingView extends StatelessWidget { 66 class _ShoppingView extends StatelessWidget {
66 -  
67 @override 67 @override
68 Widget build(BuildContext context) { 68 Widget build(BuildContext context) {
69 final bloc = BlocProvider.of<ShoppingBloc>(context); 69 final bloc = BlocProvider.of<ShoppingBloc>(context);
70 - // var title1 = bloc.productData?.name ?? '';  
71 return BlocListener<ShoppingBloc, ShoppingState>( 70 return BlocListener<ShoppingBloc, ShoppingState>(
72 listener: (context, state) { 71 listener: (context, state) {
73 - Log.d("wqf state=$state");  
74 if (state is PaySuccessState) { 72 if (state is PaySuccessState) {
75 Navigator.pop(context); 73 Navigator.pop(context);
76 } 74 }
77 }, 75 },
78 child: Scaffold( 76 child: Scaffold(
79 - appBar: const WEAppBar( 77 + appBar: WEAppBar(
80 //标题传进来的 78 //标题传进来的
81 - titleText: "商品详情", 79 + titleText: bloc.productData?.name ?? '',
82 ), 80 ),
83 body: Container( 81 body: Container(
84 margin: const EdgeInsets.only(left: 80.0, top: 28.0, right: 56.0), 82 margin: const EdgeInsets.only(left: 80.0, top: 28.0, right: 56.0),
@@ -87,49 +85,63 @@ class _ShoppingView extends StatelessWidget { @@ -87,49 +85,63 @@ class _ShoppingView extends StatelessWidget {
87 children: [ 85 children: [
88 CachedNetworkImage( 86 CachedNetworkImage(
89 imageUrl: bloc.productData?.detailPicUrl ?? '', 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 // errorWidget: (context, url, error) => const Icon(Icons.error), 99 // errorWidget: (context, url, error) => const Icon(Icons.error),
102 height: 210.0.h, 100 height: 210.0.h,
103 width: 210.0.w, 101 width: 210.0.w,
104 ), 102 ),
105 const SizedBox(width: 35.5), 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 builder: (context, state) { 114 builder: (context, state) {
118 final bloc = BlocProvider.of<ShoppingBloc>(context); 115 final bloc = BlocProvider.of<ShoppingBloc>(context);
119 return Column( 116 return Column(
120 crossAxisAlignment: CrossAxisAlignment.start, 117 crossAxisAlignment: CrossAxisAlignment.start,
121 children: [ 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 Container( 143 Container(
131 - padding:  
132 - EdgeInsets.symmetric(horizontal: 6.w, vertical: 10.h), 144 + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 10.h),
133 decoration: BoxDecoration( 145 decoration: BoxDecoration(
134 image: DecorationImage( 146 image: DecorationImage(
135 image: ImageUtil.getImageProviderOnDefault( 147 image: ImageUtil.getImageProviderOnDefault(
@@ -138,41 +150,61 @@ Widget _paymentWidget() =&gt; @@ -138,41 +150,61 @@ Widget _paymentWidget() =&gt;
138 child: const Text('支付方式选择', 150 child: const Text('支付方式选择',
139 style: TextStyle( 151 style: TextStyle(
140 color: Color(0xFF333333), 152 color: Color(0xFF333333),
141 - fontSize: 12, 153 + fontSize: 12.5,
142 fontFamily: 'PingFangSC-Regular')), 154 fontFamily: 'PingFangSC-Regular')),
143 ), 155 ),
144 - const SizedBox(height: 15.0), 156 + const SizedBox(height: 18.0),
145 buildRadioOption( 157 buildRadioOption(
146 value: PaymentChannel.wechatPay.payChannelType, 158 value: PaymentChannel.wechatPay.payChannelType,
147 icon: AssetImage('weixin'.assetPng), 159 icon: AssetImage('weixin'.assetPng),
148 text: PaymentChannel.wechatPay.payChannelName, 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 buildRadioOption( 167 buildRadioOption(
156 value: PaymentChannel.aliPay.payChannelType, 168 value: PaymentChannel.aliPay.payChannelType,
157 icon: AssetImage('zhifubao'.assetPng), 169 icon: AssetImage('zhifubao'.assetPng),
158 text: PaymentChannel.aliPay.payChannelName, 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 bloc.add(ChangePaymentChannelEvent(PaymentChannel.aliPay)); 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,3 +142,15 @@ tobias:
142 url_scheme: ishowwoweng 142 url_scheme: ishowwoweng
143 ios: 143 ios:
144 ignore_security: true 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.