From f38f1216ea0ee1fe68848b95c66c8ad1c9755e88 Mon Sep 17 00:00:00 2001 From: XingQue Date: Fri, 26 Jun 2026 01:53:28 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=85=83=E8=BD=AC=E5=88=86=E6=94=B9=E7=94=A8=20Decimal?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=202.01=20=E5=85=83=E8=A2=AB?= =?UTF-8?q?=E6=94=B6=202=20=E5=85=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- orders/views.py | 5 +++-- products/views.py | 21 +++++++++++---------- shop/views.py | 3 ++- users/tixian_shenhe_views.py | 3 ++- users/views.py | 7 ++++--- utils/money.py | 30 ++++++++++++++++++++++++++++++ 6 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 utils/money.py diff --git a/orders/views.py b/orders/views.py index 09d75cb..847f818 100644 --- a/orders/views.py +++ b/orders/views.py @@ -38,6 +38,7 @@ from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentClo from tencentcloud.sts.v20180813 import sts_client, models from utils.weixin_broadcast import WeixinBroadcastSender +from utils.money import yuan_to_fen from utils.chat_utils import _send_group_message, _subscribe_users_to_group, establish_order_chat from utils.fadan_utils import check_fadan_qiangdan_eligible @@ -886,7 +887,7 @@ class CreateOrderView(APIView): nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) # 金额(单位:分) - total_fee = int(float(jine) * 100) + total_fee = yuan_to_fen(jine) # 交易类型 trade_type = 'JSAPI' @@ -5662,7 +5663,7 @@ class AdTongYiTuiKuanPingTai(APIView): # 生成退款单号 refund_id = f"TK{int(time.time())}{random.randint(1000, 9999)}" - total_fee_in_fen = int(float(jine) * 100) + total_fee_in_fen = yuan_to_fen(jine) # 构造请求参数 refund_data = { diff --git a/products/views.py b/products/views.py index 9dd33f0..058693c 100644 --- a/products/views.py +++ b/products/views.py @@ -32,6 +32,7 @@ from rest_framework.throttling import AnonRateThrottle # ==================== 项目工具函数 ==================== from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss +from utils.money import format_yuan, quantize_yuan, yuan_to_fen from backend.utils import ( update_guanshi_daily_by_action, update_guanshi_xufei_daily, @@ -289,11 +290,11 @@ class DashouHuiyuanList(APIView): huiyuan_list.append({ 'id': huiyuan.huiyuan_id, 'mingzi': huiyuan.jieshao, - 'jiage': float(club_price), + 'jiage': format_yuan(club_price), 'formal_days': formal_days, 'jieshao': huiyuan.jtjieshao, 'trial_enabled': bool(trial_sellable), - 'trial_price': float(trial_price) if trial_sellable else 0, + 'trial_price': format_yuan(trial_price) if trial_sellable else '0.00', 'trial_days': trial_days if trial_sellable else 0, 'can_buy_trial': bool(trial_ok and trial_sellable), }) @@ -450,7 +451,7 @@ class YajinGoumai(APIView): nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) # 金额转换(元转分) - total_fee = int(jine * 100) + total_fee = yuan_to_fen(jine) # 交易类型(小程序支付使用JSAPI) trade_type = 'JSAPI' @@ -970,7 +971,7 @@ class JifenBuchong(APIView): nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) # 金额转换(元转分) - total_fee = int(jine * 100) + total_fee = yuan_to_fen(jine) # 交易类型(小程序支付使用JSAPI) trade_type = 'JSAPI' @@ -1445,7 +1446,7 @@ class HuiyuanGoumai(APIView): dingdanid = f"CzHY{timestamp_ms:011d}{timestamp_ns:06d}{random_num:02d}" # 6. 创建充值记录订单(金额仅用本俱乐部配置) - jine = float(club_price) + jine = quantize_yuan(club_price) # 组合订单说明:会员介绍 + 配置描述 shuoming = f"{huiyuan.jieshao} - {settings.HUIYUAN_PAY_DESCRIPTION}" @@ -1495,7 +1496,7 @@ class HuiyuanGoumai(APIView): 'payParams': pay_params, 'dingdanid': dingdanid, 'club_id': club_id, - 'jine': jine, + 'jine': format_yuan(jine), 'is_trial': is_trial, 'purchase_days': purchase_days, }, status=status.HTTP_200_OK) @@ -1534,7 +1535,7 @@ class HuiyuanGoumai(APIView): nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) # 金额转换(元转分) - total_fee = int(jine * 100) + total_fee = yuan_to_fen(jine) # 交易类型(小程序支付使用JSAPI) trade_type = 'JSAPI' @@ -2016,8 +2017,8 @@ class ShangjiaChongzhi(APIView): }, status=status.HTTP_400_BAD_REQUEST) try: - jine = float(jine) - except (ValueError, TypeError): + jine = quantize_yuan(jine) + except (ValueError, TypeError, InvalidOperation): return Response({ 'code': 400, 'message': '金额格式错误,请填写有效的数字' @@ -2114,7 +2115,7 @@ class ShangjiaChongzhi(APIView): nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) # 金额转换(元转分) - total_fee = int(Decimal(str(jine)) * 100) + total_fee = yuan_to_fen(jine) # 交易类型(小程序支付使用JSAPI) trade_type = 'JSAPI' diff --git a/shop/views.py b/shop/views.py index 35264b0..085dca9 100644 --- a/shop/views.py +++ b/shop/views.py @@ -33,6 +33,7 @@ from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework_simplejwt.tokens import RefreshToken from utils.oss_utils import upload_to_oss, delete_from_oss, get_oss_client +from utils.money import yuan_to_fen from .utils import verify_shop_permission from shop.utils import verify_shop_permission, update_dianpu_daily_stat @@ -2673,7 +2674,7 @@ class ShopRefundView(APIView): return {'code': 500, 'msg': '微信支付配置不完整'} out_refund_no = self._generate_refund_no(order.OrderID) - total_fee = int(float(order.Amount or 0) * 100) + total_fee = yuan_to_fen(order.Amount or 0) refund_fee = total_fee params = { diff --git a/users/tixian_shenhe_views.py b/users/tixian_shenhe_views.py index b4c6a3e..b6d9602 100644 --- a/users/tixian_shenhe_views.py +++ b/users/tixian_shenhe_views.py @@ -21,6 +21,7 @@ from rest_framework.views import APIView from utils.redis_lock import acquire_lock, release_lock from utils.wechat_v3 import build_authorization +from utils.money import yuan_to_fen from gvsdsdk.fluent import db, func, FQ from .models import TixianAutoRecord @@ -114,7 +115,7 @@ def _build_wx_transfer_body(wx_cfg, tixian_id, openid, yonghuid, leixing, shijid 'out_bill_no': tixian_id, 'transfer_scene_id': transfer_scene_id, 'openid': openid, - 'transfer_amount': int(shijidaozhang * 100), + 'transfer_amount': yuan_to_fen(shijidaozhang), 'transfer_remark': f'平台提现-{yonghuid}', 'transfer_scene_report_infos': transfer_scene_report_infos, } diff --git a/users/views.py b/users/views.py index 9fe25ac..377540e 100644 --- a/users/views.py +++ b/users/views.py @@ -42,6 +42,7 @@ from rest_framework_simplejwt.authentication import JWTAuthentication from gvsdsdk.fluent import db, func, FQ from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss +from utils.money import yuan_to_fen from utils.fadan_utils import check_fadan_qiangdan_eligible from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode from users.fadan_fenhong_utils import process_fadan_fenhong @@ -7774,7 +7775,7 @@ class KefuPlatformRefundView(APIView): # 生成退款单号 out_refund_no = self.generate_refund_no(dingdan_id) - total_fee = int(float(jine) * 100) + total_fee = yuan_to_fen(jine) refund_fee = total_fee refund_desc = '客服退款' @@ -11820,7 +11821,7 @@ class FaKuanPayView(APIView): raise Exception('未知的支付类型') nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) - total_fee = int(jine * 100) + total_fee = yuan_to_fen(jine) trade_type = 'JSAPI' user_ip = self.get_client_ip() @@ -12321,7 +12322,7 @@ class KaohePayView(APIView): raise Exception('未知支付类型') nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) - total_fee = int(jine * 100) + total_fee = yuan_to_fen(jine) trade_type = 'JSAPI' user_ip = self.get_client_ip() diff --git a/utils/money.py b/utils/money.py new file mode 100644 index 0000000..ca3e1d2 --- /dev/null +++ b/utils/money.py @@ -0,0 +1,30 @@ +"""金额换算:禁止 float 参与元/分转换(避免 2.01 元变成 200 分)。""" +from decimal import Decimal, InvalidOperation, ROUND_HALF_UP + +TWO_PLACES = Decimal('0.01') +ONE_FEN = Decimal('1') + + +def to_decimal(amount) -> Decimal: + if amount is None: + return Decimal('0') + if isinstance(amount, Decimal): + return amount + try: + return Decimal(str(amount)) + except (InvalidOperation, ValueError, TypeError): + return Decimal('0') + + +def quantize_yuan(amount) -> Decimal: + return to_decimal(amount).quantize(TWO_PLACES, rounding=ROUND_HALF_UP) + + +def yuan_to_fen(amount) -> int: + """人民币元 → 微信支付整数分(四舍五入到分)。""" + fen = quantize_yuan(amount) * 100 + return int(fen.quantize(ONE_FEN, rounding=ROUND_HALF_UP)) + + +def format_yuan(amount) -> str: + return f'{quantize_yuan(amount):.2f}'