支付支持分段和修仙费

This commit is contained in:
ahao 2025-03-17 03:56:49 +08:00
parent 6de0073336
commit 127a2734ef
5 changed files with 240 additions and 150 deletions

View File

@ -298,66 +298,39 @@ async def notify(request: Request):
success_time = resp.get('success_time')
payer = resp.get('payer')
amount = resp.get('amount').get('total')
print(float(amount/100))
# 获取数据库连接
connection = get_connection()
cursor = connection.cursor(dictionary=True)
# 查询订单
cursor.execute("""
SELECT order_id, payable_price
FROM orders
WHERE out_trade_no = %s
FOR UPDATE""", (out_trade_no,))
order = cursor.fetchone()
# 更新订单状态
cursor.execute("""
UPDATE orders SET
order_status = 'completed',
payment_method = 'wechat',
paid_price = %s,
wx_transaction_id = %s,
settlement_time = NOW()
WHERE order_id = %s""",
(float(amount/100),transaction_id, order['order_id']))
connection.commit()
if cursor:
cursor.close()
if connection:
connection.close()
return JSONResponse(content={'code': 'SUCCESS', 'message': '成功'}, status_code=200)
else:
return JSONResponse(content={'code': 'FAILED', 'message': '失败'}, status_code=500)
# async def wxpay_notify(request: Request):
# connection = None
# cursor = None
# try:
# # 获取回调数据
# headers = dict(request.headers)
# data = await request.body()
#
# # 解密回调数据
# result = wxpay.callback(headers, data)
# if result and result.get('event_type') == 'TRANSACTION.SUCCESS':
# print("收到支付成功信息")
# print(result)
# out_trade_no = result.get('out_trade_no')
# print(out_trade_no)
# transaction_id = result.get('transaction_id')
# print(transaction_id)
#
#
# # 获取数据库连接
# # connection = get_connection()
# # cursor = connection.cursor(dictionary=True)
# #
# # # 查询订单并锁定
# # cursor.execute("""
# # SELECT order_id, payable_price
# # FROM orders
# # WHERE out_trade_no = %s
# # FOR UPDATE""", (out_trade_no,))
# # order = cursor.fetchone()
# #
# # cursor.execute("""
# # UPDATE orders SET
# # order_status = 'completed',
# # payment_method = 'wechat',
# # wx_transaction_id = %s,
# # settlement_time = NOW()
# # WHERE order_id = %s""",
# # (transaction_id, order['order_id']))
# #
# #
# # connection.commit()
#
# # 返回微信要求的成功响应
# return JSONResponse(content={"code": "SUCCESS", "message": "OK"})
#
# except Exception as e:
# logging.error(f"回调处理异常: {str(e)}", exc_info=True)
# # if connection:
# # connection.rollback()
# return JSONResponse(content={"code": "FAIL", "message": "系统错误"}, status_code=500)
# # finally:
# # if cursor:
# # cursor.close()
# # if connection:
# # connection.close()
return JSONResponse(content={'code': 'FAILED', 'message': '失败'}, status_code=500)

View File

