feat: 待付款续付 jixuzhifu,复用原单号且不误改支付状态

仅 Status=9 可续付;续付前渠道查单,已付走现有 fucha 履约;本接口不把未付标成已付。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-23 03:21:23 +08:00
parent f2a9c1abc5
commit 3313da5ce2
3 changed files with 166 additions and 2 deletions

View File

@@ -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='老板下单支付宝支付回调'),

View File

@@ -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",

View File

@@ -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})