"""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, }