| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- # -*- coding: utf-8 -*-
- """第三轮:① 平安(50)渠道的退款解锁重试;② 关单腿铺开;③ 若干收尾读数。
- 第二轮遗留:
- * supply-ch50 建单成功(200001070J1226092816253210006)但**查询超时** —— 量具失效,
- 不是负读数,必须重跑。
- * /v3/cancelorder 对真实被扫失败单回「暂不支持撤销!」(sign 有效)—— 待确认是不是
- 只对这一笔/这一个渠道成立。
- """
- import io
- import json
- import os
- import re
- import time
- import uuid
- import urllib.error
- import urllib.request
- from hashlib import md5
- YML = "java/src/main/resources/application.yml"
- VAR = re.compile(r"^\$\{([A-Za-z0-9_]+):(.*)\}$")
- OUTDIR = "logs/_lsy-lc3"
- OUT = "logs/_lsy-round3.txt"
- buf = []
- rows = []
- def say(s=""):
- buf.append(s)
- print(s.encode("ascii", "replace").decode())
- def read_block(path, top):
- lines = io.open(path, encoding="utf-8", errors="replace").read().split("\n")
- start = None
- for i, ln in enumerate(lines):
- if re.match(r"^%s:\s*$" % re.escape(top), ln):
- start = i
- break
- if start is None:
- raise SystemExit("找不到顶层段 %s:" % top)
- out = {}
- for j in range(start + 1, len(lines)):
- ln = lines[j]
- if ln.strip() == "" or ln.lstrip().startswith("#"):
- continue
- if not ln.startswith((" ", "\t")):
- break
- m = re.match(r"^\s+([A-Za-z0-9_.\-]+):\s*(.*)$", ln)
- if m:
- out[m.group(1)] = re.split(r"\s+#", m.group(2), maxsplit=1)[0].strip().strip("'\"")
- return out
- def resolve(raw):
- m = VAR.match(raw)
- if not m:
- return raw
- return os.environ.get(m.group(1)) or m.group(2)
- cfg = read_block(YML, "leshouying")
- BASE = resolve(cfg["base-url"]).rstrip("/")
- INST = resolve(cfg["inst-no"])
- KEY = resolve(cfg["key"])
- MCH = "Y52211090004"
- if not os.path.isdir(OUTDIR):
- os.makedirs(OUTDIR)
- def call(path, params, tag, envelope="v3", timeout=60):
- p = dict(params)
- p.setdefault("inst_no", INST)
- p.setdefault("mch_no", MCH)
- A = "&".join("%s=%s" % (k, p[k]) for k in sorted(p) if p[k] is not None)
- p["sign"] = md5((A + "&key=" + KEY).encode("utf-8")).hexdigest().lower()
- body = json.dumps(p, ensure_ascii=False).encode("utf-8")
- req = urllib.request.Request(BASE + path, data=body,
- headers={"Content-Type": "application/json;charset=utf-8"},
- method="POST")
- try:
- with urllib.request.urlopen(req, timeout=timeout) as r:
- code, raw = r.status, r.read()
- except urllib.error.HTTPError as e:
- code, raw = e.code, e.read()
- except Exception as e: # noqa: BLE001
- say(" !! 未完成 %s: %r ← 量具失效,不是上游回答" % (tag, e))
- rows.append((tag, path, "TRANSPORT_ERR", "量具失效"))
- return None
- io.open("%s/%s.bin" % (OUTDIR, tag), "wb").write(raw)
- try:
- resp = json.loads(raw.decode("utf-8"))
- except Exception: # noqa: BLE001
- rows.append((tag, path, "NON_JSON", "%d B" % len(raw)))
- return None
- usign = resp.get("sign")
- vs = "sign=null"
- if usign:
- nn = {k: v for k, v in resp.items() if k != "sign" and v is not None}
- A2 = "&".join("%s=%s" % (k, nn[k]) for k in sorted(nn))
- vs = ("验签OK(%d)" % len(nn)
- if md5((A2 + "&key=" + KEY).encode("utf-8")).hexdigest().lower() == usign
- else "验签FAIL(%d)" % len(nn))
- elif "sign" not in resp:
- vs = "无sign键"
- rc, ec = resp.get("return_code"), resp.get("result_code")
- if envelope == "legacy":
- rc, ec = resp.get("code"), resp.get("data")
- say(" POST %-22s -> %4d B rc=%s ec=%s %s" % (path, len(raw), rc, ec, vs))
- say(" msg = %s" % (resp.get("return_msg") or resp.get("message")))
- rows.append((tag, path, "%s/%s" % (rc, ec),
- "%s; %s" % (str(resp.get("return_msg") or resp.get("message"))[:44], vs)))
- return resp
- def stamp():
- return time.strftime("%Y%m%d%H%M%S")
- def uid(p):
- return "%s%s%s" % (p, time.strftime("%H%M%S"), uuid.uuid4().hex[:6])
- # 从第二轮原始字节里读句柄,不手抄
- ch50 = json.loads(io.open("logs/_lsy-lc2/supply-ch50.bin", "rb").read().decode("utf-8"))
- CH50_NO = ch50["data"]
- ch37_no = "200001070J1226092816241610001"
- BARCODE = "200001070X3126092816241710003"
- H5_CLOSED = "200001070X4326092816241610002"
- say("句柄(均读自原始应答字节)")
- say(" ch50(平安) 补录单 = %s" % CH50_NO)
- say(" ch37(建行) 补录单 = %s" % ch37_no)
- say(" 真实被扫失败单 = %s" % BARCODE)
- say(" 已关单的 H5 = %s" % H5_CLOSED)
- say()
- # ─────────────────────────────── 1) 平安渠道:查询 + 退款
- say("=" * 78)
- say("【1】平安(50)渠道的补录单 —— 查询(第二轮超时,重跑)")
- say("=" * 78)
- q = call("/v3/queryorder", {"query_trace_no": uuid.uuid4().hex, "trade_no": CH50_NO},
- "q-ch50-bytradeno")
- say()
- say("【1b】对它退款(先 trade_no 寻址)")
- call("/v3/refundorder", {"refund_trace_no": uid("p5"), "refund_fee": "1",
- "pay_time": stamp(), "trade_no": CH50_NO}, "refund-ch50-tradeno")
- say()
- # ─────────────────────────────── 2) 关单腿铺开
- say("=" * 78)
- say("【2】关单:/v3/closeorder 全路径(我方**未实现**这个端点)")
- say("=" * 78)
- t, pt = uid("h5c"), stamp()
- h5 = call("/v3/h5pay", {"pay_trace_no": t, "pay_time": pt, "total_amount": "1",
- "pay_type": "300", "order_body": "三轮-关单用H5"}, "h5-for-close")
- NEW_H5 = h5.get("trade_no") if h5 else None
- say(" 新 H5 trade_no = %s" % NEW_H5)
- say()
- if NEW_H5:
- call("/v3/closeorder", {"close_trace_no": uid("k1"), "close_time": stamp(),
- "trade_no": NEW_H5}, "co-h5-1st")
- call("/v3/closeorder", {"close_trace_no": uid("k2"), "close_time": stamp(),
- "trade_no": NEW_H5}, "co-h5-2nd-幂等")
- call("/v3/queryorder", {"query_trace_no": uuid.uuid4().hex, "trade_no": NEW_H5},
- "q-h5-afterclose")
- say()
- # 关单:两个键都不发
- call("/v3/closeorder", {"close_trace_no": uid("k3"), "close_time": stamp()}, "co-nokey")
- # 关单:被扫单(文档说 closeorder 只用于二维码/JSAPI)
- call("/v3/closeorder", {"close_trace_no": uid("k4"), "close_time": stamp(),
- "trade_no": BARCODE}, "co-barcode")
- # 关单:已关过的老 H5
- call("/v3/closeorder", {"close_trace_no": uid("k5"), "close_time": stamp(),
- "trade_no": H5_CLOSED}, "co-oldh5-again")
- # 关单:不存在的单
- call("/v3/closeorder", {"close_trace_no": uid("k6"), "close_time": stamp(),
- "trade_no": "200001070X9999999999999999999"}, "co-nonexistent")
- say()
- # ─────────────────────────────── 3) 已关单的 H5 还能查到吗
- say("=" * 78)
- say("【3】已关单的 H5 在 queryorder 里是什么样")
- say("=" * 78)
- call("/v3/queryorder", {"query_trace_no": uuid.uuid4().hex, "trade_no": H5_CLOSED}, "q-oldh5-closed")
- say()
- # ─────────────────────────────── 4) 退款:真实被扫失败单 / 不存在单
- say("=" * 78)
- say("【4】退款错误路径补齐")
- say("=" * 78)
- call("/v3/refundorder", {"refund_trace_no": uid("rb"), "refund_fee": "1",
- "pay_time": stamp(), "trade_no": BARCODE}, "refund-barcode-unpaid")
- call("/v3/refundorder", {"refund_trace_no": uid("rz"), "refund_fee": "1",
- "pay_time": stamp(), "trade_no": "200001070X9999999999999999999"},
- "refund-bogus-tradeno")
- say()
- # ─────────────────────────────── 5) 撤销:再试一笔,确认「暂不支持」是不是普遍
- say("=" * 78)
- say("【5】撤销:换一笔再试(判「暂不支持撤销」是这一笔的还是普遍的)")
- say("=" * 78)
- t2, p2 = uid("bc2"), stamp()
- bc = call("/v3/pay", {"pay_trace_no": t2, "pay_time": p2, "total_amount": "1",
- "auth_code": "284812345678901234", "pay_type": "300",
- "order_body": "三轮-撤销用被扫"}, "barcode-for-cancel")
- if bc and bc.get("trade_no"):
- say(" 新被扫单 trade_no = %s" % bc["trade_no"])
- call("/v3/cancelorder", {"cancel_trace_no": uid("c2"), "cancel_time": stamp(),
- "trade_no": bc["trade_no"]}, "cancel-barcode-2nd")
- call("/v3/cancelorder", {"cancel_trace_no": uid("c3"), "cancel_time": stamp(),
- "pay_trace_no": t2, "pay_time": p2}, "cancel-barcode-bytrace")
- say()
- say("=" * 78)
- say("汇总(%d 条)" % len(rows))
- say("=" * 78)
- say("%-28s %-22s %-20s %s" % ("tag", "path", "rc/ec", "note"))
- for t_, p_, v, d in rows:
- say("%-28s %-22s %-20s %s" % (t_, p_, v, d))
- io.open(OUT, "w", encoding="utf-8").write("\n".join(buf) + "\n")
- print("report -> %s" % OUT)
|