feat:对接读写卡器
feat:优化用户下拉框搜索 feat:优化下单卡顿问题 feat:优化商户列表样式
This commit is contained in:
1 parent
ceff6c8831
commit
ba7e31dbd8
3 files changed
+116
-3
No files matched your search
@@ -273,6 +273,28 @@ abstract class HardApi {
|
||||
return ServiceHardOrder.fromJson(res.data);
|
||||
}
|
||||
|
||||
/// 创建订单请求超时后,按终端本地订单号反查服务端订单结果(限本设备订单)。
|
||||
static Future<ServiceOrderByAndroidCode?> getOrderByAndroidCode({
|
||||
required String hardId,
|
||||
required String androidOrderCode,
|
||||
}) async {
|
||||
final res = await HttpManage().dio.get(
|
||||
"/hard/hard-api/ad/get-order-by-android-code",
|
||||
queryParameters: {
|
||||
'androidOrderCode': androidOrderCode,
|
||||
'hardId': hardId,
|
||||
},
|
||||
options: dio.Options(
|
||||
headers: {'DEVICE-ID': hardId},
|
||||
extra: {HttpManage.silentRequestKey: true},
|
||||
),
|
||||
);
|
||||
if (res.data == null) return null;
|
||||
return ServiceOrderByAndroidCode.fromJson(
|
||||
Map<String, dynamic>.from(res.data as Map),
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取订单购物车
|
||||
static Future<List<ServiceCar>?> getCarByOrderId(
|
||||
dynamic formData,
|
||||
|
||||
@@ -1097,6 +1097,40 @@ class ServiceHardCreateHardQrCodeScanOrder {
|
||||
_$ServiceHardCreateHardQrCodeScanOrderToJson(this);
|
||||
}
|
||||
|
||||
/// 终端按本地订单号对账查询到的服务端订单最小状态。
|
||||
///
|
||||
/// 创建订单请求超时后用于确认服务端是否已建单扣款;手写 fromJson,
|
||||
/// 不参与 json_serializable 代码生成。
|
||||
class ServiceOrderByAndroidCode {
|
||||
/// 服务端订单 ID
|
||||
String orderId;
|
||||
|
||||
/// 订单状态编码(5=已支付)
|
||||
String orderStatus;
|
||||
|
||||
/// 支付方式编码
|
||||
String payType;
|
||||
|
||||
/// 实付金额(分)
|
||||
int payPrice;
|
||||
|
||||
ServiceOrderByAndroidCode({
|
||||
required this.orderId,
|
||||
required this.orderStatus,
|
||||
required this.payType,
|
||||
required this.payPrice,
|
||||
});
|
||||
|
||||
factory ServiceOrderByAndroidCode.fromJson(Map<String, dynamic> json) {
|
||||
return ServiceOrderByAndroidCode(
|
||||
orderId: '${json['orderId']}',
|
||||
orderStatus: '${json['orderStatus']}',
|
||||
payType: '${json['payType']}',
|
||||
payPrice: (json['payPrice'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 收银终端支付宝、微信聚合订单码创建结果。
|
||||
///
|
||||
/// 该模型只接收 `AGGREGATE_ORDER_PAY` HTTPS 收银地址,不承载自有钱包码或平台原生码。
|
||||
|
||||
@@ -488,9 +488,25 @@ mixin OrderMixin {
|
||||
sqlOrderDetail.serviceUniqueCode = data.orderId;
|
||||
result = data as T;
|
||||
} else {
|
||||
final data = await HardApi.createHardwareOrder(pa);
|
||||
if (data == null) throw Error();
|
||||
if (ObjectUtil.isEmpty(data)) throw Error();
|
||||
String? data;
|
||||
try {
|
||||
data = await HardApi.createHardwareOrder(pa);
|
||||
if (data == null) throw Error();
|
||||
if (ObjectUtil.isEmpty(data)) throw Error();
|
||||
} on DioException catch (e) {
|
||||
// 统一网络层会把服务端业务拒绝(库存不足等)也包装成 DioException 抛出,
|
||||
// 只有超时/连接类异常才存在"服务端已建单扣款但响应丢失"的可能,需要先对账。
|
||||
final isTimeoutLike = e.type == DioExceptionType.connectionTimeout ||
|
||||
e.type == DioExceptionType.sendTimeout ||
|
||||
e.type == DioExceptionType.receiveTimeout ||
|
||||
e.type == DioExceptionType.connectionError;
|
||||
if (!isTimeoutLike) rethrow;
|
||||
data = await _recoverTimeoutCreateOrder(
|
||||
serviceId: serviceId!,
|
||||
order: sqlOrderDetail,
|
||||
);
|
||||
if (data == null) rethrow;
|
||||
}
|
||||
logger.i("|$_tag||有网络|创建api订单,下单成功res:|$data|");
|
||||
sqlOrderDetail.status = OrderStatus.PAY;
|
||||
sqlOrderDetail.serviceUniqueCode = data;
|
||||
@@ -596,6 +612,47 @@ mixin OrderMixin {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 创建订单请求超时后,按终端本地订单号反查服务端订单结果。
|
||||
///
|
||||
/// 网络抖动时服务端处理可能长达数十秒,终端 10 秒就超时,但订单可能已建单扣款成功;
|
||||
/// 此结果必须找回并按成功收尾,否则收银员重试会造成重复下单。服务端已按本地订单号
|
||||
/// 幂等(重复提交返回原订单),即使对账期间收银员再次点击也不会重复扣款。
|
||||
/// 最多轮询 3 次(间隔 3 秒)等待服务端长事务提交;查到同号已支付且金额一致的订单时
|
||||
/// 返回服务端订单号,否则返回 null 由调用方按原失败流程处理。
|
||||
Future<String?> _recoverTimeoutCreateOrder({
|
||||
required String serviceId,
|
||||
required OrderEntity order,
|
||||
}) async {
|
||||
final expectedPayPrice = order.price + order.extraPrice;
|
||||
for (var attempt = 0; attempt < 3; attempt++) {
|
||||
if (attempt > 0) {
|
||||
await Future<void>.delayed(const Duration(seconds: 3));
|
||||
}
|
||||
try {
|
||||
final remote = await HardApi.getOrderByAndroidCode(
|
||||
hardId: serviceId,
|
||||
androidOrderCode: order.uniqueCode,
|
||||
);
|
||||
if (remote == null) continue;
|
||||
// 状态 5=已支付,且金额与本次结算一致才认定为本次订单。
|
||||
if (remote.orderStatus == '5' && remote.payPrice == expectedPayPrice) {
|
||||
logger.i(
|
||||
"|$_tag|超时对账命中已支付订单,orderId:|${remote.orderId}|attempt:|$attempt|",
|
||||
);
|
||||
return remote.orderId;
|
||||
}
|
||||
// 已查到订单但状态或金额不符(如已被退款、金额被调整),不再轮询等待。
|
||||
logger.w(
|
||||
"|$_tag|超时对账查到订单但状态或金额不符,orderStatus:|${remote.orderStatus}|payPrice:|${remote.payPrice}|expected:|$expectedPayPrice|",
|
||||
);
|
||||
return null;
|
||||
} catch (error) {
|
||||
logger.w("|$_tag|超时对账查询失败,attempt:|$attempt|error:|$error|");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 支付成功后从 SQLite 重读当前食堂分类,使扣减后的库存立即展示在商品卡片上。
|
||||
///
|
||||
/// 页面刷新失败不改变已经确认的支付结果;现有 `productSync` 会在后续空闲周期重新校准
|
||||
|
||||
Reference in new issue
Block a user