Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
ethan
/
appframe
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Commit 333e28ac
authored
2026-07-06 17:29:24 +0800
by
tanghuan
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
VIP订单支付
1 parent
efe7c8be
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
217 additions
and
0 deletions
lib/config/constant.dart
lib/config/locator.dart
lib/data/repositories/message/xe_create_order_handler.dart
lib/data/repositories/user_auth_repository.dart
lib/config/constant.dart
View file @
333e28a
...
...
@@ -93,6 +93,9 @@ class Constant {
static
const
String
wxAppId
=
'wx8c32ea248f0c7765'
;
static
const
String
universalLink
=
'https://dev.banxiaoer.net/path/to/wechat/'
;
/// 微信支付商户号
static
const
String
wxPartnerId
=
'1514530791'
;
/// IM 相关
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
...
...
lib/config/locator.dart
View file @
333e28a
...
...
@@ -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/wifi_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/user_auth_repository.dart'
;
import
'package:appframe/data/repositories/subs_repository.dart'
;
...
...
@@ -222,6 +223,9 @@ Future<void> setupLocator() async {
/// 设置屏幕模式
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
ScreenHandler
(),
instanceName:
'setScreen'
);
/// 创建订单指令
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
XeCreateOrderHandler
(),
instanceName:
'xeCreateOrder'
);
/// service
///
/// local server
...
...
lib/data/repositories/message/xe_create_order_handler.dart
0 → 100644
View file @
333e28a
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
;
}
}
lib/data/repositories/user_auth_repository.dart
View file @
333e28a
...
...
@@ -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
;
}
}
}
Write
Preview
Styling with
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment