@pisell/private-materials 6.12.180 → 6.12.182

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/build/lowcode/assets-daily.json +11 -11
  2. package/build/lowcode/assets-dev.json +2 -2
  3. package/build/lowcode/assets-prod.json +11 -11
  4. package/build/lowcode/meta.js +1 -1
  5. package/build/lowcode/render/default/view.css +1 -1
  6. package/build/lowcode/render/default/view.js +10 -10
  7. package/build/lowcode/view.css +1 -1
  8. package/build/lowcode/view.js +10 -10
  9. package/es/components/HDLKiosk/app/operations.js +0 -3
  10. package/es/components/HDLKiosk/components/common/BlockingNotice/index.js +12 -2
  11. package/es/components/HDLKiosk/components/common/BlockingNotice/index.less +84 -0
  12. package/es/components/HDLKiosk/docs/01-/346/240/270/345/277/203/350/277/220/350/241/214/346/234/272/345/210/266.md +4 -3
  13. package/es/components/HDLKiosk/docs/02-Host/351/200/202/351/205/215/344/270/216/344/270/232/345/212/241/346/225/260/346/215/256.md +1 -1
  14. package/es/components/HDLKiosk/docs/03-/344/270/232/345/212/241/346/265/201/347/250/213/344/270/216/344/272/244/344/272/222/350/247/204/345/210/231.md +2 -2
  15. package/es/components/HDLKiosk/docs/05-/344/272/244/346/230/223/345/256/211/345/205/250.md +2 -2
  16. package/es/components/HDLKiosk/docs//346/236/266/346/236/204/350/256/276/350/256/241.md +1 -1
  17. package/es/components/HDLKiosk/runtime/operations/checkoutSafety.js +2 -1
  18. package/es/components/HDLKiosk/slices/payment/view.js +2 -2
  19. package/es/components/HDLKiosk/ui/HDLKioskWorkflowView.js +16 -4
  20. package/es/components/HDLKiosk/utils.js +2 -2
  21. package/es/components/bigSale/templates/HDLKiosk/index.js +2 -0
  22. package/es/components/bigSale/templates/HDLKiosk/payment/HDLKioskPaymentRuntime.js +2 -2
  23. package/es/components/bigSale/templates/HDLKiosk/payment/noPaymentOrder.js +2 -2
  24. package/es/components/bigSale/templates/HDLKiosk/utils/transactionState.js +8 -4
  25. package/lib/components/HDLKiosk/app/operations.js +0 -3
  26. package/lib/components/HDLKiosk/components/common/BlockingNotice/index.js +12 -2
  27. package/lib/components/HDLKiosk/components/common/BlockingNotice/index.less +84 -0
  28. package/lib/components/HDLKiosk/docs/01-/346/240/270/345/277/203/350/277/220/350/241/214/346/234/272/345/210/266.md +4 -3
  29. package/lib/components/HDLKiosk/docs/02-Host/351/200/202/351/205/215/344/270/216/344/270/232/345/212/241/346/225/260/346/215/256.md +1 -1
  30. package/lib/components/HDLKiosk/docs/03-/344/270/232/345/212/241/346/265/201/347/250/213/344/270/216/344/272/244/344/272/222/350/247/204/345/210/231.md +2 -2
  31. package/lib/components/HDLKiosk/docs/05-/344/272/244/346/230/223/345/256/211/345/205/250.md +2 -2
  32. package/lib/components/HDLKiosk/docs//346/236/266/346/236/204/350/256/276/350/256/241.md +1 -1
  33. package/lib/components/HDLKiosk/runtime/operations/checkoutSafety.js +2 -1
  34. package/lib/components/HDLKiosk/slices/payment/view.js +2 -2
  35. package/lib/components/HDLKiosk/ui/HDLKioskWorkflowView.js +16 -4
  36. package/lib/components/HDLKiosk/utils.js +2 -2
  37. package/lib/components/bigSale/templates/HDLKiosk/index.js +2 -0
  38. package/lib/components/bigSale/templates/HDLKiosk/payment/HDLKioskPaymentRuntime.js +1 -1
  39. package/lib/components/bigSale/templates/HDLKiosk/payment/noPaymentOrder.js +1 -1
  40. package/lib/components/bigSale/templates/HDLKiosk/utils/transactionState.js +8 -4
  41. package/package.json +2 -2
@@ -72,21 +72,18 @@ const checkoutPayEftposOperation = defineOperationContract({
72
72
  id: "checkout.payEftpos",
73
73
  tags: ["checkout-dispatch"],
74
74
  conflictScope: "checkout",
75
- timeoutMs: 12e4,
76
75
  retry: "never"
77
76
  });
78
77
  const checkoutCompleteNoPaymentOperation = defineOperationContract({
79
78
  id: "checkout.completeNoPayment",
80
79
  tags: ["checkout-dispatch"],
81
80
  conflictScope: "checkout",
82
- timeoutMs: 12e4,
83
81
  retry: "never"
84
82
  });
85
83
  const checkoutRequestStaffPaymentApprovalOperation = defineOperationContract({
86
84
  id: "checkout.requestStaffPaymentApproval",
87
85
  tags: ["checkout-authorization"],
88
86
  conflictScope: "checkout",
89
- timeoutMs: 2e4,
90
87
  retry: "explicit-user"
91
88
  });
92
89
  //#endregion
@@ -5,12 +5,22 @@ import React from "react";
5
5
  * 运行机制:只呈现已归一化的安全文案和 trace reference;由外层决定何时挂载。
6
6
  * 边界:不恢复订单、不发起支付,也不推测 Workflow 下一节点。
7
7
  */
8
- function BlockingNotice({ title, description, reference }) {
8
+ function BackIcon() {
9
+ return /* @__PURE__ */ React.createElement("svg", {
10
+ viewBox: "0 0 24 24",
11
+ "aria-hidden": "true"
12
+ }, /* @__PURE__ */ React.createElement("path", { d: "m10 7-5 5 5 5M5 12h14" }));
13
+ }
14
+ function BlockingNotice({ title, description, reference, actionLabel, actionBusy = false, onAction }) {
9
15
  return /* @__PURE__ */ React.createElement("section", {
10
16
  className: "hdl-content-screen hdl-blocked-screen",
11
17
  role: "alert",
12
18
  "aria-live": "assertive"
13
- }, /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true" }, "!"), /* @__PURE__ */ React.createElement("h1", null, title), /* @__PURE__ */ React.createElement("p", null, description), reference ? /* @__PURE__ */ React.createElement("small", null, "Reference: ", reference) : null);
19
+ }, /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true" }, "!"), /* @__PURE__ */ React.createElement("h1", null, title), /* @__PURE__ */ React.createElement("p", null, description), reference ? /* @__PURE__ */ React.createElement("small", null, "Reference: ", reference) : null, actionLabel && onAction ? /* @__PURE__ */ React.createElement("button", {
20
+ type: "button",
21
+ disabled: actionBusy,
22
+ onClick: onAction
23
+ }, /* @__PURE__ */ React.createElement(BackIcon, null), /* @__PURE__ */ React.createElement("span", null, actionLabel)) : null);
14
24
  }
