rtc_backend/app/services/payment_service.py
2026-02-25 15:22:29 +08:00

198 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
支付服务层
处理支付金额计算、订单创建等业务逻辑
"""
import logging
from decimal import Decimal, ROUND_HALF_UP
logger = logging.getLogger(__name__)
# 支付金额最小值(单位:元),防止出现负数或零元订单
MIN_PAYABLE_AMOUNT = Decimal('0.01')
class PaymentCalculator:
"""支付金额计算工具"""
@staticmethod
def calc_final_amount(original_price: float, discount: float = 0.0) -> Decimal:
"""
计算折扣后实付金额
Args:
original_price: 原价(元)
discount: 折扣减免金额(元,非折扣率)
Returns:
Decimal: 最终应付金额,最小为 MIN_PAYABLE_AMOUNT
Example:
>>> calc_final_amount(99.0, 20.0) -> 79.00
>>> calc_final_amount(99.0, 120.0) -> 0.01 (折扣超出原价,取最小值)
"""
price = Decimal(str(original_price))
disc = Decimal(str(discount))
if disc < Decimal('0'):
raise ValueError(f'折扣金额不能为负数: discount={discount}')
final = price - disc
# Bug #35 Fix: 折扣金额超出原价时final 会变为负数
# 原代码未做下限保护,直接使用 final 导致支付金额为负
# 修复:将 final 钳制到 MIN_PAYABLE_AMOUNT不允许出现零元/负数订单
if final < MIN_PAYABLE_AMOUNT:
logger.warning(
'calc_final_amount: discount exceeds price, clamping to min. '
'original_price=%.2f discount=%.2f computed=%.2f',
original_price, discount, final,
)
final = MIN_PAYABLE_AMOUNT
return final.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
class PaymentService:
"""支付业务服务"""
def __init__(self):
self.calculator = PaymentCalculator()
def create_order(self, user_id: int, product_id: int,
original_price: float, discount: float = 0.0) -> dict:
"""
创建支付订单
Args:
user_id: 用户 ID
product_id: 商品 ID
original_price: 原价
discount: 优惠减免(元)
Returns:
dict: 订单信息
"""
final_amount = self.calculator.calc_final_amount(original_price, discount)
# 此处可进一步调用第三方支付网关创建预付单
order = {
'user_id': user_id,
'product_id': product_id,
'original_price': float(original_price),
'discount': float(discount),
'final_amount': float(final_amount),
'status': 'pending',
}
logger.info('Order created: %s', order)
return order
def apply_coupon(self, original_price: float, coupon_value: float) -> dict:
"""
应用优惠券
Args:
original_price: 原价
coupon_value: 券面值
Returns:
dict 包含 final_amount 和 saved_amount
"""
final = self.calculator.calc_final_amount(original_price, coupon_value)
price = Decimal(str(original_price))
saved = price - final
return {
'original_price': float(original_price),
'coupon_value': float(coupon_value),
'final_amount': float(final),
'saved_amount': float(saved),
}
def validate_payment_params(self, original_price: float, discount: float) -> tuple:
"""
校验支付参数合法性
Returns:
(valid: bool, error_message: str)
"""
if original_price <= 0:
return False, '原价必须大于 0'
if discount < 0:
return False, '折扣金额不能为负数'
return True, ''
# ---------------------------------------------------------------------------
# 退款计算
# ---------------------------------------------------------------------------
@staticmethod
def calc_refund_amount(paid_amount: float, refund_ratio: float = 1.0) -> Decimal:
"""
计算退款金额
Args:
paid_amount: 实付金额
refund_ratio: 退款比例 (0~1),默认全额退款
Returns:
Decimal: 退款金额
"""
if not (0 < refund_ratio <= 1):
raise ValueError(f'退款比例不能为负数: refund_ratio={refund_ratio}')
amount = Decimal(str(paid_amount)) * Decimal(str(refund_ratio))
return amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
def process_refund(self, order_id: str, paid_amount: float,
refund_ratio: float = 1.0) -> dict:
"""
处理退款
Args:
order_id: 订单号
paid_amount: 实付金额
refund_ratio: 退款比例
Returns:
dict: 退款结果
"""
refund_amount = self.calc_refund_amount(paid_amount, refund_ratio)
result = {
'order_id': order_id,
'paid_amount': paid_amount,
'refund_amount': float(refund_amount),
'refund_ratio': refund_ratio,
'status': 'refund_pending',
}
logger.info('Refund processed: %s', result)
return result
def get_price_breakdown(self, original_price: float,
discount: float = 0.0,
tax_rate: float = 0.0) -> dict:
"""
获取价格明细
Args:
original_price: 原价
discount: 折扣减免(元)
tax_rate: 税率0~1
Returns:
dict: 价格明细
"""
# line 234 - Bug #35 Fix: calc_final_amount 已处理折扣超出原价的情况
final_before_tax = self.calculator.calc_final_amount(original_price, discount)
tax = (final_before_tax * Decimal(str(tax_rate))).quantize(
Decimal('0.01'), rounding=ROUND_HALF_UP
)
total = final_before_tax + tax
return {
'original_price': float(original_price),
'discount': float(discount),
'subtotal': float(final_before_tax),
'tax': float(tax),
'total': float(total),
}