This commit is contained in:
dzc committed 2026-10-22 19:08:46 +08:00
1 parent 7c20242ad7
commit 382b8d660f
8 files changed
+802 -21

No files matched your search

+54 -13
View File
@@ -3,11 +3,13 @@ import 'dart:convert';
import 'dart:math';
import 'package:common_utils/common_utils.dart';
import 'package:dio/dio.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_subscreen_plugin/flutter_subscreen_plugin.dart';
import 'package:get/get.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
import 'package:pj_supermarket_cloud/common/utils/order/terminal_order_recovery.dart';
mixin OrderMixin {
static const _tag = "OrderMixin";
@@ -15,6 +17,9 @@ mixin OrderMixin {
/// 下单参数日志保留的最大字符数,与 HttpManage 请求日志的截断口径一致。
static const _maxOrderParamLogLength = 2000;
/// 覆盖参数准备、下单、超时对账和本地收尾,避免多种硬件回调同时提交本次订单。
bool _creatingApiOrder = false;
/// 当前收银弹框成功的人脸证据结果,只有人脸支付下单时才会消费并绑定订单号。
Rxn<FaceStream> pendingFaceRecognition = Rxn<FaceStream>();
@@ -227,8 +232,8 @@ mixin OrderMixin {
return false;
}
// 更新本地订单
Future<bool> updateSqlOrder() async {
/// 更新结算快照;只有已由扫码轮询确认支付时才提前写为 PAY,普通下单等待服务端确认。
Future<bool> updateSqlOrder({bool paymentConfirmed = false}) async {
if (ObjectUtil.isEmpty(sqlOrderId)) {
logger.e("|$_tag|更新本地订单,失败本地订单id不存在");
return false;
@@ -253,7 +258,7 @@ mixin OrderMixin {
sqlOrderDetail.hourType = hourType.value.code;
sqlOrderDetail.type = orderType;
sqlOrderDetail.payType = orderPayType.value!;
sqlOrderDetail.status = OrderStatus.PAY;
if (paymentConfirmed) sqlOrderDetail.status = OrderStatus.PAY;
await orderDao.edit(sqlOrderDetail);
logger.i("|$_tag|更新本地订单,成功orderId:|$sqlOrderId|;");
await createSqlChildCar(sqlOrderDetail);
@@ -294,8 +299,19 @@ mixin OrderMixin {
return OrderApiPrepare(order: sqlOrderDetail!, setting: appSetting);
}
// |接口|创建api订单
/// 同一次结算只允许一个在途请求,重试始终沿用本地持久化的订单号。
Future<T?> createApiOrder<T>() async {
if (_creatingApiOrder) return null;
_creatingApiOrder = true;
try {
return await _createApiOrder<T>();
} finally {
_creatingApiOrder = false;
}
}
/// 准备并提交原协议订单;网络响应丢失时先只读对账,确认后统一完成本地状态。
Future<T?> _createApiOrder<T>() async {
final prepare = await createApiOrderValidate();
if (prepare == null) return null;
@@ -306,6 +322,11 @@ mixin OrderMixin {
final orderDao = appC.dataBase.getOrderDao;
// 固定本次请求的支付方式,异步网络处理期间不读取可能被硬件回调更新的选择状态。
final submittedPayType = orderPayType.value!;
final submittedDisplayType = orderPayDisplayType.value!;
final anonymousPayment = OrderPayDisplayType.isOffline(submittedDisplayType);
// 获取下单商品
List<Map<String, dynamic>> shopList = [];
if (_sysType == SysType.CANTEEN) {
@@ -332,7 +353,7 @@ mixin OrderMixin {
final isNetwork = AppNetworkTool.isAutoNetwork();
// 人脸支付订单在发起请求前把识别会话绑定到终端订单号;离线单同样绑定,
// 联网同步并由服务端确认支付后,后台补传任务按订单号对账上传证据。
final isFacePayment = orderPayType.value == OrderPayType.FACE;
final isFacePayment = submittedPayType == OrderPayType.FACE;
final faceSessionId = pendingFaceRecognition.value?.sessionId?.trim();
var evidenceMetadataList = const <Map<String, dynamic>>[];
String? evidenceAppVersion;
@@ -355,19 +376,18 @@ mixin OrderMixin {
"merchantId": serviceMerchantId!,
"orderType": orderType.name,
"orderSourceTypeId": getOrderSourceTypeId(),
"orderPayType": orderPayType.value?.name,
"orderPayType": submittedPayType.name,
"list": shopList,
"extraPrice": amountMealPrice.value,
if (evidenceMetadataList.isNotEmpty)
"evidenceMetadataList": evidenceMetadataList,
if (evidenceAppVersion != null) "appVersion": evidenceAppVersion,
};
if (orderPayType.value != null &&
appSetting.sysType == SysType.CANTEEN &&
(orderPayType.value == OrderPayType.WALLET_QR_CODE)) {
if (appSetting.sysType == SysType.CANTEEN &&
(submittedPayType == OrderPayType.WALLET_QR_CODE)) {
pa["walletId"] = globalQrCode?.id;
} else if (OrderPayDisplayType.isSelectWalletPayType(
orderPayDisplayType.value!)) {
submittedDisplayType)) {
if (payUser.value?.walletId != null) {
pa["walletId"] = payUser.value?.walletId;
}
@@ -382,7 +402,7 @@ mixin OrderMixin {
T? result;
try {
if (isNetwork) {
if (orderPayType.value == OrderPayType.WALLET_QR_CODE_SCAN) {
if (submittedPayType == OrderPayType.WALLET_QR_CODE_SCAN) {
final data = await HardApi.createHardQrCodeScanOrder(pa);
if (data == null) throw Error();
if (ObjectUtil.isEmpty(data)) throw Error();
@@ -391,7 +411,28 @@ mixin OrderMixin {
sqlOrderDetail.serviceUniqueCode = data.orderId;
result = data as T;
} else {
final data = await HardApi.createHardwareOrder(pa);
String? data;
try {
data = await HardApi.createHardwareOrder(pa);
} on DioException catch (error) {
if (!TerminalOrderRecovery.shouldQuery(error)) rethrow;
data = await TerminalOrderRecovery.recover(
lookup: () => HardApi.getOrderByAndroidCode(
hardId: serviceId!,
androidOrderCode: sqlOrderDetail.uniqueCode,
),
// 旧 Flutter 枚举的消费卡 code 与人民医院重复;线上请求按名称编码,
// 对账使用后端既有消费卡编码 14,不改动本地历史枚举和存储协议。
payType: submittedPayType == OrderPayType.CSXFK
? '14'
: submittedPayType.code,
userId: anonymousPayment ? null : pa['userId']?.toString(),
totalPrice: sqlOrderDetail.price,
extraPrice: sqlOrderDetail.extraPrice,
);
if (data == null) rethrow;
logger.i('|$_tag|超时对账确认订单已支付|orderId:$data|');
}
if (data == null) throw Error();
if (ObjectUtil.isEmpty(data)) throw Error();
logger.i("|$_tag||有网络|创建api订单,下单成功res:|$data|");
@@ -437,7 +478,7 @@ mixin OrderMixin {
);
}
}
if (orderPayType.value != OrderPayType.WALLET_QR_CODE_SCAN) {
if (submittedPayType != OrderPayType.WALLET_QR_CODE_SCAN) {
// 支付结果已落库,副屏通知只能尽力发送,不能等待其回执阻塞主屏订单收尾。
unawaited(sendSubScreenPaySuccess());
if (sqlOrderDetail.payPrice != 0) {