From 3313da5ce2e82b95628b5b6516758e3e3a445b45 Mon Sep 17 00:00:00 2001 From: XingQue Date: Thu, 23 Jul 2026 03:21:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BE=85=E4=BB=98=E6=AC=BE=E7=BB=AD?= =?UTF-8?q?=E4=BB=98=20jixuzhifu=EF=BC=8C=E5=A4=8D=E7=94=A8=E5=8E=9F?= =?UTF-8?q?=E5=8D=95=E5=8F=B7=E4=B8=94=E4=B8=8D=E8=AF=AF=E6=94=B9=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 仅 Status=9 可续付;续付前渠道查单,已付走现有 fucha 履约;本接口不把未付标成已付。 Co-authored-by: Cursor --- orders/urls.py | 3 +- orders/views/__init__.py | 4 +- orders/views/payment.py | 161 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 2 deletions(-) diff --git a/orders/urls.py b/orders/urls.py index 10beaea..50de523 100644 --- a/orders/urls.py +++ b/orders/urls.py @@ -1,7 +1,7 @@ from django.urls import path from django.views.decorators.csrf import csrf_exempt from .views import CreateOrderView, WechatPayNotifyView, AlipayPayNotifyView, alipay_pay_page, \ - PaymentFailView, PaymentVerifyView, DingdanHuoquView, DingdanXiangqingView, JiedanView, \ + PaymentFailView, PaymentVerifyView, ContinuePayView, DingdanHuoquView, DingdanXiangqingView, JiedanView, \ ShangpinLeixingView, ShangjiaPaifaView, ShangjiaDingdanHuoquView, ShangjiaJiesuanView, \ ShangjiaDingdanXiangqingView, ShangjiaChufaShenqingView, ShangjiaTuikuanShenqingView, \ ShangjiaChexiaoView, DashouDingdanHuoquView, ShangpinLeixingHuoquView, QiangdanView, \ @@ -17,6 +17,7 @@ from jituan.services.fubei_notify import FubeiPayNotifyView urlpatterns = [ path('xiadan', CreateOrderView.as_view(), name='下单订单生成接口'), + path('jixuzhifu', ContinuePayView.as_view(), name='待付款继续支付'), #path('pay-notify/', WechatPayNotifyView.as_view(), name='老板下单微信支付回调'), path('pay-notify/', csrf_exempt(WechatPayNotifyView.as_view()), name='老板下单微信支付回调'), path('alipay-notify/', csrf_exempt(AlipayPayNotifyView.as_view()), name='老板下单支付宝支付回调'), diff --git a/orders/views/__init__.py b/orders/views/__init__.py index ffc3956..098048b 100644 --- a/orders/views/__init__.py +++ b/orders/views/__init__.py @@ -8,6 +8,7 @@ from .payment import ( AlipayPayNotifyView, PaymentVerifyView, CreateOrderView, + ContinuePayView, alipay_pay_page, ) from .boss_orders import ( @@ -68,7 +69,8 @@ from .merchant_penalty import ( ) __all__ = [ - "PaymentFailView", "WechatPayNotifyView", "AlipayPayNotifyView", "PaymentVerifyView", "CreateOrderView", + "PaymentFailView", "WechatPayNotifyView", "AlipayPayNotifyView", "PaymentVerifyView", "CreateOrderView", + "ContinuePayView", "alipay_pay_page", "DingdanHuoquView", "DingdanXiangqingView", "JiedanView", "DingdanXiangqingView2", "JiedanView2", diff --git a/orders/views/payment.py b/orders/views/payment.py index b033302..6c941d7 100644 --- a/orders/views/payment.py +++ b/orders/views/payment.py @@ -1202,4 +1202,165 @@ class CreateOrderView(APIView): raise e +class ContinuePayView(APIView): + """ + 待付款续付:复用原 OrderID 重新拉起支付,不新建订单。 + + 安全约束(硬保证): + 1. 本接口绝不把 Status 从 9 改成已付;已付只由微信/支付宝回调或 /dingdan/fucha + 在「渠道查单确认 SUCCESS」后写入(与现网一致)。 + 2. 续付前先查渠道:若其实已付成功,则走现有复查履约,避免「用户已付仍显示待付款」。 + 3. 仅 Status==9 且本人订单可续付;其它状态直接拒绝,不会误改。 + """ + permission_classes = [permissions.IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + try: + dingdanid = (request.data.get('dingdanid') or request.data.get('dingdan_id') or '').strip() + pay_method = (request.data.get('pay_method') or 'wechat').strip().lower() + if not dingdanid: + return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None}) + if pay_method not in ('wechat', 'alipay'): + return Response({'code': 1, 'msg': '不支持的支付方式', 'data': None}) + + current_user = request.user + boss_id = f"Boss{current_user.UserUID}" + + dingdan = Order.query.filter(OrderID=dingdanid).first() + if not dingdan: + return Response({'code': 2, 'msg': '订单不存在', 'data': None}) + if dingdan.User1ID != boss_id: + logger.warning('续付越权 dingdanid=%s uid=%s', dingdanid, current_user.UserUID) + return Response({'code': 403, 'msg': '无权操作此订单', 'data': None}) + + # 已进入正常履约/服务流程:不得再拉起支付,也不得改状态 + if dingdan.Status in (1, 7): + return Response({ + 'code': 0, + 'msg': '订单已支付', + 'data': { + 'dingdanid': dingdanid, + 'zhuangtai': dingdan.Status, + 'already_paid': True, + }, + }) + if dingdan.Status != 9: + return Response({ + 'code': 3, + 'msg': '仅待付款订单可继续支付', + 'data': {'zhuangtai': dingdan.Status}, + }) + + club_id = getattr(dingdan, 'ClubID', None) or resolve_club_id_from_request(request) + + # 续付前强制查渠道:已付则履约,杜绝「付了还待付款」 + from jituan.services.pay_confirm import confirm_remote_payment + remote = confirm_remote_payment( + dingdanid, + club_id, + prefer_amount=getattr(dingdan, 'Amount', None), + ) + if remote.get('ok'): + PaymentVerifyView()._process_payment_success( + dingdan, + remote.get('transaction_id'), + current_user.UserUID, + ) + dingdan.refresh_from_db() + return Response({ + 'code': 0, + 'msg': '订单已支付', + 'data': { + 'dingdanid': dingdanid, + 'zhuangtai': dingdan.Status, + 'already_paid': True, + }, + }) + + # 仍未付:复用原单号重新下预支付单(不改 Status,保持 9) + helper = CreateOrderView() + user_openid, club_id = get_payment_openid(request, current_user, club_id) + if pay_method == 'wechat' and not user_openid: + return Response({'code': 10, 'msg': '用户openid不存在', 'data': None}) + + jine = dingdan.Amount + if jine is None or Decimal(str(jine)) <= 0: + return Response({'code': 6, 'msg': '订单金额异常', 'data': None}) + + subject = getattr(settings, 'WEIXIN_PAY_DESCRIPTION', '星阙订单') + try: + if pay_method == 'alipay': + pay_params = helper.generate_alipay_pay_params( + dingdanid=dingdanid, + jine=jine, + subject=subject, + club_id=club_id, + ) + else: + pay_params = helper.generate_wechat_pay_params( + dingdanid=dingdanid, + jine=jine, + beizhu=subject, + user_ip=helper.get_client_ip(request), + openid=user_openid, + club_id=club_id, + ) + except Exception as pay_exc: + err_text = str(pay_exc) + # 渠道侧可能已付(同 out_trade_no),再查一次并履约,绝不静默标已付 + if 'ORDERPAID' in err_text.upper() or '已支付' in err_text: + remote2 = confirm_remote_payment( + dingdanid, club_id, prefer_amount=getattr(dingdan, 'Amount', None), + ) + if remote2.get('ok'): + PaymentVerifyView()._process_payment_success( + dingdan, + remote2.get('transaction_id'), + current_user.UserUID, + ) + dingdan.refresh_from_db() + return Response({ + 'code': 0, + 'msg': '订单已支付', + 'data': { + 'dingdanid': dingdanid, + 'zhuangtai': dingdan.Status, + 'already_paid': True, + }, + }) + logger.exception('续付拉起支付失败 dingdanid=%s', dingdanid) + return Response({'code': 11, 'msg': f'拉起支付失败: {err_text}', 'data': None}) + + if not pay_params: + return Response({'code': 11, 'msg': '支付参数生成失败', 'data': None}) + + # 再次确认本地仍为 9(并发下回调可能已履约) + dingdan.refresh_from_db() + if dingdan.Status != 9: + return Response({ + 'code': 0, + 'msg': '订单状态已更新', + 'data': { + 'dingdanid': dingdanid, + 'zhuangtai': dingdan.Status, + 'already_paid': dingdan.Status in (1, 7), + }, + }) + + return Response({ + 'code': 0, + 'msg': 'ok', + 'data': { + 'dingdanid': dingdanid, + 'payMethod': pay_method, + 'payParams': pay_params, + 'zhuangtai': 9, + 'already_paid': False, + }, + }) + except Exception as e: + logger.exception('ContinuePayView 异常') + return Response({'code': 99, 'msg': f'系统错误: {str(e)}', 'data': None}) +