Commit 333e28ac by tanghuan

VIP订单支付

1 parent efe7c8be
...@@ -93,6 +93,9 @@ class Constant { ...@@ -93,6 +93,9 @@ class Constant {
static const String wxAppId = 'wx8c32ea248f0c7765'; static const String wxAppId = 'wx8c32ea248f0c7765';
static const String universalLink = 'https://dev.banxiaoer.net/path/to/wechat/'; static const String universalLink = 'https://dev.banxiaoer.net/path/to/wechat/';
/// 微信支付商户号
static const String wxPartnerId = '1514530791';
/// IM 相关 /// IM 相关
/// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ///
......
...@@ -41,6 +41,7 @@ import 'package:appframe/data/repositories/message/wechat_qr_bind_handler.dart'; ...@@ -41,6 +41,7 @@ import 'package:appframe/data/repositories/message/wechat_qr_bind_handler.dart';
import 'package:appframe/data/repositories/message/video_info_handler.dart'; import 'package:appframe/data/repositories/message/video_info_handler.dart';
import 'package:appframe/data/repositories/message/wifi_info_handler.dart'; import 'package:appframe/data/repositories/message/wifi_info_handler.dart';
import 'package:appframe/data/repositories/message/window_info_handler.dart'; import 'package:appframe/data/repositories/message/window_info_handler.dart';
import 'package:appframe/data/repositories/message/xe_create_order_handler.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart'; import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart'; import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:appframe/data/repositories/subs_repository.dart'; import 'package:appframe/data/repositories/subs_repository.dart';
...@@ -222,6 +223,9 @@ Future<void> setupLocator() async { ...@@ -222,6 +223,9 @@ Future<void> setupLocator() async {
/// 设置屏幕模式 /// 设置屏幕模式
getIt.registerLazySingleton<MessageHandler>(() => ScreenHandler(), instanceName: 'setScreen'); getIt.registerLazySingleton<MessageHandler>(() => ScreenHandler(), instanceName: 'setScreen');
/// 创建订单指令
getIt.registerLazySingleton<MessageHandler>(() => XeCreateOrderHandler(), instanceName: 'xeCreateOrder');
/// service /// service
/// ///
/// local server /// local server
......
import 'dart:async';
import 'dart:convert';
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:flutter/foundation.dart';
import 'package:fluwx/fluwx.dart';
class XeCreateOrderHandler extends MessageHandler {
late final Fluwx _fluwx;
late final UserAuthRepository _userAuthRepository;
late FluwxCancelable _fluwxCancelable;
// 支付结果 Completer,用于将异步回调转为 await
Completer<WeChatPaymentResponse>? _payCompleter;
// 超时定时器
Timer? _timeoutTimer;
XeCreateOrderHandler() {
_fluwx = getIt.get<Fluwx>();
_userAuthRepository = getIt.get<UserAuthRepository>();
}
@override
Future<dynamic> handleMessage(params) async {
// 从 params 中提取支付参数
final String chargeCode = params['chargeCode'] as String? ?? '';
final int durationType = params['durationType'] as int? ?? 0;
final int duration = params['duration'] as int? ?? 0;
final int totalFee = params['totalFee'] as int? ?? 0;
final bool renew = params['renew'] as bool? ?? false;
final String phone = params['phone'] as String? ?? '';
final String userCode = params['userCode'] as String? ?? '';
final String tbxStuId = params['tbxStuId'] as String? ?? '';
final String stuId = params['stuId'] as String? ?? '';
// ext 为 JSON 格式字符串
final String ext = params['ext'] as String? ?? '{}';
// 解析 ext
final Map<String, dynamic> extMap = jsonDecode(ext);
final useCoin = extMap['useCoin'] as bool? ?? false;
debugPrint('PayHandler params: chargeCode=$chargeCode, durationType=$durationType, '
'duration=$duration, totalFee=$totalFee, renew=$renew, '
'phone=$phone, userCode=$userCode, tbxStuId=$tbxStuId, stuId=$stuId, '
'ext=$ext');
// 1. 检查微信是否安装
if (!await _fluwx.isWeChatInstalled) {
throw Exception('设备上未安装微信App,不支持微信支付');
}
// 2. 请求后端获取预支付订单信息
final deviceType = 'android';
var result = await _userAuthRepository.createOrder(userCode, tbxStuId, chargeCode, duration, durationType, totalFee,
renew, phone, stuId, ext, useCoin ? 1 : 0, deviceType);
if (result == null) {
throw Exception('获取支付信息失败');
}
var data = result['data'] as Map<String, dynamic>?;
if (data == null) {
throw Exception('支付参数获取失败');
}
// 3. 从后端响应中提取预支付ID
final prepayId = data['prepayId'] as String?;
if (prepayId == null || prepayId.isEmpty) {
throw Exception('预支付订单ID获取失败');
}
// 4. 从后端响应中提取支付参数(nonceStr、timestamp、paySign 由后端生成)
final appId = Constant.wxAppId;
final partnerId = Constant.wxPartnerId;
final packageValue = 'Sign=WXPay';
final nonceStr = data['nonceStr'] as String;
final timestamp = data['timeStamp'] as int;
final paySign = data['paySign'] as String;
final wxTradeNo = data['wxTradeNo'] as String;
debugPrint('prepayId: $prepayId');
debugPrint('nonceStr: $nonceStr');
debugPrint('timestamp: $timestamp');
debugPrint('paySign: $paySign');
debugPrint('appId: $appId');
debugPrint('partnerId: $partnerId');
debugPrint('packageValue: $packageValue');
debugPrint('wxTradeNo: $wxTradeNo');
if (nonceStr.isEmpty || paySign.isEmpty || wxTradeNo.isEmpty) {
throw Exception('支付参数获取失败');
}
// 5. 注册支付结果监听器
_payCompleter = Completer<WeChatPaymentResponse>();
_fluwxCancelable = _fluwx.addSubscriber(_responseListener);
// 6. 启动超时兜底(60秒 * 10)
_timeoutTimer = Timer(const Duration(seconds: 600), () {
if (_payCompleter != null && !_payCompleter!.isCompleted) {
_payCompleter!.completeError(TimeoutException('支付超时'));
}
});
// 7. 调用 fluwx.pay() 拉起微信支付
final payResult = await _fluwx.pay(
which: Payment(
// 微信开放平台 AppID
appId: appId,
// 商户号(固定)
partnerId: partnerId,
// 预支付ID(后端返回)
prepayId: prepayId,
// 固定值 Sign=WXPay
packageValue: packageValue,
// 随机字符串(后端返回)
nonceStr: nonceStr,
// 时间戳(后端返回,秒级)
timestamp: timestamp,
// 签名(后端生成)
sign: paySign,
),
);
if (!payResult) {
_cleanup();
throw Exception('拉起微信支付失败');
}
// 8. 等待支付结果回调
try {
final response = await _payCompleter!.future;
if (response.isSuccessful) {
debugPrint('wechat pay success: ${response.errStr}');
return {"orderUnique": wxTradeNo, "orderState": 1};
} else if (response.errCode == -2) {
debugPrint('wechat pay cancel: ${response.errStr}');
// 用户取消支付
// throw Exception('已取消支付');
return {"orderUnique": wxTradeNo, "orderState": 0};
} else {
debugPrint('wechat pay failed: ${response.errStr}');
// 支付失败
throw Exception('支付失败');
}
} catch (e) {
debugPrint('wechat pay error: $e');
throw Exception('支付异常:$e');
} finally {
_cleanup();
}
}
/// Fluwx 响应监听器
void _responseListener(WeChatResponse response) {
if (response is WeChatPaymentResponse) {
_timeoutTimer?.cancel();
if (_payCompleter != null && !_payCompleter!.isCompleted) {
_payCompleter!.complete(response);
}
}
}
/// 清理资源
void _cleanup() {
_fluwxCancelable.cancel();
_timeoutTimer?.cancel();
_timeoutTimer = null;
_payCompleter = null;
}
}
...@@ -129,4 +129,37 @@ class UserAuthRepository { ...@@ -129,4 +129,37 @@ class UserAuthRepository {
} }
} }
///
/// chargeCodes : bxe_vip
/// duration: durationType=1-天数|=2-月数|=3-yyyyMMdd|=4-学期数
/// durationType : 1.天|2.月|3.特定过期日期|4.学期|5.永久
/// totalFee: 经过计算后的实收价格,单位分
/// useCoin: 1.使用,0.不使用
///
Future<dynamic> createOrder(String userId, String tbxStuId, String chargeCodes, int duration, int durationType, int totalFee,
bool renew, String phone, String stuId, String ext, int useCoin, String deviceType) async {
try {
Response resp = await _appService.post(
'/api/v1/comm/user/buyvip',
{
"userId": userId,
"tbxStuId": tbxStuId,
"chargeCodes": chargeCodes,
"duration": duration,
"durationType": durationType,
"totalFee": totalFee,
"renew": renew,
"phone": phone,
"stuId": stuId,
"ext": ext,
"useCoin": useCoin,
"deviceType": deviceType
},
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
} on DioException {
return null;
}
}
} }
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!