from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from sqlalchemy import func from typing import List, Optional from app.models.merchant_order import ( MerchantOrderDB, MerchantOrderCreate, MerchantOrderInfo, MerchantOrderStatus ) from app.models.merchant_product import MerchantProductDB from app.models.database import get_db from app.api.deps import get_current_user, get_admin_user, get_merchant_user from app.models.user import UserDB from app.core.response import success_response, error_response, ResponseModel from datetime import datetime, timezone from app.models.merchant import MerchantDB from app.models.point import PointRecordDB from app.core.account import AccountManager from app.core.wechat import WeChatClient from pydantic import BaseModel from app.core.config import settings from app.core.utils import CommonUtils from sqlalchemy.sql import text import qrcode import io from fastapi import UploadFile from app.core.qcloud import qcloud_manager from app.core.imageprocessor import process_image, ImageFormat from starlette.datastructures import Headers from app.models.merchant_order import MerchantOrderVerify from app.core.point_manager import PointManager, PointRecordType router = APIRouter() @router.post("", response_model=ResponseModel) async def create_merchant_order( order: MerchantOrderCreate, db: Session = Depends(get_db), current_user: UserDB = Depends(get_current_user) ): """创建商家订单""" # 检查商品是否存在 product = db.query(MerchantProductDB).filter( MerchantProductDB.id == order.merchant_product_id ).first() if not product: return error_response(code=404, message="商品不存在") # 检查限购 if product.purchase_limit and product.purchase_limit > 0: # 查询用户已购买数量 purchased_count = db.query(func.count(MerchantOrderDB.id)).filter( MerchantOrderDB.user_id == current_user.userid, MerchantOrderDB.merchant_product_id == product.id, MerchantOrderDB.status.in_([ MerchantOrderStatus.CREATED, MerchantOrderStatus.VERIFIED, MerchantOrderStatus.UNVERIFIED ]) ).scalar() if purchased_count >= product.purchase_limit: return error_response( code=400, message=f"该商品限购{product.purchase_limit}次,您已达到限购次数" ) order_id = CommonUtils.generate_order_id('M') verify_code = CommonUtils.generate_verify_code() # 创建订单 pay_amount = float(product.sale_price) db_order = MerchantOrderDB( order_id=order_id, user_id=current_user.userid, merchant_product_id=order.merchant_product_id, order_amount=pay_amount, pay_amount=pay_amount, gift_points=int(float(product.sale_price) * (float(product.gift_points_rate) / 100) * settings.POINT_RATIO), status=MerchantOrderStatus.CREATED, order_verify_code=verify_code ) try: db.add(db_order) db.commit() db.refresh(db_order) return success_response(data=MerchantOrderInfo.model_validate(db_order)) except Exception as e: db.rollback() return error_response(code=500, message=f"创建订单失败: {str(e)}") @router.get("/user", response_model=ResponseModel) async def get_user_orders( skip: int = 0, limit: int = 20, db: Session = Depends(get_db), current_user: UserDB = Depends(get_current_user) ): """获取用户的订单列表""" orders = db.query( MerchantOrderDB, MerchantProductDB.name.label('product_name'), MerchantProductDB.image_url.label('product_image'), MerchantProductDB.tags.label('product_tags'), MerchantDB.name.label('merchant_name'), MerchantDB.latitude.label('merchant_latitude'), MerchantDB.longitude.label('merchant_longitude'), MerchantDB.phone.label('merchant_phone'), MerchantDB.id.label('merchant_id') ).join( MerchantProductDB, MerchantOrderDB.merchant_product_id == MerchantProductDB.id ).join( MerchantDB, MerchantProductDB.merchant_id == MerchantDB.id ).filter( MerchantOrderDB.user_id == current_user.userid ).order_by( MerchantOrderDB.create_time.desc() ).offset(skip).limit(limit).all() # 构建返回数据 order_list = [{ "id": order.MerchantOrderDB.id, "order_id": order.MerchantOrderDB.order_id, "user_id": order.MerchantOrderDB.user_id, "merchant_product_id": order.MerchantOrderDB.merchant_product_id, "order_amount": order.MerchantOrderDB.order_amount, "status": order.MerchantOrderDB.status, "order_verify_code": order.MerchantOrderDB.order_verify_code, "verify_time": order.MerchantOrderDB.verify_time, "verify_user_id": order.MerchantOrderDB.verify_user_id, "create_time": order.MerchantOrderDB.create_time, "update_time": order.MerchantOrderDB.update_time, # 商品信息 "product_name": order.product_name, "product_image": process_image(order.product_image).thumbnail(width=450, height=450).format(ImageFormat.WEBP).build(), "product_tags": order.product_tags, # 商家信息 "merchant_id": order.merchant_id, "merchant_name": order.merchant_name, "merchant_latitude": order.merchant_latitude, "merchant_longitude": order.merchant_longitude, "merchant_phone": order.merchant_phone } for order in orders] return success_response(data=order_list) @router.get("/merchant/verify/{verify_code}", response_model=ResponseModel) async def query_verify( verify_code: str, db: Session = Depends(get_db), merchant_user: UserDB = Depends(get_merchant_user) ): """查询待核销订单""" order = db.query( MerchantOrderDB, MerchantProductDB, MerchantDB ).join( MerchantProductDB, MerchantOrderDB.merchant_product_id == MerchantProductDB.id ).join( MerchantDB, MerchantProductDB.merchant_id == MerchantDB.id ).filter( MerchantOrderDB.order_verify_code == verify_code, MerchantOrderDB.status == MerchantOrderStatus.UNVERIFIED, MerchantDB.user_id == merchant_user.userid ).first() return success_response(data=MerchantOrderInfo.model_validate(order)) @router.post("/merchant/verify", response_model=ResponseModel) async def verify_order( request: MerchantOrderVerify, db: Session = Depends(get_db), merchant_user: UserDB = Depends(get_merchant_user) ): """核销订单""" # 查询订单及相关信息 order = db.query( MerchantOrderDB, MerchantProductDB, MerchantDB ).join( MerchantProductDB, MerchantOrderDB.merchant_product_id == MerchantProductDB.id ).join( MerchantDB, MerchantProductDB.merchant_id == MerchantDB.id ).filter( MerchantOrderDB.order_verify_code == request.verify_code, MerchantOrderDB.status == MerchantOrderStatus.UNVERIFIED, MerchantDB.user_id == merchant_user.userid ).first() if not order: return error_response(code=404, message="订单不存在或已核销") try: # 更新核销时间和核销用户 order.MerchantOrderDB.verify_time = datetime.now(timezone.utc) order.MerchantOrderDB.verify_user_id = merchant_user.userid order.MerchantOrderDB.status = MerchantOrderStatus.VERIFIED # 如果有积分奖励,赠送积分 if order.MerchantProductDB.gift_points > 0: point_manager = PointManager(db) point_manager.add_points( order.MerchantOrderDB.user_id, order.MerchantProductDB.gift_points, PointRecordType.CONSUME_RETURN, f"团购券核销奖励", order.MerchantOrderDB.order_id ) # 对商家进行结算 account_manager = AccountManager(db) settlement_amount = float(order.MerchantProductDB.settlement_amount) account_manager.change_balance( user_id=order.MerchantDB.user_id, amount=settlement_amount, description=f"团购券核销", transaction_id=order.MerchantOrderDB.order_id ) db.commit() return success_response( message="核销成功", data=MerchantOrderInfo.model_validate(order.MerchantOrderDB) ) except Exception as e: db.rollback() return error_response(code=500, message=f"核销失败: {str(e)}") @router.post("/{order_id}/refund/apply", response_model=ResponseModel) async def apply_refund( order_id: str, db: Session = Depends(get_db), current_user: UserDB = Depends(get_current_user) ): """申请退款""" order = db.query(MerchantOrderDB).filter( MerchantOrderDB.order_id == order_id, MerchantOrderDB.user_id == current_user.userid, # 只能申请自己的订单 MerchantOrderDB.status == MerchantOrderStatus.UNVERIFIED # 只有未核销的订单可以退款 ).first() if not order: return error_response(code=404, message="订单不存在或状态不允许退款") # 更新状态为退款中 order.status = MerchantOrderStatus.REFUNDING try: # 调用微信支付退款 wechat = WeChatClient() await wechat.apply_refund( order_id=order.order_id, # total_amount=int(float(order.pay_amount) * 100), # 转换为分 total_amount=int(1), # 测试 1 分钱 reason="用户申请退款" ) db.commit() return success_response( message="退款申请成功", data=MerchantOrderInfo.model_validate(order) ) except Exception as e: db.rollback() return error_response(code=500, message=f"申请退款失败: {str(e)}") @router.get("/{order_id}", response_model=ResponseModel) async def get_order_detail( order_id: str, longitude: Optional[float] = None, latitude: Optional[float] = None, db: Session = Depends(get_db), current_user: UserDB = Depends(get_current_user) ): """获取订单详情""" query = db.query( MerchantOrderDB, MerchantProductDB.name.label('product_name'), MerchantProductDB.image_url.label('product_image'), MerchantProductDB.tags.label('product_tags'), MerchantDB.name.label('merchant_name'), MerchantDB.latitude.label('merchant_latitude'), MerchantDB.longitude.label('merchant_longitude'), MerchantDB.phone.label('merchant_phone') ) if longitude is not None and latitude is not None: query = query.add_columns( text("ST_Distance_Sphere(point(merchants.longitude, merchants.latitude), " "point(:lon, :lat)) as distance").params(lon=longitude, lat=latitude) ) else: query = query.add_columns(text("NULL as distance")) order = query.join( MerchantProductDB, MerchantOrderDB.merchant_product_id == MerchantProductDB.id ).join( MerchantDB, MerchantProductDB.merchant_id == MerchantDB.id ).filter( MerchantOrderDB.order_id == order_id ).first() if not order: return error_response(code=404, message="订单不存在") # 检查权限 if order.MerchantOrderDB.user_id != current_user.userid: return error_response(code=403, message="无权查看此订单") # 构建返回数据 order_data = { "id": order.MerchantOrderDB.id, "order_id": order.MerchantOrderDB.order_id, "user_id": order.MerchantOrderDB.user_id, "merchant_product_id": order.MerchantOrderDB.merchant_product_id, "order_amount": order.MerchantOrderDB.order_amount, "status": order.MerchantOrderDB.status, "order_verify_code": order.MerchantOrderDB.order_verify_code, "verify_time": order.MerchantOrderDB.verify_time, "verify_user_id": order.MerchantOrderDB.verify_user_id, "create_time": order.MerchantOrderDB.create_time, "update_time": order.MerchantOrderDB.update_time, # 商品信息 "product_name": order.product_name, "product_tags": order.product_tags, "product_image": process_image(order.product_image).thumbnail(width=450, height=450).format(ImageFormat.WEBP).build(), # 商家信息 "merchant_name": order.merchant_name, "merchant_latitude": order.merchant_latitude, "merchant_longitude": order.merchant_longitude, "merchant_phone": order.merchant_phone, # 距离信息 "distance": round(order[8]) if order[8] else None } return success_response(data=order_data) @router.post("/calculate-price", response_model=ResponseModel) async def calculate_order_price( order: MerchantOrderCreate, db: Session = Depends(get_db), current_user: UserDB = Depends(get_current_user) ): """计算订单价格 1. 获取商品信息和用户积分 2. 计算最高可抵扣金额 3. 返回最终支付金额 """ # 查询商品信息 product = db.query(MerchantProductDB).filter( MerchantProductDB.id == order.merchant_product_id ).first() if not product: return error_response(code=404, message="商品不存在") return success_response(data={ "gift_points": int(float(product.sale_price) * (float(product.gift_points_rate) / 100) * settings.POINT_RATIO), "amount": product.sale_price }) @router.get("/{order_id}/verify-qrcode", response_model=ResponseModel) async def get_order_qrcode( order_id: str, db: Session = Depends(get_db), current_user: UserDB = Depends(get_current_user) ): """获取订单核销二维码""" # 查询订单 order = db.query(MerchantOrderDB).filter( MerchantOrderDB.order_id == order_id ).first() if not order: return error_response(code=404, message="订单不存在") # 如果已经有二维码,直接返回 if order.qrcode_url: url = process_image(order.qrcode_url).thumbnail(800, 800).format(ImageFormat.WEBP).quality(90).build() return success_response(data={"qrcode_url":url}) try: # 生成二维码 qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=1, ) qr.add_data(order.order_verify_code) qr.make(fit=True) # 创建图片 img = qr.make_image(fill_color="black", back_color="white") # 将图片转换为字节流 img_byte_arr = io.BytesIO() img.save(img_byte_arr, format='PNG') img_byte_arr.seek(0) # 创建 UploadFile 对象 file = UploadFile( file=img_byte_arr, filename=f"qrcode_{order_id}.png", headers=Headers({"content-type": "image/png"}) ) # 上传到腾讯云 url = await qcloud_manager.upload_file(file) # 更新订单的二维码URL order.qrcode_url = url db.commit() process_url = process_image(url).thumbnail(800, 800).format(ImageFormat.WEBP).quality(90).build() return success_response(data={"qrcode_url": process_url}) except Exception as e: db.rollback() return error_response(code=500, message=f"生成二维码失败: {str(e)}")