增加退款申请。

This commit is contained in:
aaron 2025-01-27 23:57:58 +08:00
parent f81b0a9c2f
commit 1429b2d113
3 changed files with 204 additions and 3 deletions

View File

@ -19,7 +19,8 @@ from datetime import datetime, timezone
from app.models.merchant import MerchantDB from app.models.merchant import MerchantDB
from app.models.point import PointRecordDB from app.models.point import PointRecordDB
from app.core.account import AccountManager from app.core.account import AccountManager
from app.core.wechat import WeChatClient
from pydantic import BaseModel
router = APIRouter() router = APIRouter()
@router.post("", response_model=ResponseModel) @router.post("", response_model=ResponseModel)
@ -364,4 +365,50 @@ async def calculate_order_price(
return success_response(data={ return success_response(data={
"original_price": float(product.sale_price), "original_price": float(product.sale_price),
"final_amount": product.sale_price "final_amount": product.sale_price
}) })
class RefundRequest(BaseModel):
"""退款请求"""
order_id: str
@router.post("/refund/approve", response_model=ResponseModel)
async def refund_merchant_order(
request: RefundRequest,
db: Session = Depends(get_db),
admin_user: UserDB = Depends(get_admin_user)
):
"""
审核通过退款申请管理员接口
- 检查订单是否处于退款中状态
- 调用微信支付退款接口
- 退款状态由微信支付回调更新
"""
try:
# 查询订单
order = db.query(MerchantOrderDB).filter(
MerchantOrderDB.order_id == request.order_id,
MerchantOrderDB.status == MerchantOrderStatus.REFUNDING, # 只能退款申请中的订单
MerchantOrderDB.pay_status == True # 已支付的订单
).first()
if not order:
return error_response(code=404, message="订单不存在或状态不正确")
# 调用微信支付退款
wechat = WeChatClient()
try:
await wechat.apply_refund(
order_id=order.order_id,
total_amount=int(float(order.pay_amount) * 100), # 转换为分
reason="用户申请退款"
)
except Exception as e:
return error_response(code=500, message=f"申请退款失败: {str(e)}")
db.commit()
return success_response(message="退款申请成功")
except Exception as e:
db.rollback()
return error_response(code=500, message=f"处理退款失败: {str(e)}")

View File

@ -18,6 +18,8 @@ from app.models.merchant_order import MerchantOrderDB, MerchantOrderStatus
from app.models.merchant_pay_order import MerchantPayOrderDB, MerchantPayOrderStatus from app.models.merchant_pay_order import MerchantPayOrderDB, MerchantPayOrderStatus
import enum import enum
from app.core.point_manager import PointManager from app.core.point_manager import PointManager
from app.core.point_manager import PointRecordType
router = APIRouter() router = APIRouter()
class PhoneNumberRequest(BaseModel): class PhoneNumberRequest(BaseModel):
@ -271,4 +273,87 @@ async def payment_notify(
except Exception as e: except Exception as e:
print(f"处理支付回调失败: {str(e)}") print(f"处理支付回调失败: {str(e)}")
db.rollback() db.rollback()
return error_response(code=500, message=f"处理支付回调失败: {str(e)}") return error_response(code=500, message=f"处理支付回调失败: {str(e)}")
@router.post("/refund/notify")
async def refund_notify(
request: Request,
db: Session = Depends(get_db)
):
"""微信支付退款回调通知"""
try:
# 初始化微信支付客户端
wechat = WeChatClient()
data = await wechat.verify_payment_notify(request)
if not data:
print(f"退款回调数据验证失败: {data}")
return error_response(code=400, message="回调数据验证失败")
print(f"退款回调数据验证成功: {data}")
# 获取退款信息
out_trade_no = data.get("out_trade_no") # 订单号
refund_status = data.get("refund_status")
success_time = data.get("success_time")
if not all([out_trade_no, refund_status]) or refund_status != "SUCCESS":
return error_response(code=400, message="缺少必要的退款信息或退款未成功")
# 根据订单类型处理退款
if out_trade_no.startswith('M'): # 商家商品订单
order = db.query(MerchantOrderDB).filter(
MerchantOrderDB.order_id == out_trade_no,
MerchantOrderDB.status == MerchantOrderStatus.REFUNDING
).first()
if not order:
return error_response(code=404, message="订单不存在或状态不正确")
# 更新订单状态
order.status = MerchantOrderStatus.REFUNDED
# 扣除积分
points = int(float(order.pay_amount) * 10) # 按照支付金额计算积分
point_manager = PointManager(db)
point_manager.deduct_points(
order.user_id,
points,
PointRecordType.CONSUME_RETURN,
f"订单退款,扣除{settings.POINT_ALIAS}",
order.order_id
)
elif out_trade_no.startswith('P'): # 商家在线买单
order = db.query(MerchantPayOrderDB).filter(
MerchantPayOrderDB.order_id == out_trade_no,
MerchantPayOrderDB.status == MerchantPayOrderStatus.REFUNDING
).first()
if not order:
return error_response(code=404, message="订单不存在或状态不正确")
# 更新订单状态
order.status = MerchantPayOrderStatus.REFUNDED
# 扣除积分
points = int(float(order.amount) * 10) # 按照支付金额计算积分
point_manager = PointManager(db)
point_manager.deduct_points(
order.user_id,
points,
PointRecordType.CONSUME_RETURN,
f"订单退款,扣除{settings.POINT_ALIAS}",
order.order_id
)
else:
return error_response(code=400, message="不支持的订单类型")
db.commit()
return success_response(message="退款处理成功")
except Exception as e:
print(f"处理退款回调失败: {str(e)}")
db.rollback()
return error_response(code=500, message=f"处理退款回调失败: {str(e)}")

View File

@ -363,5 +363,74 @@ class WeChatClient:
import traceback import traceback
print("详细错误信息:", traceback.format_exc()) print("详细错误信息:", traceback.format_exc())
raise Exception(f"处理支付回调失败: {str(e)}") raise Exception(f"处理支付回调失败: {str(e)}")
async def apply_refund(
self,
order_id: str,
total_amount: int,
reason: str = "用户申请退款"
) -> dict:
"""
申请退款
Args:
order_id: 订单号(同时作为退款单号)
total_amount: 退款金额()
reason: 退款原因
Returns:
dict: 退款结果
"""
url_path = "/v3/refund/domestic/refunds"
api_url = f"https://api.mch.weixin.qq.com{url_path}"
# 构建请求数据
body = {
"out_trade_no": order_id, # 原订单号
"out_refund_no": order_id, # 退款单号使用原订单号
"reason": reason,
"notify_url": f"{settings.API_BASE_URL}/api/wechat/refund/notify",
"amount": {
"refund": total_amount, # 退款金额
"total": total_amount, # 原订单金额
"currency": "CNY"
}
}
# 生成签名
nonce_str = str(uuid.uuid4()).replace('-', '')
timestamp = str(int(time.time()))
signature = self.sign_message("POST", url_path, body)
# 构建认证头
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': (
f'WECHATPAY2-SHA256-RSA2048 '
f'mchid="{self.mch_id}",'
f'nonce_str="{nonce_str}",'
f'timestamp="{timestamp}",'
f'serial_no="{self.cert_serial_no}",'
f'signature="{signature}"'
)
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(api_url, json=body, headers=headers) as response:
result = await response.json()
if response.status != 200:
raise Exception(f"退款申请失败: {result.get('message')}")
# 验证响应签名
if not self.verify_response(response.headers, await response.read()):
raise Exception("响应签名验证失败")
return result
except Exception as e:
raise Exception(f"申请退款失败: {str(e)}")