From ba7e31dbd8e1cf34b03d4a9feddebbe89bebcb2c Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Tue, 13 Oct 2026 17:26:11 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=AF=B9=E6=8E=A5=E8=AF=BB?= =?UTF-8?q?=E5=86=99=E5=8D=A1=E5=99=A8=20feat=EF=BC=9A=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=B8=8B=E6=8B=89=E6=A1=86=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=20feat=EF=BC=9A=E4=BC=98=E5=8C=96=E4=B8=8B=E5=8D=95=E5=8D=A1?= =?UTF-8?q?=E9=A1=BF=E9=97=AE=E9=A2=98=20feat=EF=BC=9A=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=95=86=E6=88=B7=E5=88=97=E8=A1=A8=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/api/hard_api.dart | 22 +++++++ lib/common/models/api/hard_api_models.dart | 34 ++++++++++ .../widgets/order/mixin/order_mixin.dart | 63 ++++++++++++++++++- 3 files changed, 116 insertions(+), 3 deletions(-) diff --git a/lib/common/api/hard_api.dart b/lib/common/api/hard_api.dart index 43df1d8..f0d2158 100644 --- a/lib/common/api/hard_api.dart +++ b/lib/common/api/hard_api.dart @@ -273,6 +273,28 @@ abstract class HardApi { return ServiceHardOrder.fromJson(res.data); } + /// 创建订单请求超时后,按终端本地订单号反查服务端订单结果(限本设备订单)。 + static Future 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.from(res.data as Map), + ); + } + /// 获取订单购物车 static Future?> getCarByOrderId( dynamic formData, diff --git a/lib/common/models/api/hard_api_models.dart b/lib/common/models/api/hard_api_models.dart index 3144d23..42c9acc 100644 --- a/lib/common/models/api/hard_api_models.dart +++ b/lib/common/models/api/hard_api_models.dart @@ -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 json) { + return ServiceOrderByAndroidCode( + orderId: '${json['orderId']}', + orderStatus: '${json['orderStatus']}', + payType: '${json['payType']}', + payPrice: (json['payPrice'] as num?)?.toInt() ?? 0, + ); + } +} + /// 收银终端支付宝、微信聚合订单码创建结果。 /// /// 该模型只接收 `AGGREGATE_ORDER_PAY` HTTPS 收银地址,不承载自有钱包码或平台原生码。 diff --git a/lib/common/widgets/order/mixin/order_mixin.dart b/lib/common/widgets/order/mixin/order_mixin.dart index fcffc02..23ff3de 100644 --- a/lib/common/widgets/order/mixin/order_mixin.dart +++ b/lib/common/widgets/order/mixin/order_mixin.dart @@ -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 _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.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` 会在后续空闲周期重新校准