支付支持分段和修仙费
This commit is contained in:
parent
6de0073336
commit
127a2734ef
@ -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)
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
128
backend/app/utils/calculate_price.py
Normal file
128
backend/app/utils/calculate_price.py
Normal 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()
|
||||
Loading…
x
Reference in New Issue
Block a user