geo/backend/app/services/payment/wechat_pay.py

108 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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