之前存在106.52上的仓库被重新初始化了,同时增加了测试账号的兼容,测试账号hxbtest001,...hxbtest005

This commit is contained in:
qcloud
2025-09-01 20:09:26 +08:00
parent e6644a5262
commit 8d90798647
16 changed files with 25420 additions and 157 deletions

View File

@@ -19,7 +19,7 @@ from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.backends import default_backend
import httpx
import httpx,threading,asyncio
class CustomJSONResponse(JSONResponse):
"""
@@ -90,6 +90,7 @@ logger = logging.getLogger("payment")
# 微信支付配置
WX_APPID = "wx446813bfb3a6985a"
WX_APPSECRET= "a7455fca777ad59ce96cc154d62f795f"
WX_MCH_ID = "1721301006"
WX_PAY_KEY = "7xK9pR2qY5vN3zW8bL1cD4fG6hJ7mQ0t"
WX_PAY_KEY_V3 = "7xK9pR2qY5vN3zW8bL1cD4fG6hJ7mQ0t"
@@ -154,6 +155,66 @@ def get_wx_platform_public_key(serial_no: str):
logger.error(f"加载平台证书失败: {str(e)}")
return None
class WechatTokenManager:
_instance = None
_lock = threading.Lock()
def __new__(cls, appid: str, secret: str):
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.appid = appid
cls._instance.secret = secret
cls._instance.access_token = None
cls._instance.expires_at = 0 # 过期时间戳
cls._instance.refresh_margin = 300 # 提前5分钟刷新
return cls._instance
async def _request_token(self) -> dict:
url = "https://api.weixin.qq.com/cgi-bin/token"
params = {
"grant_type": "client_credential",
"appid": self.appid,
"secret": self.secret
}
async with httpx.AsyncClient() as client:
try:
resp = await client.get(url, params=params, timeout=10)
resp.raise_for_status()
data = resp.json()
if "access_token" not in data:
error_msg = f"Wechat API error: {data.get('errcode', '')} - {data.get('errmsg', 'Unknown error')}"
logging.error(error_msg)
raise HTTPException(status_code=500, detail=error_msg)
return data
except (httpx.RequestError, httpx.HTTPStatusError) as e:
logging.error(f"Token request failed: {str(e)}")
raise HTTPException(status_code=503, detail="Wechat service unavailable")
async def get_token(self) -> str:
current_time = time.time()
# 检查是否需要刷新 (包含安全边际)
if self.access_token and current_time < (self.expires_at - self.refresh_margin):
return self.access_token
# 双检锁避免重复刷新
with self._lock:
if self.access_token and current_time < (self.expires_at - self.refresh_margin):
return self.access_token
# 请求新token
token_data = await self._request_token()
self.access_token = token_data["access_token"]
self.expires_at = current_time + token_data["expires_in"]
logging.info(f"Token refreshed, expires at: {time.ctime(self.expires_at)}")
return self.access_token
# 初始化单例
wechat_server_token_manager = WechatTokenManager(WX_APPID, WX_APPSECRET)
async def get_current_user(token: str = Depends(oauth2_scheme)):
"""
@@ -453,7 +514,7 @@ async def check_payment_status(
"msg": "success",
"data": {
"order_id": order_id,
"paid": order["status"] in ["paid", "activated"],
"paid": order["status"] in ["paid", "activated","delivered"],
"status": order["status"]
}
})
@@ -500,13 +561,15 @@ async def get_user_museum_subscriptions(
data = await request.json()
museum_id = data.get("museum_id")
user_id = current_user["user_id"] # 用户id
is_test_account = current_user.get('is_test_account',0)
is_free = False
museum_info = get_museum_by_id(museum_id=museum_id)
if museum_info and museum_info['free']:
is_free = True
is_free_period = is_museum_free_period(museum_id)
is_free_period,free_period_subscription = is_museum_free_period(museum_id)
is_subscription_valid = get_user_valid_subscription(user_id, museum_id)
can_access = False
if is_test_account:
is_subscription_valid = True
can_access = is_free or is_free_period or is_subscription_valid
result = {
'can_access': can_access,
@@ -514,6 +577,11 @@ async def get_user_museum_subscriptions(
'is_free_period': is_free_period,
'is_subscription_valid': is_subscription_valid
}
if free_period_subscription:
if free_period_subscription.get('validity_type') == 'free_interval':
if free_period_subscription.get('valid_time_range'):
result['valid_time_range'] = free_period_subscription.get('valid_time_range') # 增加免费时间段信息
return CustomJSONResponse({
"code": 0,
"msg": "success",
@@ -596,7 +664,7 @@ async def generate_wx_prepay_params_v2(order_id: str, total_fee: int, openid: st
logging.error(f"解析微信响应XML失败: {parse_err}")
raise Exception("微信支付返回数据格式错误")
logging.info(f"generate_wx_prepay_params--3 响应数据: {response_data}")
# logging.info(f"generate_wx_prepay_params--3 响应数据: {response_data}")
# 检查返回结果
if response_data.get("return_code") != "SUCCESS":
@@ -824,6 +892,8 @@ async def process_payment_success(order_id: str, transaction_id: str, total_fee:
if not order:
logger.error(f"订单不存在: {order_id}")
return False, "订单不存在"
# 判断商品类型
# 激活订阅
success = activate_user_subscription(
@@ -834,10 +904,61 @@ async def process_payment_success(order_id: str, transaction_id: str, total_fee:
if success:
# 更新订单状态为已激活
update_order(order_id, {"status": "activated"})
access_token = await wechat_server_token_manager.get_token()
if order.get("museum_subscription_id",None) and True: # 通过获取museum_subscription_id 来判断是否为订阅
# 虚拟商品(订阅)处理流程
asyncio.create_task(deliver_virtual_goods(order)) # 创建独立任务
return True, "OK"
async def deliver_virtual_goods(order: dict) -> bool:
"""调用微信API发货虚拟商品"""
try:
# 获取access_token
# 延时3秒这样避免发货调用API时获取支付订单失败
await asyncio.sleep(3) # 异步等待2秒
access_token = await wechat_server_token_manager.get_token()
# 获取组合查询后的订单信息
order_combined_info = get_order_by_id(order_id= order["order_id"], combined=True)
item_desc = f"{order_combined_info.get('museum_name','')}-{order_combined_info.get('template_name','')}"
# 构造发货数据
delivery_data = {
"order_key": {
"order_number_type": 1, # 使用商户订单号
"mchid":WX_MCH_ID , # 商户号
"out_trade_no":order_combined_info["order_id"]
#"transaction_id":order_combined_info["transaction_id"]
},
"logistics_type": 3, # 虚拟商品标识
"delivery_mode": 1, # 统一发货
"shipping_list": [{
"item_desc": item_desc
}],
"upload_time": datetime.now().astimezone().isoformat(), # 带时区的时间
"payer": {"openid": order_combined_info["openid"]}
}
# 调用微信API
async with httpx.AsyncClient() as client:
url = f"https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token={access_token}"
resp = await client.post(url, json=delivery_data, timeout=10)
resp.raise_for_status()
result = resp.json()
if result.get("errcode") != 0:
logger.error(f"发货API错误: {result.get('errmsg')}")
return False
logger.info(f"虚拟商品发货成功: {order_combined_info.get('order_id')}")
return True
except httpx.RequestError as e:
logger.error(f"发货请求失败: {str(e)}")
except Exception as e:
logger.exception(f"发货处理异常: {str(e)}")
return False
# 套餐激活函数
def activate_user_product(user_id: str, product_id: int, museum_id: int, order_id: str):
product = get_product_by_id(product_id)