Files
live-hub-py/services/yyb-worker/runtime/pyvm/protocol.py
T

94 lines
4.2 KiB
Python

"""YYB 支付协议常量、材料校验与脱敏指纹。
这里仅收敛已由成功请求验证的固定值,不负责推断或修改协议字段。
"""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from typing import Any
PAY_APPID = "1450243039"
PAY_GOODS_URL = "https://pay.qq.com/midas/minipay_v2/views/cpay/goods.shtml"
PAY_SAVE_URL = f"https://api.unipay.qq.com/v1/r/{PAY_APPID}/web_save"
PAY_PAGE_INFO_URL = f"https://api.unipay.qq.com/v1/r/{PAY_APPID}/web_page_info"
PAY_FP_URL = "https://api.unipay.qq.com/cgi-bin/fp-behv.fcg"
MALL_API_URL = "https://storeapi.pay.qq.com/api/CommonCallMpgo"
PAY_WEB_VERSION = "web_1.0.6"
PAY_WEBVERSION = "minipayv2"
DEFAULT_ORDER_PF = "mds_myappjp-__mds_myappjp_PC_aW9zd2hpdGVsaX0-android"
OLD_ENCRYPT_OFFERIDS = ("1450007826", "1110165571")
# 这两个 UA 分别来自已成功的 goods 和商城请求,不能因为版本不同而强行统一。
GOODS_USER_AGENT = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
)
MALL_USER_AGENT = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36"
)
def _sha256_prefix_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()[:12]
def _json_fingerprint(value: Any) -> str:
raw = json.dumps(value, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
return _sha256_prefix_bytes(raw)
def file_fingerprint(path: Path) -> str:
"""返回协议文件的短哈希,用于日志比对,不暴露内容。"""
return _sha256_prefix_bytes(path.read_bytes())
def validate_goods_materials(args_template: list, xmidas: list[int] | None = None) -> None:
"""校验 goods webSave VM 所需静态表结构,发现升级时尽早失败。"""
if not isinstance(args_template, list) or len(args_template) != 18:
raise ValueError("goods args-template 必须是 18 槽数组")
for index in (0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16):
if not isinstance(args_template[index], list) or not args_template[index]:
raise ValueError(f"goods args-template 槽 {index} 缺失")
expected_lengths = {0: 16, 1: 256, 2: 256, 3: 256, 4: 256, 5: 256,
6: 16, 10: 528, 11: 1024, 12: 1024, 13: 256,
14: 256, 15: 256, 16: 256}
for index, expected in expected_lengths.items():
value = args_template[index][0]
if not isinstance(value, list) or len(value) != expected:
actual = len(value) if isinstance(value, list) else "非数组"
raise ValueError(f"goods args-template 槽 {index} 长度异常: {actual} != {expected}")
if xmidas is not None and len(xmidas) != 59640:
raise ValueError(f"goods xMidasOps 长度异常: {len(xmidas)} != 59640")
def validate_mall_materials(transform_fixed: dict[str, Any]) -> None:
"""校验 mall 固定槽模板;动态槽仍由当前会话的 GetPayToken 填充。"""
if not isinstance(transform_fixed, dict):
raise ValueError("mall transform-fixed 必须是对象")
required = {str(index) for index in (1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17)}
missing = sorted(required - set(transform_fixed))
if missing:
raise ValueError(f"mall transform-fixed 缺少槽: {', '.join(missing)}")
for index in required:
if not isinstance(transform_fixed[index], list):
raise ValueError(f"mall transform-fixed 槽 {index} 不是数组")
def goods_material_diagnostics(root: Path, args_template: list, xmidas: list[int]) -> dict[str, Any]:
"""构造脱敏协议指纹,便于区分页面升级和服务端业务拒绝。"""
validate_goods_materials(args_template, xmidas)
replay = root / "replay"
return {
"goods_xmidas_ops_length": len(xmidas),
"args_template_slots": len(args_template),
"args_template_sha256_prefix": _json_fingerprint(args_template),
"bytecode_sha256_prefix": file_fingerprint(replay / "bytecode.json"),
"constants_sha256_prefix": file_fingerprint(replay / "constants.json"),
"web_version": PAY_WEB_VERSION,
}