15
25
  //#endregion
16
26
  export { BlockingNotice };
@@ -1,6 +1,90 @@
1
1
  .pisell-hdl-kiosk {
2
+ .hdl-blocked-screen {
3
+ width: min(820px, 100%);
4
+ align-items: center;
5
+ text-align: center;
6
+ }
7
+
8
+ .hdl-blocked-screen > span {
9
+ width: 99px;
10
+ height: 99px;
11
+ margin-bottom: 28px;
12
+ border-radius: 30px;
13
+ background: #fff1f2;
14
+ color: var(--hdl-brand);
15
+ box-shadow: inset 0 0 0 1px rgba(178, 13, 18, 0.08),
16
+ 0 12px 28px rgba(178, 13, 18, 0.1);
17
+ }
18
+
19
+ .hdl-blocked-screen h1 {
20
+ max-width: 650px;
21
+ margin: 0 auto;
22
+ color: #111827;
23
+ font-size: 46px;
24
+ font-weight: 950;
25
+ letter-spacing: -0.04em;
26
+ line-height: 1.08;
27
+ }
28
+
29
+ .hdl-blocked-screen p {
30
+ max-width: 610px;
31
+ margin: 18px auto 0;
32
+ color: var(--hdl-muted);
33
+ font-size: 22px;
34
+ font-weight: 680;
35
+ line-height: 1.5;
36
+ }
37
+
2
38
  .hdl-blocked-screen small {
3
39
  margin-top: 26px;
4
40
  color: var(--hdl-muted);
5
41
  }
42
+
43
+ .hdl-blocked-screen button {
44
+ display: flex;
45
+ width: min(610px, 100%);
46
+ min-height: 72px;
47
+ align-items: center;
48
+ justify-content: center;
49
+ gap: 14px;
50
+ margin-top: 42px;
51
+ border: 0;
52
+ border-radius: 1079px;
53
+ padding: 0 28px;
54
+ background: linear-gradient(180deg, #20b8aa, #119184);
55
+ color: #fff;
56
+ font-size: 20px;
57
+ font-weight: 900;
58
+ box-shadow: 0 15px 32px rgba(17, 145, 132, 0.28);
59
+ cursor: pointer;
60
+ transition: transform 160ms ease, box-shadow 160ms ease;
61
+ }
62
+
63
+ .hdl-blocked-screen button:hover:not(:disabled) {
64
+ transform: translateY(-2px);
65
+ box-shadow: 0 19px 39px rgba(17, 145, 132, 0.32);
66
+ }
67
+
68
+ .hdl-blocked-screen button:active:not(:disabled) {
69
+ transform: translateY(1px) scale(0.99);
70
+ }
71
+
72
+ .hdl-blocked-screen button:focus-visible {
73
+ outline: 4px solid rgba(37, 99, 235, 0.25);
74
+ outline-offset: 3px;
75
+ }
76
+
77
+ .hdl-blocked-screen button:disabled {
78
+ cursor: not-allowed;
79
+ }
80
+
81
+ .hdl-blocked-screen button > svg {
82
+ width: 27px;
83
+ height: 27px;
84
+ fill: none;
85
+ stroke: currentColor;
86
+ stroke-linecap: round;
87
+ stroke-linejoin: round;
88
+ stroke-width: 2;
89
+ }
6
90
  }
@@ -320,8 +320,9 @@ partialFailure/unknown 的 Screen Command 会通过 Actor 的 system block 入
320
320
  | `cart.update/remove` | cart | 15s | 数量修改/删除、summary 重算和目标行验证 |
321
321
  | `product.syncPrimaryWeightedProduct` | cart | — | 首次写入可能等待 OS 额外业务弹窗;更新或写入后验证重量/options 和写后证据 |
322
322
  | `product.removePrimaryProduct` | cart | 15s | 将主商品置为不存在;本来不存在时直接成功,存在时验证删除结果 |
323
- | `checkout.payEftpos` | checkout | 120s | 内部校验 fulfillment、cart、summary 与 token,再派发 EFTPOS |
324
- | `checkout.requestStaffPaymentApproval` | checkout | 20s | 请求主屏店员授权;终态结果由支付订阅单独返回 |
323
+ | `checkout.payEftpos` | checkout | — | 内部校验 fulfillment、cart、summary 与 token,再等待 EFTPOS 终态 |
324
+ | `checkout.completeNoPayment` | checkout | — | 提交零元订单并等待 Host 返回可验证的订单终态 |
325
+ | `checkout.requestStaffPaymentApproval` | checkout | — | 请求主屏店员授权;终态结果由支付订阅单独返回 |
325
326
 
326
327
  ### 7.2 冲突矩阵
327
328
 
@@ -434,7 +435,7 @@ Shell Login
434
435
 
435
436
  当前物料不维护 idle 计时器或警告弹窗。Payment 成功进入 Payment Completed 后立即执行 `session.finalizeCompletedOrder`,验证 Host 空订单和一致性,但保留成功页 Actor 与订单展示快照。10 秒倒计时或手动返回调用 `returnToEntry(reason?)`:它对并发请求去重;已完成清理时直接释放 Actor 和 Ingress,清理失败时补做 `session.reset`,成功后再返回 Entry。
436
437
 
437
- 未来应用级生命周期策略可以决定何时调用该接口,但不能直接清 React 状态或只切换页面。支付 pending/unknown、reset 失败等情况仍由既有安全策略 fail closed。
438
+ 未来应用级生命周期策略可以决定何时调用该接口,但不能直接清 React 状态或只切换页面。支付 pending 继续等待外部终态;unknown 保持重付与订单写入冻结,但允许阻断页显式 reset。reset 失败保留错误证据并允许再次发起恢复,不形成无出口页面。
438
439
 
439
440
  ### 8.6 主动退出订单
440
441
 
@@ -182,7 +182,7 @@ Adapter 不从 Cart 行合计最终金额。
182
182
  | BigSale/OS 原始事实 | HDLKiosk Payment |
183
183
  | ----------------------------------------- | ---------------------------------- |
184
184
  | `unpaid`、`pending`、`payment_processing` | `idle`,仍是可编辑草稿 |
185
- | `payment_pending`、通用 `processing` | `pending`,冻结重付/reset |
185
+ | `payment_pending`、通用 `processing` | `pending`,冻结重付与订单写入;显式 reset 仅作为恢复出口 |
186
186
  | `failed`、`declined` | `failed` |
187
187
  | `paid` + orderId | `succeeded` |
188
188
  | `paid` + 无 orderId + 空 cart | `idle`,识别 OS reset 后的短暂空壳 |
@@ -35,8 +35,8 @@ Entry → Identity → Customization → Catalog → Fulfillment
35
35
  - Cart Overlay 中的卡券和中途登录可能改变金额,Checkout Prepare 必须在这些写入稳定后执行。
36
36
  - EFTPOS 或店员付款只有在 Host 确认 `payment.succeeded` 后才切换为 Checkout Screen 内的支付成功展示。
37
37
  - 成功展示复用 `PaymentResultToastV2`,优先显示 OS 本地订单的 `shop_full_order_number`;本地为空时异步查询只读订单详情,查询期间显示生成中,不得从 payment result 或内部 `orderId` 推断。
38
- - Host 明确确认支付成功并进入 Payment Completed 后立即清空购物车、客户和履约工作区,同时保留支付结果与已完成订单快照供成功页展示;成功页沿用旧 Kiosk 的 10 秒返回首页能力,本地缺少取餐码时,订单详情异步加载的时间不计入倒计时,请求结束后才从完整 10 秒开始。明确取消或失败留在支付页并给出可重试反馈,timeout/unknown 继续阻断重复支付和订单重置。
39
- - Payment Method 提供“重新开始订单”,仅在 payment idle/failed 时可用;二次确认后复用 `session.reset` 安全清空,pending/unknown/succeeded 时禁止执行。
38
+ - Host 明确确认支付成功并进入 Payment Completed 后立即清空购物车、客户和履约工作区,同时保留支付结果与已完成订单快照供成功页展示;成功页沿用旧 Kiosk 的 10 秒返回首页能力,本地缺少取餐码时,订单详情异步加载的时间不计入倒计时,请求结束后才从完整 10 秒开始。明确取消或失败留在支付页并给出可重试反馈,unknown 继续阻断重复支付和订单写入,但阻断页允许显式 reset 返回入口。
39
+ - Payment Method 提供“重新开始订单”,仅在 payment idle/failed 时可用;二次确认后复用 `session.reset` 安全清空。pending 时继续等待外部终态,unknown 则进入带恢复入口的安全阻断页。
40
40
 
41
41
  ## 4. Barcode
42
42
 
@@ -7,10 +7,10 @@
7
7
  1. Host/OS 是订单、金额和支付的最终权威。
8
8
  2. 写命令返回成功不等于业务事实已经可见。
9
9
  3. timeout/unknown 可能已经生效,不能自动重试危险操作。
10
- 4. payment pending/unknown 时不能重付、reset 或开始下一单。
10
+ 4. payment pending/unknown 时不能重付或修改订单;明确的 `session.reset` 是唯一恢复出口。
11
11
  5. 当前不做跨刷新会话恢复;刷新后的订单与支付处置由 Host/OS 负责。
12
12
 
13
- Operation timeout 只保护不应无限等待的机器阶段,不能限制顾客完成选择的时间。会等待规格或 OS 业务弹窗的 Operation 不声明 `timeoutMs`,由 Host 的完成或取消结果结束;登录认证 UI 同样在 Operation 外独立等待。纯机器调用可以声明超时,例如认证完成后的 `identity.sync`。支付 dispatch 是例外:超时后可能已经扣款,必须保留有限等待并进入 `unknown`,禁止直接重试。未声明超时的 Operation 等待期间仍持有冲突域,因此 Host 必须保证正常完成、取消和页面销毁路径最终可终结调用。
13
+ Operation timeout 只保护不应无限等待的机器阶段,不能限制顾客完成选择、设备支付或跨屏授权的时间。会等待规格、OS 业务弹窗、支付设备或外部事件的 Operation 不声明 `timeoutMs`,由 Host 的完成、取消或终态结果结束;登录认证 UI 同样在 Operation 外独立等待。纯机器调用可以声明超时,例如认证完成后的 `identity.sync`。未声明超时的 Operation 等待期间仍持有冲突域,因此 Host 必须保证正常完成、取消和页面销毁路径最终可终结调用。
14
14
 
15
15
  ## 2. Cart Mutation
16
16
 
@@ -168,7 +168,7 @@ Screen:输入和展示状态
168
168
  10. Operation 只有在写后事实收敛后才成功。
169
169
  11. partialFailure、timeout、unknown 不得伪装成失败或成功。
170
170
  12. Checkout opening/pending/unknown 后冻结危险写。
171
- 13. Payment pending/unknown 时不可重付、reset 或开始下一单。
171
+ 13. Payment pending/unknown 时不可重付或修改订单;阻断页只允许显式 `session.reset` 恢复并返回入口。
172
172
  14. Shell 登录不改变 Workflow 节点,完成后必须观察最新 customer/summary。
173
173
  15. OS 加购中的额外业务弹窗属于 OS,HDLKiosk 只消费加购结果。
174
174
  16. Scanner/Scale 在顶层单一接入;Entry Ingress 只负责启动会话,设备入口不是 Workflow Activity;会话内的 Screen 只消费自身所需的实时快照。
@@ -101,7 +101,8 @@ function createCheckoutSafetyController({ tokenTtlMs = DEFAULT_TOKEN_TTL_MS, now
101
101
  state: () => currentState,
102
102
  blocks: (operationTags, scope) => {
103
103
  if (scope === "none") return false;
104
- if (currentState === "succeeded" && (operationTags.includes("session-reset") || operationTags.includes("completed-order-finalize"))) return false;
104
+ if (operationTags.includes("session-reset")) return false;
105
+ if (currentState === "succeeded" && operationTags.includes("completed-order-finalize")) return false;
105
106
  return [
106
107
  "opening",
107
108
  "unknown",
@@ -52,14 +52,14 @@ function createPaymentFeedback(snapshot, sales) {
52
52
  };
53
53
  }
54
54
  function createCheckoutScreenModel(snapshot, sales, payment, completed = false, resetting = false, sourceData, paymentResult) {
55
- var _sourceSummary$amount;
55
+ var _ref, _sourceData$amountDue;
56
56
  const tempOrder = completed ? void 0 : sourceData === null || sourceData === void 0 ? void 0 : sourceData.tempOrder;
57
57
  const completedOrder = completed ? sourceData === null || sourceData === void 0 ? void 0 : sourceData.completedOrder : void 0;
58
58
  const sourceSummary = completed ? void 0 : sourceData === null || sourceData === void 0 ? void 0 : sourceData.summary;
59
59
  const paymentSafeToChange = sales.payment.status === "idle" || sales.payment.status === "failed";
60
60
  const interactionAvailable = !completed && !resetting && snapshot.status === "idle" && paymentSafeToChange;
61
61
  const noPaymentRequired = !completed && isZeroCheckoutDue(sales);
62
- const sourceAmountDue = (_sourceSummary$amount = sourceSummary === null || sourceSummary === void 0 ? void 0 : sourceSummary.amount_gap) !== null && _sourceSummary$amount !== void 0 ? _sourceSummary$amount : sourceSummary === null || sourceSummary === void 0 ? void 0 : sourceSummary.expect_amount;
62
+ const sourceAmountDue = (_ref = (_sourceData$amountDue = sourceData === null || sourceData === void 0 ? void 0 : sourceData.amountDue) !== null && _sourceData$amountDue !== void 0 ? _sourceData$amountDue : sourceSummary === null || sourceSummary === void 0 ? void 0 : sourceSummary.amount_gap) !== null && _ref !== void 0 ? _ref : sourceSummary === null || sourceSummary === void 0 ? void 0 : sourceSummary.expect_amount;
63
63
  const sourceCurrencySymbol = tempOrder === null || tempOrder === void 0 ? void 0 : tempOrder.currency_symbol;
64
64
  const totalText = sales.summary.status === "ready" ? formatCurrencyAmount(sales.summary.due, sales.summary.currencySymbol) : sourceAmountDue !== void 0 && sourceAmountDue !== null ? formatCurrencyAmount(sourceAmountDue, sourceCurrencySymbol) : void 0;
65
65
  return Object.freeze({
@@ -42,11 +42,14 @@ function HDLKioskWorkflowCartOverlay({ model, busy, memberAction, send, renderCa
42
42
  } : void 0
43
43
  });
44
44
  }
45
- function HDLKioskWorkflowBlockingSurface({ snapshot, title, description, reference, hideBottomBar = false }) {
45
+ function HDLKioskWorkflowBlockingSurface({ snapshot, title, description, reference, hideBottomBar = false, actionLabel, actionBusy = false, onAction }) {
46
46
  return /* @__PURE__ */ React.createElement(HDLKioskShell, { snapshot }, /* @__PURE__ */ React.createElement(HDLKioskBottomBarHost, { mode: hideBottomBar ? "hidden" : void 0 }, /* @__PURE__ */ React.createElement(HDLKioskHeader, null), /* @__PURE__ */ React.createElement("main", { className: "hdl-shell__main" }, /* @__PURE__ */ React.createElement(BlockingNotice, {
47
47
  title,
48
48
  description,
49
- reference
49
+ reference,
50
+ actionLabel,
51
+ actionBusy,
52
+ onAction
50
53
  }))));
51
54
  }
52
55
  /**
@@ -124,6 +127,9 @@ function HDLKioskWorkflowView({ session, host, sales, config, isActive, returnin
124
127
  const returnFromPaymentConflict = useCallback(() => {
125
128
  returnToEntry("payment-order-conflict");
126
129
  }, [returnToEntry]);
130
+ const recoverToEntry = useCallback(() => {
131
+ returnToEntry(snapshot.status === "blocked" ? "blocked-recovery" : "reset-retry");
132
+ }, [returnToEntry, snapshot.status]);
127
133
  const customer = useHDLKioskCustomerController({
128
134
  session,
129
135
  snapshot,
@@ -196,7 +202,10 @@ function HDLKioskWorkflowView({ session, host, sales, config, isActive, returnin
196
202
  snapshot,
197
203
  title: locales.getText("pisell2.hdl-kiosk.reset-failed-title"),
198
204
  description: locales.getText("pisell2.hdl-kiosk.reset-failed-description"),
199
- reference: returnError.traceId
205
+ reference: returnError.traceId,
206
+ actionLabel: locales.getText(returning ? "pisell2.hdl-kiosk.resetting-order" : "pisell2.hdl-kiosk.back-home"),
207
+ actionBusy: returning,
208
+ onAction: recoverToEntry
200
209
  });
201
210
  if (snapshot.status === "blocked") {
202
211
  var _snapshot$lastError;
@@ -205,7 +214,10 @@ function HDLKioskWorkflowView({ session, host, sales, config, isActive, returnin
205
214
  title: locales.getText("pisell2.hdl-kiosk.blocked-title"),
206
215
  description: locales.getText("pisell2.hdl-kiosk.blocked-description"),
207
216
  reference: (_snapshot$lastError = snapshot.lastError) === null || _snapshot$lastError === void 0 ? void 0 : _snapshot$lastError.traceId,
208
- hideBottomBar: true
217
+ hideBottomBar: true,
218
+ actionLabel: locales.getText(returning ? "pisell2.hdl-kiosk.resetting-order" : "pisell2.hdl-kiosk.back-home"),
219
+ actionBusy: returning,
220
+ onAction: recoverToEntry
209
221
  });
210
222
  }
211
223
  if (paymentOrderConflict) return /* @__PURE__ */ React.createElement(HDLKioskShell, { snapshot }, /* @__PURE__ */ React.createElement(HDLKioskBottomBarHost, { mode: "hidden" }, /* @__PURE__ */ React.createElement(HDLKioskHeader, null), /* @__PURE__ */ React.createElement("main", { className: "hdl-shell__main" }, /* @__PURE__ */ React.createElement(PaymentConflictNotice, {
@@ -47,7 +47,7 @@ function isMergedSourceBookingLine(line, product) {
47
47
  }
48
48
  /** 直接从 sourceData 就近派生 UI 所需字段,不经过 Host 或 Template DTO。 */
49
49
  function createSourceDataSalesView(sourceData) {
50
- var _sourceData$cartItems, _tempOrder$payment_st, _tempOrder$currency_c, _tempOrder$currency_s, _summary$total_amount, _ref9, _summary$amount_gap, _customer$id, _tempOrder$order_id, _sourceData$salesStat, _summary$product_quan;
50
+ var _sourceData$cartItems, _tempOrder$payment_st, _tempOrder$currency_c, _tempOrder$currency_s, _summary$total_amount, _ref9, _ref10, _sourceData$amountDue, _customer$id, _tempOrder$order_id, _sourceData$salesStat, _summary$product_quan;
51
51
  const lines = Object.freeze(((_sourceData$cartItems = sourceData === null || sourceData === void 0 ? void 0 : sourceData.cartItems) !== null && _sourceData$cartItems !== void 0 ? _sourceData$cartItems : []).map((rawLine) => {
52
52
  var _ref2, _ref3, _rawLine$_extend$disp, _rawLine$_extend, _product$_extend5, _product$product_sku$, _product$product_sku2, _product$product_sku$2, _product$product_sku3, _ref4, _product$product_sku$3, _product$product_sku4, _product$total, _ref5, _product$image_url, _product$image, _product$main_image, _ref6, _product$product_id2, _ref7, _ref8, _product$title, _rawLine$_extend2;
53
53
  const product = readSourceProduct(rawLine);
@@ -83,7 +83,7 @@ function createSourceDataSalesView(sourceData) {
83
83
  currency: String((_tempOrder$currency_c = tempOrder === null || tempOrder === void 0 ? void 0 : tempOrder.currency_code) !== null && _tempOrder$currency_c !== void 0 ? _tempOrder$currency_c : ""),
84
84
  currencySymbol: String((_tempOrder$currency_s = tempOrder === null || tempOrder === void 0 ? void 0 : tempOrder.currency_symbol) !== null && _tempOrder$currency_s !== void 0 ? _tempOrder$currency_s : ""),
85
85
  total: String((_summary$total_amount = summary.total_amount) !== null && _summary$total_amount !== void 0 ? _summary$total_amount : 0),
86
- due: String((_ref9 = (_summary$amount_gap = summary.amount_gap) !== null && _summary$amount_gap !== void 0 ? _summary$amount_gap : summary.expect_amount) !== null && _ref9 !== void 0 ? _ref9 : 0)
86
+ due: String((_ref9 = (_ref10 = (_sourceData$amountDue = sourceData === null || sourceData === void 0 ? void 0 : sourceData.amountDue) !== null && _sourceData$amountDue !== void 0 ? _sourceData$amountDue : summary.amount_gap) !== null && _ref10 !== void 0 ? _ref10 : summary.expect_amount) !== null && _ref9 !== void 0 ? _ref9 : 0)
87
87
  } : { status: "missing" };
88
88
  const customerId = String((_customer$id = customer === null || customer === void 0 ? void 0 : customer.id) !== null && _customer$id !== void 0 ? _customer$id : "");
89
89
  const fingerprint = [
@@ -4,6 +4,7 @@ import { getOrderDetail } from "../../../ticketBooking/serve.js";
4
4
  import localeTexts from "../../../HDLKiosk/locales.js";
5
5
  import HDLKiosk from "../../../HDLKiosk/index.js";
6
6
  import { resolveHDLKioskConfig } from "../../../HDLKiosk/app/contracts/config.js";
7
+ import { resolveBigSaleCurrentAmountDue } from "./utils/transactionState.js";
7
8
  import { useBigSaleHDLKioskAdapter } from "./hooks/useBigSaleHDLKioskAdapter.js";
8
9
  import { useHDLKioskEntryPreparationToast } from "./hooks/useHDLKioskEntryPreparationToast.js";
9
10
  import { HDLKioskPaymentRuntime } from "./payment/HDLKioskPaymentRuntime.js";
@@ -136,6 +137,7 @@ function HDLKioskBigSaleTemplate({ isActive = true, context, businessCode, local
136
137
  cartItems: (_context$cart$display = context.cart.displayLines) !== null && _context$cart$display !== void 0 ? _context$cart$display : context.cart.items,
137
138
  customer: context.customer.selected,
138
139
  summary: context.summary.summary,
140
+ amountDue: resolveBigSaleCurrentAmountDue(context),
139
141
  salesStatus: context.sales.status,
140
142
  cartConsistencyPending: context.cart.consistencyPending
141
143
  },
@@ -3,7 +3,7 @@ import useEngineContext from "../../../../../hooks/useEngineContext.js";
3
3
  import { CHECKOUT_VISIBLE_PAYMENT_CODES } from "../../../../checkout/constants.js";
4
4
  import PaymentMethodRuntime_default from "../../../../checkout/paymentRuntime/PaymentMethodRuntime.js";
5
5
  import { reorderPaymentMethodList } from "../../../../checkout/utils.js";
6
- import { resolveBigSaleCheckoutAmountDue } from "../utils/transactionState.js";
6
+ import { resolveBigSaleCurrentAmountDue } from "../utils/transactionState.js";
7
7
  import { failed } from "../utils/normalize.js";
8
8
  import { executeEftposPayment } from "./eftposPayment.js";
9
9
  import { createStaffPaymentAuthorizationStore, isStaffPaymentAuthorizationCancelled, resolveStaffPaymentResult } from "./staffPaymentAuthorization.js";
@@ -32,7 +32,7 @@ function asKioskMethod(method) {
32
32
  });
33
33
  }
34
34
  function currentAmount(context) {
35
- const amount = resolveBigSaleCheckoutAmountDue(context);
35
+ const amount = resolveBigSaleCurrentAmountDue(context);
36
36
  return typeof amount === "number" && Number.isFinite(amount) && amount > 0 ? amount : 0;
37
37
  }
38
38
  function HDLKioskPaymentRuntime({ context, controllerRef, onPaymentResultChange }) {
@@ -1,6 +1,6 @@
1
1
  import { _asyncToGenerator } from "../../../../../_virtual/_@oxc-project_runtime@0.122.0/helpers/asyncToGenerator.js";
2
2
  import { isSalesOrderSubmitSuccess, resolveSaleDetailOrderIdFromSalesResult } from "../../../../../plus/saleDetail/components/DetailActions/utils.js";
3
- import { resolveBigSaleCheckoutAmountDue, resolveBigSaleLiveTempOrder } from "../utils/transactionState.js";
3
+ import { resolveBigSaleCurrentAmountDue, resolveBigSaleLiveTempOrder } from "../utils/transactionState.js";
4
4
  import { failed, unknown } from "../utils/normalize.js";
5
5
  import { createHDLKioskPaymentResult } from "./paymentResult.js";
6
6
  //#region src/components/bigSale/templates/HDLKiosk/payment/noPaymentOrder.ts
@@ -23,7 +23,7 @@ function completeBigSaleNoPaymentOrder(_x) {
23
23
  function _completeBigSaleNoPaymentOrder() {
24
24
  _completeBigSaleNoPaymentOrder = _asyncToGenerator(function* ({ context, signal, onCompleted }) {
25
25
  var _data$payment_status;
26
- if (resolveBigSaleCheckoutAmountDue(context) !== 0) return failed("noPaymentAmountInvalid", "back");
26
+ if (resolveBigSaleCurrentAmountDue(context) !== 0) return failed("noPaymentAmountInvalid", "back");
27
27
  if (signal.aborted) return {
28
28
  status: "cancelled",
29
29
  reason: "noPaymentAbortedBeforeDispatch"
@@ -17,8 +17,12 @@ function finiteMoney(value) {
17
17
  const amount = Number(value);
18
18
  return Number.isFinite(amount) ? value : void 0;
19
19
  }
20
- /** 与 BigSale Checkout 共用 OS 金额语义,优先使用 BaseSales 当前支付阶段 amountGap。 */
21
- function resolveBigSaleCheckoutAmountDue(context) {
20
+ /**
21
+ * 解析 HDLKiosk 当前临时订单的实时应付金额。
22
+ * 仅用于提交前 To Pay、Payment Amount Due、支付入口和零元门禁;正式提交返回的
23
+ * order_wait_pay_amount 只负责结果一致性确认,不能反向成为 Kiosk 展示状态。
24
+ */
25
+ function resolveBigSaleCurrentAmountDue(context) {
22
26
  var _context$summary$summ, _context$sales$bookin, _context$sales$bookin2, _finiteMoney;
23
27
  const liveOrder = resolveBigSaleLiveTempOrder(context);
24
28
  const summary = (_context$summary$summ = context.summary.summary) !== null && _context$summary$summ !== void 0 ? _context$summary$summ : liveOrder === null || liveOrder === void 0 ? void 0 : liveOrder.summary;
@@ -106,7 +110,7 @@ function projectBigSaleTransactionState(context, observationId = 0) {
106
110
  const rawLines = (_context$cart$display = context.cart.displayLines) !== null && _context$cart$display !== void 0 ? _context$cart$display : context.cart.items;
107
111
  const lines = Object.freeze(rawLines.map(projectBigSaleCartLine).filter((line) => line !== null));
108
112
  const summary = context.summary.summary;
109
- const amountDue = resolveBigSaleCheckoutAmountDue(context);
113
+ const amountDue = resolveBigSaleCurrentAmountDue(context);
110
114
  const liveOrder = resolveBigSaleLiveTempOrder(context);
111
115
  const normalizedSummary = summary && amountDue !== void 0 ? Object.freeze({
112
116
  status: "ready",
@@ -162,4 +166,4 @@ function createBigSaleTransactionStateReader(getContext) {
162
166
  };
163
167
  }
164
168
  //#endregion
165
- export { createBigSaleTransactionStateReader, projectBigSaleCartLine, resolveBigSaleCheckoutAmountDue, resolveBigSaleLineId, resolveBigSaleLiveTempOrder };
169
+ export { createBigSaleTransactionStateReader, projectBigSaleCartLine, resolveBigSaleCurrentAmountDue, resolveBigSaleLineId, resolveBigSaleLiveTempOrder };
@@ -72,21 +72,18 @@ const checkoutPayEftposOperation = defineOperationContract({
72
72
  id: "checkout.payEftpos",
73
73
  tags: ["checkout-dispatch"],
74
74
  conflictScope: "checkout",
75
- timeoutMs: 12e4,
76
75
  retry: "never"
77
76
  });
78
77
  const checkoutCompleteNoPaymentOperation = defineOperationContract({
79
78
  id: "checkout.completeNoPayment",
80
79
  tags: ["checkout-dispatch"],
81
80
  conflictScope: "checkout",
82
- timeoutMs: 12e4,
83
81
  retry: "never"
84
82
  });
85
83
  const checkoutRequestStaffPaymentApprovalOperation = defineOperationContract({
86
84
  id: "checkout.requestStaffPaymentApproval",
87
85
  tags: ["checkout-authorization"],
88
86
  conflictScope: "checkout",
89
- timeoutMs: 2e4,
90
87
  retry: "explicit-user"
91
88
  });
92
89
  //#endregion
@@ -7,12 +7,22 @@ react = require_runtime.__toESM(react);
7
7
  * 运行机制:只呈现已归一化的安全文案和 trace reference;由外层决定何时挂载。
8
8
  * 边界:不恢复订单、不发起支付,也不推测 Workflow 下一节点。
9
9
  */
10
- function BlockingNotice({ title, description, reference }) {
10
+ function BackIcon() {
11
+ return /* @__PURE__ */ react.default.createElement("svg", {
12
+ viewBox: "0 0 24 24",
13
+ "aria-hidden": "true"
14
+ }, /* @__PURE__ */ react.default.createElement("path", { d: "m10 7-5 5 5 5M5 12h14" }));
15
+ }
16
+ function BlockingNotice({ title, description, reference, actionLabel, actionBusy = false, onAction }) {
11
17
  return /* @__PURE__ */ react.default.createElement("section", {
12
18
  className: "hdl-content-screen hdl-blocked-screen",
13
19
  role: "alert",
14
20
  "aria-live": "assertive"
15
- }, /* @__PURE__ */ react.default.createElement("span", { "aria-hidden": "true" }, "!"), /* @__PURE__ */ react.default.createElement("h1", null, title), /* @__PURE__ */ react.default.createElement("p", null, description), reference ? /* @__PURE__ */ react.default.createElement("small", null, "Reference: ", reference) : null);
21
+ }, /* @__PURE__ */ react.default.createElement("span", { "aria-hidden": "true" }, "!"), /* @__PURE__ */ react.default.createElement("h1", null, title), /* @__PURE__ */ react.default.createElement("p", null, description), reference ? /* @__PURE__ */ react.default.createElement("small", null, "Reference: ", reference) : null, actionLabel && onAction ? /* @__PURE__ */ react.default.createElement("button", {
22
+ type: "button",
23
+ disabled: actionBusy,
24
+ onClick: onAction
25
+ }, /* @__PURE__ */ react.default.createElement(BackIcon, null), /* @__PURE__ */ react.default.createElement("span", null, actionLabel)) : null);
16
26
  }
17
27
  //#endregion
18
28
  exports.BlockingNotice = BlockingNotice;
@@ -1,6 +1,90 @@
1
1
  .pisell-hdl-kiosk {
2
+ .hdl-blocked-screen {
3
+ width: min(820px, 100%);
4
+ align-items: center;
5
+ text-align: center;
6
+ }
7
+
8
+ .hdl-blocked-screen > span {
9
+ width: 99px;
10
+ height: 99px;
11
+ margin-bottom: 28px;
12
+ border-radius: 30px;
13
+ background: #fff1f2;
14
+ color: var(--hdl-brand);
15
+ box-shadow: inset 0 0 0 1px rgba(178, 13, 18, 0.08),
16
+ 0 12px 28px rgba(178, 13, 18, 0.1);
17
+ }
18
+
19
+ .hdl-blocked-screen h1 {
20
+ max-width: 650px;
21
+ margin: 0 auto;
22
+ color: #111827;
23
+ font-size: 46px;
24
+ font-weight: 950;
25
+ letter-spacing: -0.04em;
26
+ line-height: 1.08;
27
+ }
28
+
29
+ .hdl-blocked-screen p {
30
+ max-width: 610px;
31
+ margin: 18px auto 0;
32
+ color: var(--hdl-muted);
33
+ font-size: 22px;
34
+ font-weight: 680;
35
+ line-height: 1.5;
36
+ }
37
+
2
38
  .hdl-blocked-screen small {
3
39
  margin-top: 26px;
4
40
  color: var(--hdl-muted);
5
41
  }
42
+
43
+ .hdl-blocked-screen button {
44
+ display: flex;
45
+ width: min(610px, 100%);
46
+ min-height: 72px;
47
+ align-items: center;
48
+ justify-content: center;
49
+ gap: 14px;
50
+ margin-top: 42px;
51
+ border: 0;
52
+ border-radius: 1079px;
53
+ padding: 0 28px;
54
+ background: linear-gradient(180deg, #20b8aa, #119184);
55
+ color: #fff;
56
+ font-size: 20px;
57
+ font-weight: 900;
58
+ box-shadow: 0 15px 32px rgba(17, 145, 132, 0.28);
59
+ cursor: pointer;
60
+ transition: transform 160ms ease, box-shadow 160ms ease;
61
+ }
62
+
63
+ .hdl-blocked-screen button:hover:not(:disabled) {
64
+ transform: translateY(-2px);
65
+ box-shadow: 0 19px 39px rgba(17, 145, 132, 0.32);
66
+ }
67
+
68
+ .hdl-blocked-screen button:active:not(:disabled) {
69
+ transform: translateY(1px) scale(0.99);
70
+ }
71
+
72
+ .hdl-blocked-screen button:focus-visible {
73
+ outline: 4px solid rgba(37, 99, 235, 0.25);
74
+ outline-offset: 3px;
75
+ }
76
+
77
+ .hdl-blocked-screen button:disabled {
78
+ cursor: not-allowed;
79
+ }
80
+
81
+ .hdl-blocked-screen button > svg {
82
+ width: 27px;
83
+ height: 27px;
84
+ fill: none;
85
+ stroke: currentColor;
86
+ stroke-linecap: round;
87
+ stroke-linejoin: round;
88
+ stroke-width: 2;
89
+ }
6
90
  }
@@ -320,8 +320,9 @@ partialFailure/unknown 的 Screen Command 会通过 Actor 的 system block 入
320
320
  | `cart.update/remove` | cart | 15s | 数量修改/删除、summary 重算和目标行验证 |
321
321
  | `product.syncPrimaryWeightedProduct` | cart | — | 首次写入可能等待 OS 额外业务弹窗;更新或写入后验证重量/options 和写后证据 |
322
322
  | `product.removePrimaryProduct` | cart | 15s | 将主商品置为不存在;本来不存在时直接成功,存在时验证删除结果 |
323
- | `checkout.payEftpos` | checkout | 120s | 内部校验 fulfillment、cart、summary 与 token,再派发 EFTPOS |
324
- | `checkout.requestStaffPaymentApproval` | checkout | 20s | 请求主屏店员授权;终态结果由支付订阅单独返回 |
323
+ | `checkout.payEftpos` | checkout | — | 内部校验 fulfillment、cart、summary 与 token,再等待 EFTPOS 终态 |
324
+ | `checkout.completeNoPayment` | checkout | — | 提交零元订单并等待 Host 返回可验证的订单终态 |
325
+ | `checkout.requestStaffPaymentApproval` | checkout | — | 请求主屏店员授权;终态结果由支付订阅单独返回 |
325
326
 
326
327
  ### 7.2 冲突矩阵
327
328
 
@@ -434,7 +435,7 @@ Shell Login
434
435
 
435
436
  当前物料不维护 idle 计时器或警告弹窗。Payment 成功进入 Payment Completed 后立即执行 `session.finalizeCompletedOrder`,验证 Host 空订单和一致性,但保留成功页 Actor 与订单展示快照。10 秒倒计时或手动返回调用 `returnToEntry(reason?)`:它对并发请求去重;已完成清理时直接释放 Actor 和 Ingress,清理失败时补做 `session.reset`,成功后再返回 Entry。
436
437
 
437
- 未来应用级生命周期策略可以决定何时调用该接口,但不能直接清 React 状态或只切换页面。支付 pending/unknown、reset 失败等情况仍由既有安全策略 fail closed。
438
+ 未来应用级生命周期策略可以决定何时调用该接口,但不能直接清 React 状态或只切换页面。支付 pending 继续等待外部终态;unknown 保持重付与订单写入冻结,但允许阻断页显式 reset。reset 失败保留错误证据并允许再次发起恢复,不形成无出口页面。
438
439
 
439
440
  ### 8.6 主动退出订单
440
441
 
@@ -182,7 +182,7 @@ Adapter 不从 Cart 行合计最终金额。
182
182
  | BigSale/OS 原始事实 | HDLKiosk Payment |
183
183
  | ----------------------------------------- | ---------------------------------- |
184
184
  | `unpaid`、`pending`、`payment_processing` | `idle`,仍是可编辑草稿 |
185
- | `payment_pending`、通用 `processing` | `pending`,冻结重付/reset |
185
+ | `payment_pending`、通用 `processing` | `pending`,冻结重付与订单写入;显式 reset 仅作为恢复出口 |
186
186
  | `failed`、`declined` | `failed` |
187
187
  | `paid` + orderId | `succeeded` |
188
188
  | `paid` + 无 orderId + 空 cart | `idle`,识别 OS reset 后的短暂空壳 |
@@ -35,8 +35,8 @@ Entry → Identity → Customization → Catalog → Fulfillment
35
35
  - Cart Overlay 中的卡券和中途登录可能改变金额,Checkout Prepare 必须在这些写入稳定后执行。
36
36
  - EFTPOS 或店员付款只有在 Host 确认 `payment.succeeded` 后才切换为 Checkout Screen 内的支付成功展示。
37
37
  - 成功展示复用 `PaymentResultToastV2`,优先显示 OS 本地订单的 `shop_full_order_number`;本地为空时异步查询只读订单详情,查询期间显示生成中,不得从 payment result 或内部 `orderId` 推断。
38
- - Host 明确确认支付成功并进入 Payment Completed 后立即清空购物车、客户和履约工作区,同时保留支付结果与已完成订单快照供成功页展示;成功页沿用旧 Kiosk 的 10 秒返回首页能力,本地缺少取餐码时,订单详情异步加载的时间不计入倒计时,请求结束后才从完整 10 秒开始。明确取消或失败留在支付页并给出可重试反馈,timeout/unknown 继续阻断重复支付和订单重置。
39
- - Payment Method 提供“重新开始订单”,仅在 payment idle/failed 时可用;二次确认后复用 `session.reset` 安全清空,pending/unknown/succeeded 时禁止执行。
38
+ - Host 明确确认支付成功并进入 Payment Completed 后立即清空购物车、客户和履约工作区,同时保留支付结果与已完成订单快照供成功页展示;成功页沿用旧 Kiosk 的 10 秒返回首页能力,本地缺少取餐码时,订单详情异步加载的时间不计入倒计时,请求结束后才从完整 10 秒开始。明确取消或失败留在支付页并给出可重试反馈,unknown 继续阻断重复支付和订单写入,但阻断页允许显式 reset 返回入口。
39
+ - Payment Method 提供“重新开始订单”,仅在 payment idle/failed 时可用;二次确认后复用 `session.reset` 安全清空。pending 时继续等待外部终态,unknown 则进入带恢复入口的安全阻断页。
40
40
 
41
41
  ## 4. Barcode
42
42
 
@@ -7,10 +7,10 @@
7
7
  1. Host/OS 是订单、金额和支付的最终权威。
8
8
  2. 写命令返回成功不等于业务事实已经可见。
9
9
  3. timeout/unknown 可能已经生效,不能自动重试危险操作。
10
- 4. payment pending/unknown 时不能重付、reset 或开始下一单。
10
+ 4. payment pending/unknown 时不能重付或修改订单;明确的 `session.reset` 是唯一恢复出口。
11
11
  5. 当前不做跨刷新会话恢复;刷新后的订单与支付处置由 Host/OS 负责。
12
12
 
13
- Operation timeout 只保护不应无限等待的机器阶段,不能限制顾客完成选择的时间。会等待规格或 OS 业务弹窗的 Operation 不声明 `timeoutMs`,由 Host 的完成或取消结果结束;登录认证 UI 同样在 Operation 外独立等待。纯机器调用可以声明超时,例如认证完成后的 `identity.sync`。支付 dispatch 是例外:超时后可能已经扣款,必须保留有限等待并进入 `unknown`,禁止直接重试。未声明超时的 Operation 等待期间仍持有冲突域,因此 Host 必须保证正常完成、取消和页面销毁路径最终可终结调用。
13
+ Operation timeout 只保护不应无限等待的机器阶段,不能限制顾客完成选择、设备支付或跨屏授权的时间。会等待规格、OS 业务弹窗、支付设备或外部事件的 Operation 不声明 `timeoutMs`,由 Host 的完成、取消或终态结果结束;登录认证 UI 同样在 Operation 外独立等待。纯机器调用可以声明超时,例如认证完成后的 `identity.sync`。未声明超时的 Operation 等待期间仍持有冲突域,因此 Host 必须保证正常完成、取消和页面销毁路径最终可终结调用。
14
14
 
15
15
  ## 2. Cart Mutation
16
16
 
@@ -168,7 +168,7 @@ Screen:输入和展示状态
168
168
  10. Operation 只有在写后事实收敛后才成功。
169
169
  11. partialFailure、timeout、unknown 不得伪装成失败或成功。
170
170
  12. Checkout opening/pending/unknown 后冻结危险写。
171
- 13. Payment pending/unknown 时不可重付、reset 或开始下一单。
171
+ 13. Payment pending/unknown 时不可重付或修改订单;阻断页只允许显式 `session.reset` 恢复并返回入口。
172
172
  14. Shell 登录不改变 Workflow 节点,完成后必须观察最新 customer/summary。
173
173
  15. OS 加购中的额外业务弹窗属于 OS,HDLKiosk 只消费加购结果。
174
174
  16. Scanner/Scale 在顶层单一接入;Entry Ingress 只负责启动会话,设备入口不是 Workflow Activity;会话内的 Screen 只消费自身所需的实时快照。
@@ -101,7 +101,8 @@ function createCheckoutSafetyController({ tokenTtlMs = DEFAULT_TOKEN_TTL_MS, now
101
101
  state: () => currentState,
102
102
  blocks: (operationTags, scope) => {
103
103
  if (scope === "none") return false;
104
- if (currentState === "succeeded" && (operationTags.includes("session-reset") || operationTags.includes("completed-order-finalize"))) return false;
104
+ if (operationTags.includes("session-reset")) return false;
105
+ if (currentState === "succeeded" && operationTags.includes("completed-order-finalize")) return false;
105
106
  return [
106
107
  "opening",
107
108
  "unknown",