之前存在106.52上的仓库被重新初始化了,同时增加了测试账号的兼容,测试账号hxbtest001,...hxbtest005
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user