All checks were successful
Build and Deploy Backend / build-and-deploy (push) Successful in 4m19s
198 lines
6.2 KiB
Python
198 lines
6.2 KiB
Python
"""
|
||
支付服务层
|
||
处理支付金额计算、订单创建等业务逻辑
|
||
"""
|
||
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'退款比例必须在 (0, 1] 范围内: 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),
|
||
}
|