Explorar el Código

chore(汇安融): 存量「被上游拒绝却还在轮询」的订单收尾 SQL 交付件(待用户执行)

代码那半(a5fbd76)只管「以后不再产生这种行」,而用户报的现象是**已经躺在库里的**那些行
每轮都在报「上游找不到订单」。这个脚本修存量。

判据三格取交集,逐格都在文件里写了为什么,其中一格是**安全关键**:
被拒的行 description 是「上游返回 …」(create 失败分支写的,这张表上只有它写这个前缀),
而**超时的行 description 还是商品标题** —— 上游超时是「未知」不是拒绝,那笔单可能已经
在上游建好了,必须留在「0 等待支付」继续等收款通知。把两类混在一起关掉,就是把一笔
可能已经收款的单写成「已关闭」,而付款通知到达时会因为本地已终态被幂等跳过:钱在,账没了。
文件里专门写了「别把这条 LIKE 判据换成『凡是没有 upstream_order_no 的 0 都关掉』」。

默认**不删行**(收尾成「8 交易关闭」+ 清 next_query_time),理由同 a5fbd76;
要删的话文件末尾留了同一判据的 DELETE,由用户确认清单后自己换。

幂等:跑完这些行变成 '8',再跑判据不命中。★ 先发版再跑,否则旧包还在跑、修完又冒新的。
alphaH hace 2 semanas
padre
commit
9680b6b79f
Se han modificado 1 ficheros con 79 adiciones y 0 borrados
  1. 79 0
      java/src/main/resources/db/har-close-rejected-orders.sql

+ 79 - 0
java/src/main/resources/db/har-close-rejected-orders.sql

@@ -0,0 +1,79 @@
+-- 【数据修复,不是 DDL】把「上游明确拒绝、本地却还在轮询」的存量订单收尾(2026-09-28)
+-- 手工执行: psycopg2 / psql 连接 payment_platform_java 库
+--
+-- 【本脚本尚未执行】执行由用户做。本平台没有直连该库的通道,执行读数只能来自执行者本人。
+--
+-- ── 先做什么 ─────────────────────────────────────────────────────────
+-- ★ **先发版,再跑这个脚本。** 代码那边(HarOrderService#closeRejectedOrder)修的是
+--   「以后不再产生这种行」;这个脚本修的是**已经躺在库里的**。顺序反过来的话,
+--   旧包还在跑,修完又会冒新的出来。
+--
+-- ── 背景:这些行为什么要收尾 ─────────────────────────────────────────
+-- 旧版 create 是「先落库、再发上游」,失败分支只改了 description,而 trade_status 停在
+-- 「0 等待支付」、next_query_time 已经排到 30 秒后。轮询按 trade_status IN ('0','4')
+-- 捞单(HarOrderMapper#findPendingForPoll),于是这些行每轮都被捞起来去上游问一笔
+-- **在上游从来没被建出来**的订单 —— 日志里一直刷「查询返回非成功码 / 找不到订单」,
+-- 退避封顶 900 秒也只是把「一直报」拉长成「每 15 分钟报一次」。
+--
+-- 收尾动作与代码里那两格完全一致:trade_status 置 '8'(不在 PENDING 里,捞单条件立刻失效)
+-- + next_query_time 写回 NULL。
+--
+-- ── 判据(三格取交集,逐格说清为什么)────────────────────────────────
+--   trade_status = '0'           :只动还在轮询起点的;已经是 '8' 的(新版写的)不碰
+--   description LIKE '上游返回 %' :**这是被拒的唯一指纹**。create 的失败分支写的就是
+--                                  '上游返回 ' + response_code + ': ' + msg,全仓只有它写这个前缀
+--                                  (HarShopService 那句写在 pay_har_shop.fail_reason,不是这张表)
+--   upstream_order_no IS NULL    :再确认一次「上游确实没建出来」。被拒的单必然没有上游单号;
+--                                  这一格非空却带这个 description 的话属于另一类异常,别硬塞进本脚本
+--
+-- ★ **超时那条路不在这里,也不能在这里。** 上游超时(HarApiException)是「未知」不是拒绝:
+--   那笔单**可能已经在上游建好了**,本地必须留在「0 等待支付」继续轮询等收款通知。
+--   把它们也关掉就是把一笔可能已经收款的单写成「已关闭」,而付款通知到达时会因为
+--   本地已是终态被幂等跳过 —— 钱在,账没了。
+--   两类行的区别**就在 description**:超时的行 description 还是下单时写的商品标题(没被改过),
+--   被拒的行才是 '上游返回 …'。这就是上面那条 LIKE 判据的全部意义,别把它换成
+--   「凡是没有 upstream_order_no 的 0 都关掉」——那一条会把超时的单一起杀了。
+--
+-- ── 幂等 ─────────────────────────────────────────────────────────────
+-- 是。跑完这些行变成 '8',再跑一次判据不命中,影响 0 行。
+--
+-- ── 执行 ─────────────────────────────────────────────────────────────
+-- 第 1 步:先看命中哪些行(只读,先跑它,确认清单符合预期再跑第 2 步)
+-- SELECT order_no, out_biz_no, merchant_code, trade_status, query_count,
+--        next_query_time, created_time, description
+--   FROM pay_har_order
+--  WHERE trade_status = '0'
+--    AND description LIKE '上游返回 %'
+--    AND upstream_order_no IS NULL
+--  ORDER BY created_time;
+
+-- 第 2 步:收尾(跑完看 UPDATE 报了几行,应当与第 1 步的行数一致)
+UPDATE pay_har_order
+   SET trade_status    = '8',
+       next_query_time = NULL,
+       updated_time    = now()::timestamp,
+       version         = version + 1
+ WHERE trade_status = '0'
+   AND description LIKE '上游返回 %'
+   AND upstream_order_no IS NULL;
+
+-- version 那一格:MyBatis-Plus 的 @Version 让 updateById 带上 "AND version = ?",
+-- 手动 +1 是为了让**手里攥着旧 version 的并发写**(轮询任务正好也在处理这一行)
+-- 落不下去,而不是把我们的收尾覆盖掉。updated_time 用会话本地时间,这一格只是给人看的。
+
+-- ── 只读核对(执行后跑)──────────────────────────────────────────────
+-- 期望:第一段返回 0 行(没有漏网的),第二段返回的 order_no 与第 1 步清单一致。
+-- SELECT count(*) AS 仍未收尾 FROM pay_har_order
+--  WHERE trade_status = '0' AND description LIKE '上游返回 %' AND upstream_order_no IS NULL;
+--
+-- SELECT order_no, trade_status, next_query_time
+--   FROM pay_har_order
+--  WHERE description LIKE '上游返回 %' AND upstream_order_no IS NULL
+--  ORDER BY updated_time DESC;
+
+-- ── 备选:真要删行的话 ───────────────────────────────────────────────
+-- 默认**不删**:本地先落库再发上游是刻意的(超时那条路全靠这一行不丢单,见 create 的注释),
+-- 而被拒只说明上游建不出这笔单 —— 调用方那次请求确实发生过,description 里留着上游原话,
+-- 换个 out_biz_no 重试时还查得到上一次是怎么没的。要删就自己确认清单后把上面那条 UPDATE 换成:
+-- DELETE FROM pay_har_order
+--  WHERE trade_status = '0' AND description LIKE '上游返回 %' AND upstream_order_no IS NULL;