from typing import Optional, Dict, Any import httpx import logging from app.config import settings logger = logging.getLogger(__name__) class WeChatService: def __init__(self): self.app_id = settings.WECHAT_APP_ID self.app_secret = settings.WECHAT_APP_SECRET self.api_base = "https://api.weixin.qq.com" async def code2session(self, js_code: str) -> Optional[Dict[str, Any]]: if not self.app_id or not self.app_secret: logger.warning("WeChat not configured") return None async with httpx.AsyncClient() as client: resp = await client.get( f"{self.api_base}/sns/jscode2session", params={ "appid": self.app_id, "secret": self.app_secret, "js_code": js_code, "grant_type": "authorization_code", }, timeout=10, ) data = resp.json() if data.get("errcode", 0) != 0: logger.error(f"WeChat code2session failed: {data}") return None return { "openid": data.get("openid"), "session_key": data.get("session_key"), "unionid": data.get("unionid"), } async def get_phone_number(self, code: str) -> Optional[str]: if not self.app_id or not self.app_secret: return None access_token = await self._get_access_token() if not access_token: return None async with httpx.AsyncClient() as client: resp = await client.post( f"{self.api_base}/wxa/business/getuserphonenumber", params={"access_token": access_token}, json={"code": code}, timeout=10, ) data = resp.json() if data.get("errcode", 0) != 0: logger.error(f"WeChat getPhoneNumber failed: {data}") return None return data.get("phone_info", {}).get("phoneNumber") async def _get_access_token(self) -> Optional[str]: async with httpx.AsyncClient() as client: resp = await client.get( f"{self.api_base}/cgi-bin/token", params={ "grant_type": "client_credential", "appid": self.app_id, "secret": self.app_secret, }, timeout=10, ) data = resp.json() return data.get("access_token") wechat_service = WeChatService()