Commit ad37b6538c6ae4c2ea19c971845f4baa4cebd8d9

Authored by 吴启风
1 parent e62c1a97

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

lib/common/request/apis.dart
... ... @@ -92,4 +92,7 @@ class Apis {
92 92 /// 获取阿里支付token
93 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 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&lt;ReadingPageEvent, ReadingPageState&gt; {
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&lt;ReadingPageEvent, ReadingPageState&gt; {
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&lt;ShoppingEvent, ShoppingState&gt; {
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&lt;ShoppingEvent, ShoppingState&gt; {
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&lt;ShoppingEvent, ShoppingState&gt; {
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&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 160 enum PaymentChannel {
... ...
lib/pages/shopping/view.dart
... ... @@ -8,7 +8,6 @@ import &#39;package:wow_english/models/product_entity.dart&#39;;
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() =&gt;
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.
... ...