增加退款申请。
This commit is contained in:
parent
f81b0a9c2f
commit
1429b2d113
@ -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)}")
|
||||||
@ -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)}")
|
||||||
@ -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)}")
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user