"""WeChat mini-program helpers (jscode2session).""" from __future__ import annotations import hashlib import os from typing import Any import httpx WECHAT_APPID = os.getenv("WECHAT_APPID", "").strip() WECHAT_SECRET = os.getenv("WECHAT_SECRET", "").strip() WECHAT_ALLOW_DEVTOOLS = os.getenv("WECHAT_ALLOW_DEVTOOLS", "true").lower() in { "1", "true", "yes", "on", } async def code2session(js_code: str) -> dict[str, Any]: code = (js_code or "").strip() if not code: raise RuntimeError("缺少微信登录 code") # Local / 开发者工具联调:无密钥或 mock code 时降级 mock = code in {"dev", "the code is a mock one"} or code.startswith("mock") if mock or (not WECHAT_APPID or not WECHAT_SECRET): if WECHAT_ALLOW_DEVTOOLS or mock: digest = hashlib.sha256(code.encode("utf-8")).hexdigest()[:16] return { "openid": f"devtools_{digest}", "session_key": f"devtools_session_{digest}", "unionid": None, } raise RuntimeError("未配置 WECHAT_APPID / WECHAT_SECRET") url = "https://api.weixin.qq.com/sns/jscode2session" params = { "appid": WECHAT_APPID, "secret": WECHAT_SECRET, "js_code": code, "grant_type": "authorization_code", } async with httpx.AsyncClient(timeout=15) as client: r = await client.get(url, params=params) data = r.json() err = data.get("errcode") if err: if WECHAT_ALLOW_DEVTOOLS and int(err) in {40029, 40163, 40252}: digest = hashlib.sha256(code.encode("utf-8")).hexdigest()[:16] return { "openid": f"devtools_{digest}", "session_key": f"devtools_session_{digest}", "unionid": None, } raise RuntimeError(f"code2session failed: {data}") if not data.get("openid"): raise RuntimeError(f"code2session missing openid: {data}") return data