@ -1,16 +1,15 @@
from fastapi import HTTPException
from datetime import datetime
from decimal import Decimal, ROUND_HALF_UP
from decimal import Decimal
from ..db import get_connection
from ..utils.jwt_handler import verify_token
import configparser
from datetime import datetime
import pytz
from ..utils import calculate_price
# 读取配置文件
config = configparser.ConfigParser()
config.read('backend/config.conf')
unit_price = Decimal(config.get('price', 'unit_price'))
points_rate = Decimal(config.get('price', 'points_rate'))
get_points_rate = Decimal(config.get('price', 'get_points_rate'))
@ -79,53 +78,51 @@ def query_orders_by_status_and_range(token: str, start: int, end: int, order_sta
connection.close()
def complete_order(token: str, order_id: int, end_datetime: datetime):
"""结束订单并计算原始价格"""
"""结束订单并计算最终价格"""
verify_admin_permission(token)
connection = get_connection()
try:
with connection.cursor(dictionary=True) as cursor:
# 查询订单基础信息(确保订单存在并锁定)
cursor.execute("""
SELECT o.start_datetime, t.price AS table_price,
COALESCE(g.price, 1) AS game_price, o.num_players -- 处理空值情况
FROM orders o
JOIN game_tables t ON o.game_table_id = t.table_id
LEFT JOIN games g ON o.game_id = g.game_id
WHERE o.order_id = %s
FOR UPDATE
""", (order_id,))
SELECT start_datetime
FROM orders
WHERE order_id = %s
FOR UPDATE
""", (order_id,))
order = cursor.fetchone()
# 将数据库中的时间和输入的时间转换为 naive移除时区信息
if not order:
raise HTTPException(status_code=404, detail="订单不存在")
# 处理时间
db_start = order['start_datetime'].replace(tzinfo=None)
input_end = end_datetime.replace(tzinfo=None)
# 计算游戏时长(分钟)duration_minutes 是 float 类型
# 计算游戏时长(分钟)
duration_minutes = (input_end - db_start).total_seconds() / 60
# 将时长转换为 Decimal 类型,避免与 Decimal 相乘时报错
duration_minutes_dec = Decimal(str(duration_minutes))
# 计算原始价格(假设 table_price 单位为每分钟价格)
base_price = (
order['table_price'] *
order['game_price'] *
order['num_players'] *
duration_minutes_dec *
unit_price
)
print(f"Base Price: {base_price}")
# 更新订单信息
# 更新订单状态并设置结束时间
cursor.execute("""
UPDATE orders
SET end_datetime = %s,
payable_price = %s,
game_process_time = %s,
game_process_time = %s,
order_status = 'pending'
WHERE order_id = %s
""", (end_datetime, base_price, duration_minutes_dec, order_id))
""", (end_datetime, duration_minutes_dec, order_id))
# 提交事务,确保订单已结束
connection.commit()
return {"message": "订单已结束待结算", "base_price": base_price}
# 调用价格计算逻辑(会自动写入数据库)
success = calculate_price.calculate_order_price(order_id)
if not success:
raise HTTPException(status_code=500, detail="订单价格计算失败")
return {"message": "订单已结束待结算"}
except Exception as e:
connection.rollback()
@ -133,6 +130,7 @@ def complete_order(token: str, order_id: int, end_datetime: datetime):
finally:
connection.close()
def settle_order(token: str, order_id: int, used_points: int = 0, coupon_id: int = None):
"""结算最终订单"""
verify_admin_permission(token)

View File

