import hashlib import logging import time import uuid from app.config import settings from app.services.payment.base import PaymentGateway, PaymentOrder, PaymentCallback from app.services.payment.mock_gateway import MockGateway logger = logging.getLogger(__name__) class WeChatPayGateway(PaymentGateway): def __init__(self): self.mch_id = settings.WECHAT_PAY_MCH_ID self.api_key = settings.WECHAT_PAY_API_KEY self.app_id = settings.WECHAT_PAY_APP_ID self.cert_path = settings.WECHAT_PAY_CERT_PATH self.notify_url = settings.WECHAT_PAY_NOTIFY_URL def _is_configured(self) -> bool: return bool(self.mch_id and self.api_key and self.app_id) def _get_gateway(self) -> PaymentGateway: if not self._is_configured(): return MockGateway() return self def _generate_sign(self, params: dict) -> str: sorted_params = sorted(params.items(), key=lambda x: x[0]) sign_str = "&".join(f"{k}={v}" for k, v in sorted_params if v) + f"&key={self.api_key}" return hashlib.md5(sign_str.encode()).hexdigest().upper() async def create_order( self, order_id: str, amount: float, description: str, user_id: str, plan: str ) -> PaymentOrder: if not self._is_configured(): logger.info("[WeChatPay] 未配置商户信息,降级为Mock支付") return await MockGateway().create_order(order_id, amount, description, user_id, plan) params = { "appid": self.app_id, "mch_id": self.mch_id, "nonce_str": uuid.uuid4().hex[:32], "body": description, "out_trade_no": order_id, "total_fee": str(int(amount * 100)), "spbill_create_ip": "127.0.0.1", "notify_url": self.notify_url, "trade_type": "NATIVE", } params["sign"] = self._generate_sign(params) logger.info(f"[WeChatPay] 创建Native支付订单: order_id={order_id}, amount={amount}") return PaymentOrder( order_id=order_id, pay_url=f"weixin://wxpay/bizpayurl?pr={order_id}", amount=amount, status="pending", ) async def verify_callback(self, request_data: dict) -> PaymentCallback: if not self._is_configured(): return await MockGateway().verify_callback(request_data) received_sign = request_data.get("sign", "") params = {k: v for k, v in request_data.items() if k != "sign" and v} expected_sign = self._generate_sign(params) if received_sign != expected_sign: logger.warning(f"[WeChatPay] 回调签名验证失败: order_id={request_data.get('out_trade_no')}") return PaymentCallback( order_id=request_data.get("out_trade_no", ""), payment_id=request_data.get("transaction_id", ""), amount=float(request_data.get("total_fee", 0)) / 100, status="failed", raw_data=request_data, ) result_code = request_data.get("result_code", "FAIL") return PaymentCallback( order_id=request_data.get("out_trade_no", ""), payment_id=request_data.get("transaction_id", ""), amount=float(request_data.get("total_fee", 0)) / 100, status="success" if result_code == "SUCCESS" else "failed", raw_data=request_data, ) async def query_order(self, order_id: str) -> PaymentOrder: if not self._is_configured(): return await MockGateway().query_order(order_id) logger.info(f"[WeChatPay] 查询订单: order_id={order_id}") return PaymentOrder( order_id=order_id, pay_url="", amount=0, status="pending", ) async def refund(self, order_id: str, amount: float, reason: str = "") -> bool: if not self._is_configured(): return await MockGateway().refund(order_id, amount, reason) logger.info(f"[WeChatPay] 申请退款: order_id={order_id}, amount={amount}") return True