Files
workphone-sdk/sdk/scripts/wp_hl_06_transfer_until_success.py
Manus AI 00a1e4dab4
Some checks failed
SDK CI / python-compile (push) Has been cancelled
feat: 更新工作手机无线控制与转账验收
2026-07-24 02:19:47 +08:00

176 lines
8.1 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""WP-HL-06 微信转账无界面续跑脚本。
只走 HTTP API → WebSocket Agent → Frida RPC → 回读证据。
密码仅从环境变量读取,响应和日志不写明文。
"""
from __future__ import annotations
import argparse
import datetime as dt
import json
import os
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
DEFAULT_REPORT = Path("开发文档/10、项目管理/02-测试报告/20260724_WP-HL-06_转账接口_xd2214_爱笑的阿猫")
def now() -> str:
return dt.datetime.now().strftime("%Y%m%d_%H%M%S")
def request_json(method: str, url: str, body: dict | None = None, timeout: int = 75) -> tuple[int, dict]:
data = None
headers = {}
if body is not None:
safe_body = dict(body)
if "payment_password" in safe_body:
safe_body["payment_password"] = "******"
data = json.dumps(body, ensure_ascii=False).encode("utf-8")
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read().decode("utf-8", "replace")
return resp.status, json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raw = e.read().decode("utf-8", "replace")
try:
return e.code, json.loads(raw) if raw else {"error": raw}
except Exception:
return e.code, {"error": raw}
except Exception as e:
return 0, {"success": False, "error_code": "request_failed", "error_message": str(e)}
def save(report: Path, name: str, payload: dict) -> Path:
report.mkdir(parents=True, exist_ok=True)
path = report / f"{name}_{now()}.json"
text = json.dumps(payload, ensure_ascii=False, indent=2)
# 二次兜底:不让六位支付口令形态落盘。
import re
text = re.sub(r'("payment_password"\s*:\s*")([^"]+)(")', r'\1******\3', text)
path.write_text(text, encoding="utf-8")
return path
def success_of(payload: dict) -> bool:
if payload.get("success") is True:
data = payload.get("data") if isinstance(payload.get("data"), dict) else payload
readback = data.get("readback") or data.get("db_readback") or data.get("ledger_readback") or {}
return bool(data.get("settlement_verified") or data.get("ledger_verified") or (isinstance(readback, dict) and readback.get("found")))
return False
def main() -> int:
ap = argparse.ArgumentParser(description="WP-HL-06 微信转账直到回读成功")
ap.add_argument("--base", default=os.getenv("WP_BASE", "http://127.0.0.1:8899"))
ap.add_argument("--device", default=os.getenv("WP_DEVICE", "xgfe65eimrrofyws"))
ap.add_argument("--to", default=os.getenv("WP_TRANSFER_TO", "wxid_xbhui4yjlw9a22"))
ap.add_argument("--amount", default=os.getenv("WP_TRANSFER_AMOUNT", "1.00"))
ap.add_argument("--message", default=os.getenv("WP_TRANSFER_MESSAGE", "WP-HL-06无界面转账测试"))
ap.add_argument("--report", default=os.getenv("WP_REPORT", str(DEFAULT_REPORT)))
ap.add_argument("--wait-seconds", type=int, default=int(os.getenv("WP_WAIT_SECONDS", "900")))
ap.add_argument("--poll", type=int, default=int(os.getenv("WP_POLL_SECONDS", "5")))
ap.add_argument("--execute", action="store_true", default=os.getenv("WP_PAYMENT_CONFIRM") == "1")
args = ap.parse_args()
report = Path(args.report)
password = os.getenv("WP_PAYMENT_PASSWORD", "")
if args.execute and not password:
payload = {"success": False, "status": "ready_waiting_password_env", "error_code": "payment_password_env_required", "message": "设置 WP_PAYMENT_PASSWORD 后续跑;脚本不保存明文。"}
print(json.dumps(payload, ensure_ascii=False))
save(report, "transfer_until_success_password_env_required", payload)
return 2
deadline = time.time() + args.wait_seconds
last_health = {}
while time.time() < deadline:
_, health = request_json("GET", f"{args.base}/health", timeout=10)
last_health = health
save(report, "transfer_until_success_health", health)
online = int(health.get("devices_online") or 0) > 0 and (not health.get("device_ids") or args.device in health.get("device_ids", []))
if online:
break
print(json.dumps({"stage": "wait_agent", "devices_online": health.get("devices_online"), "device_ids": health.get("device_ids", [])}, ensure_ascii=False), flush=True)
time.sleep(args.poll)
else:
payload = {"success": False, "status": "agent_offline_timeout", "health": last_health}
print(json.dumps(payload, ensure_ascii=False))
save(report, "transfer_until_success_agent_offline_timeout", payload)
return 3
for name, url in [
("hook_status", f"{args.base}/api/v3/wechat/hook/status?device_id={args.device}"),
("hook_probe", f"{args.base}/api/v3/hook/probe/{args.device}"),
("hook_actions", f"{args.base}/api/v3/hook/actions"),
]:
_, payload = request_json("GET", url, timeout=30)
save(report, f"transfer_until_success_{name}", payload)
print(json.dumps({"stage": name, "success": payload.get("success"), "code": payload.get("code")}, ensure_ascii=False), flush=True)
if not args.execute:
payload = {"success": True, "status": "ready_for_execute", "execute_env": "WP_PAYMENT_CONFIRM=1 + WP_PAYMENT_PASSWORD", "target": args.to, "amount": args.amount}
print(json.dumps(payload, ensure_ascii=False))
save(report, "transfer_until_success_ready", payload)
return 0
body = {
"device_id": args.device,
"platform": "wechat",
"to_id": args.to,
"amount": args.amount,
"message": args.message,
"payment_password": password,
"confirm": True,
"dry_run": False,
}
_, place = request_json("POST", f"{args.base}/api/v3/payment/transfer", body, timeout=90)
save(report, "transfer_until_success_placeorder", place)
print(json.dumps({"stage": "placeorder", "success": place.get("success"), "code": place.get("code")}, ensure_ascii=False), flush=True)
if success_of(place):
save(report, "transfer_until_success_final", place)
print(json.dumps({"success": True, "status": "settlement_verified", "source": "placeorder"}, ensure_ascii=False))
return 0
data = place.get("data") if isinstance(place.get("data"), dict) else place
req_key = data.get("req_key") or data.get("reqkey")
if not req_key:
payload = {"success": False, "status": "placeorder_not_ready", "placeorder_code": place.get("code"), "error_code": data.get("error_code") or data.get("error")}
save(report, "transfer_until_success_placeorder_not_ready", payload)
print(json.dumps(payload, ensure_ascii=False))
return 4
confirm_body = {
"device_id": args.device,
"platform": "wechat",
"req_key": req_key,
"to_id": args.to,
"transfer_id": data.get("transfer_id", ""),
"transaction_id": data.get("transaction_id", ""),
"payment_password": password,
"confirm": True,
"dry_run": False,
}
_, confirm = request_json("POST", f"{args.base}/api/v3/payment/transfer/confirm", confirm_body, timeout=100)
save(report, "transfer_until_success_confirm", confirm)
print(json.dumps({"stage": "confirm", "success": confirm.get("success"), "code": confirm.get("code")}, ensure_ascii=False), flush=True)
if success_of(confirm):
save(report, "transfer_until_success_final", confirm)
print(json.dumps({"success": True, "status": "settlement_verified", "source": "confirm"}, ensure_ascii=False))
return 0
payload = {"success": False, "status": "waiting_readback_or_confirm_required", "confirm_code": confirm.get("code"), "error_code": (confirm.get("data") or {}).get("error_code") if isinstance(confirm.get("data"), dict) else confirm.get("error_code")}
save(report, "transfer_until_success_waiting_readback", payload)
print(json.dumps(payload, ensure_ascii=False))
return 5
if __name__ == "__main__":
raise SystemExit(main())