@ -4,12 +4,14 @@ from decimal import Decimal
from ..db import get_connection
from ..utils.jwt_handler import verify_token
import configparser
from ..utils import calculate_price
config = configparser.ConfigParser()
config.read('backend/config.conf')
unit_price = Decimal(config.get('price', 'unit_price'))
points_rate = Decimal(config.get('price', 'points_rate'))
get_points_rate = Decimal(config.get('price', 'get_points_rate'))
strategy_id = int(config.get('now_price', 'strategy_id'))
def get_user_active_order(token: str):
try:
@ -86,14 +88,14 @@ def create_user_order(token: str, table_id: int, num_players: int):
raise HTTPException(status_code=404, detail="用户不存在")
# 检查桌子占用
cursor.execute("""
SELECT EXISTS(
SELECT 1 FROM orders
WHERE game_table_id = %s
AND order_status = 'in_progress'
)""", (table_id,))
if cursor.fetchone()[0]:
raise HTTPException(status_code=400, detail="桌子已被占用")
# cursor.execute("""
# SELECT EXISTS(
# SELECT 1 FROM orders
# WHERE game_table_id = %s
# AND order_status = 'in_progress'
# )""", (table_id,))
# if cursor.fetchone()[0]:
# raise HTTPException(status_code=400, detail="桌子已被占用")
# 创建订单
order_date = datetime.now().date()
@ -103,9 +105,9 @@ def create_user_order(token: str, table_id: int, num_players: int):
INSERT INTO orders (
order_id, user_id, game_table_id,
order_date, start_datetime, num_players,
order_status
) VALUES (%s, %s, %s, %s, %s, %s, %s)
""", (order_id, user[0], table_id, order_date, start_datetime, num_players, 'in_progress'))
order_status, pricing_strategy_id
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (order_id, user[0], table_id, order_date, start_datetime, num_players, 'in_progress', strategy_id))
connection.commit()
return {"order_id": order_id, "message": "订单创建成功"}
@ -118,8 +120,9 @@ def create_user_order(token: str, table_id: int, num_players: int):
def complete_user_order(token: str, order_id: int):
"""完成用户订单"""
"""完成用户订单(计算最终价格)"""
try:
# 验证用户身份
payload = verify_token(token)
phone_number = payload["sub"]
except ValueError as e:
@ -127,66 +130,54 @@ def complete_user_order(token: str, order_id: int):
connection = get_connection()
try:
cursor = connection.cursor(dictionary=True)
# 修改后的SQL查询
cursor.execute("""
SELECT
o.user_id,
t.price AS table_price,
COALESCE(g.price, 1.0) AS game_price,
o.num_players,
o.start_datetime
FROM orders o
JOIN game_tables t ON o.game_table_id = t.table_id
LEFT JOIN games g ON o.game_id = g.game_id
WHERE o.order_id = %s
FOR UPDATE
""", (order_id,))
order = cursor.fetchone()
with connection.cursor(dictionary=True) as cursor:
# 获取订单信息
cursor.execute("""
SELECT o.user_id, o.start_datetime
FROM orders o
WHERE o.order_id = %s
FOR UPDATE
""", (order_id,))
order = cursor.fetchone()
if not order:
raise HTTPException(status_code=404, detail="订单不存在")
if not order:
raise HTTPException(status_code=404, detail="订单不存在")
# 验证用户权限
cursor.execute("SELECT user_id FROM users WHERE phone_number = %s", (phone_number,))
user = cursor.fetchone()
if order['user_id'] != user['user_id']:
raise HTTPException(status_code=403, detail="无权操作此订单")
# 验证用户权限
cursor.execute("SELECT user_id FROM users WHERE phone_number = %s", (phone_number,))
user = cursor.fetchone()
if not user or order['user_id'] != user['user_id']:
raise HTTPException(status_code=403, detail="无权操作此订单")
# 计算时长(优化精度处理)
end_datetime = datetime.now()
duration = end_datetime - order['start_datetime']
duration_minutes = duration.total_seconds() / 60
duration_minutes_dec = Decimal(duration_minutes).quantize(Decimal('0.0000'))
# 计算订单时长
end_datetime = datetime.now()
duration_minutes = (end_datetime - order['start_datetime']).total_seconds() / 60
duration_minutes_dec = Decimal(str(duration_minutes))
# 直接使用Decimal类型计算
unit_price = Decimal('0.1667') # 示例单位价格(需按实际调整)
base_price = (
order['table_price'] *
order['game_price'] *
order['num_players'] *
duration_minutes_dec *
unit_price
)
total_price = round(base_price, 2)
# 使用Decimal类型更新数据库
cursor.execute("""
UPDATE orders SET
end_datetime = %s,
payable_price = %s,
game_process_time = %s,
order_status = 'pending'
WHERE order_id = %s
""", (end_datetime, total_price, duration_minutes_dec, order_id))
# 先更新订单状态
cursor.execute("""
UPDATE orders
SET end_datetime = %s,
game_process_time = %s,
order_status = 'pending'
WHERE order_id = %s
""", (end_datetime, duration_minutes_dec, order_id))
# 提交事务,确保订单结束
connection.commit()
return {"message": "订单已结束待结算", "base_price": float(base_price)}
# 调用价格计算逻辑(会自动写入数据库)
success = calculate_price.calculate_order_price(order_id)
if not success:
raise HTTPException(status_code=500, detail="订单价格计算失败")
return {"message": "订单已结束待结算"}
except Exception as e:
connection.rollback()
raise HTTPException(status_code=500, detail=str(e))
finally:
cursor.close()
connection.close()

View File

@ -38,7 +38,7 @@ def check_table_occupancy_service(table_id: int):
) AS is_occupied
""", (table_id,))
result = cursor.fetchone()
return {"is_occupied": bool(result['is_occupied'])}
return {"is_occupied": "false"} # 不检测桌子状态
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
finally:

View File

@ -0,0 +1,128 @@
from datetime import datetime, timedelta
from decimal import Decimal
import configparser
from fastapi import HTTPException
from backend.app.db import get_connection
def get_pricing_strategy(strategy_id, cursor):
"""获取指定 ID 的价格策略"""
cursor.execute("""
SELECT segment1_threshold, segment1_price,
segment2_threshold, segment2_price,
segment3_price, segment3_threshold
FROM pricing_strategies
WHERE strategy_id = %s
""", (strategy_id,))
return cursor.fetchone()
def get_table_pricing_strategy(strategy_id, cursor):
"""获取指定 ID 的桌费策略"""
cursor.execute("""
SELECT segment1_threshold, segment1_price,
segment2_threshold, segment2_price,
segment3_price, segment3_threshold
FROM table_pricing_strategies
WHERE strategy_id = %s
""", (strategy_id,))
return cursor.fetchone()
def calculate_segmented_price(duration, strategy):
"""
计算按照分段收费策略的价格
- duration: 总时长分钟
- strategy: 包含分段时间和价格的策略字典
"""
price = Decimal(0)
s1, p1, s2, p2, p3, s3 = strategy.values()
if duration <= s1:
price = duration * p1
elif duration <= s2:
price = (s1 * p1) + ((duration - s1) * p2)
else:
# 超过 s2 但小于 s3如果 s3 设定)
if s3 is not None and duration > s3:
duration = s3 # 限制最大计费时间
price = (s1 * p1) + ((s2 - s1) * p2) + ((duration - s2) * p3)
return price
def calculate_overtime_fee(start_datetime, end_datetime):
"""计算超时费用(深夜时段收费)"""
config = configparser.ConfigParser()
config.read('backend/config.conf')
stay_up_late = config['stay_up_late']
start_time = datetime.strptime(stay_up_late['start_time'], '%H:%M:%S').time()
end_time = datetime.strptime(stay_up_late['end_time'], '%H:%M:%S').time()
price_per_minute = Decimal(stay_up_late['price_minutes'])
overtime_fee = Decimal(0)
current_time = start_datetime
while current_time < end_datetime:
if start_time <= current_time.time() or current_time.time() < end_time:
overtime_fee += price_per_minute
current_time += timedelta(minutes=1)
return overtime_fee
def calculate_order_price(order_id):
"""计算订单价格并写入数据库"""
connection = get_connection()
try:
with connection.cursor(dictionary=True) as cursor:
# 获取订单信息
cursor.execute("""
SELECT o.start_datetime, o.end_datetime, o.num_players,
o.pricing_strategy_id, t.table_pricing_strategy_id,
o.game_table_id
FROM orders o
JOIN game_tables t ON o.game_table_id = t.table_id
WHERE o.order_id = %s
FOR UPDATE
""", (order_id,))
order = cursor.fetchone()
if not order or not order['end_datetime']:
raise HTTPException(status_code=404, detail="订单不存在或未完成")
# 计算游戏时长(分钟)
start_time = order['start_datetime'].replace(tzinfo=None)
end_time = order['end_datetime'].replace(tzinfo=None)
duration = (end_time - start_time).total_seconds() / 60
duration_dec = Decimal(str(duration))
# 获取价格策略
pricing_strategy = get_pricing_strategy(order['pricing_strategy_id'], cursor)
table_strategy = get_table_pricing_strategy(order['table_pricing_strategy_id'], cursor)
if not pricing_strategy or not table_strategy:
raise HTTPException(status_code=400, detail="价格策略未找到")
# 计算价格
unit_price = calculate_segmented_price(duration_dec, pricing_strategy)
table_price = calculate_segmented_price(duration_dec, table_strategy)
# 计算总价格
base_price = (unit_price * order['num_players']) + table_price
overtime_fee = calculate_overtime_fee(start_time, end_time)
total_price = base_price + overtime_fee
# 更新订单价格
cursor.execute("""
UPDATE orders
SET payable_price = %s,
game_process_time = %s,
overtime_fee = %s
WHERE order_id = %s
""", (total_price, duration_dec, overtime_fee, order_id))
connection.commit()
return True
except Exception as e:
connection.rollback()
print(f"Error calculating order price: {e}")
return False
finally:
connection.close()