fix: 付呗支付复查/轮询改查付呗单,避免一直显示支付中

下单已走付呗但复查仍查微信直连,导致付成功不入账;现按流水查付呗并履约,同时加强回调日志。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-21 20:50:14 +08:00
parent e03d8728ae
commit b19430213a
6 changed files with 158 additions and 4 deletions

View File

@@ -403,7 +403,16 @@ def confirm_czjilu_paid(dingdan_id, yonghuid, expected_leixing):
paid = Decimal(str(order.jine))
club_id = getattr(order, 'club_id', None)
if is_alipay:
# 付呗单:必须查付呗(勿回落微信直连,否则永远「支付中」)
from jituan.services.fubei_query import fubei_success_payload
fb = fubei_success_payload(dingdan_id, club_id)
if fb is not None:
txn, total_fee = fb
if not txn and total_fee is None:
raise CzjiluFulfillError('付呗支付结果尚未同步,请稍后再试', 'not_paid')
if total_fee not in (None, ''):
paid = Decimal(str(total_fee))
elif is_alipay:
# 支付宝订单主动查单alipay.trade.queryTRADE_SUCCESS/FINISHED 则触发履约
from jituan.services.alipay_pay import query_alipay_trade
trade_status = query_alipay_trade(dingdan_id, club_id)

View File

@@ -16,6 +16,7 @@ logger = logging.getLogger(__name__)
METHOD_MINA = 'openapi.payment.order.mina'
METHOD_REFUND = 'openapi.payment.order.refund'
METHOD_QUERY = 'openapi.payment.order.query'
def _yuan_str(amount) -> str:
@@ -162,6 +163,31 @@ def refund_order(
)
def query_order(
*,
gateway_url: str,
app_id: str,
app_secret: str,
merchant_order_sn: str = '',
order_sn: str = '',
) -> Dict[str, Any]:
"""订单查询。trade_state=SUCCESS 表示已支付。"""
biz: Dict[str, Any] = {}
if merchant_order_sn:
biz['merchant_order_sn'] = merchant_order_sn
if order_sn:
biz['order_sn'] = order_sn
if not biz:
raise Exception('付呗查单缺少订单号')
return call_fubei_api(
gateway_url=gateway_url,
app_id=app_id,
app_secret=app_secret,
method=METHOD_QUERY,
biz=biz,
)
def parse_callback_form(post_dict: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
"""解析付呗 form 回调,返回 (顶层参数含 sign, data 业务字典)。"""
params = {k: (v if not isinstance(v, list) else (v[0] if v else '')) for k, v in post_dict.items()}

View File

@@ -30,6 +30,11 @@ class FubeiPayNotifyView(View):
def post(self, request):
try:
raw_preview = ''
try:
raw_preview = (request.body or b'')[:800].decode('utf-8', errors='replace')
except Exception:
raw_preview = ''
post = request.POST.dict() if request.POST else {}
if not post and request.body:
# 偶发 JSON
@@ -38,6 +43,8 @@ class FubeiPayNotifyView(View):
except Exception:
post = {}
logger.info('[FUBEI_NOTIFY] raw=%s post_keys=%s', raw_preview, list(post.keys()))
params, data = fubei_client.parse_callback_form(post)
merchant_order_sn = str(data.get('merchant_order_sn') or '').strip()
result_code = str(params.get('result_code') or '')
@@ -58,7 +65,12 @@ class FubeiPayNotifyView(View):
return HttpResponse('fail', content_type='text/plain')
if not fubei_client.verify_callback_sign(params, cfg.api_key):
logger.error('[FUBEI_NOTIFY] 验签失败 order=%s', merchant_order_sn)
# 验签失败仍记录,便于排查;同时尝试用 data 字符串原样参与(部分网关会改写)
logger.error(
'[FUBEI_NOTIFY] 验签失败 order=%s params=%s',
merchant_order_sn,
{k: params.get(k) for k in ('result_code', 'result_message', 'sign') if k in params},
)
return HttpResponse('fail', content_type='text/plain')
if result_code not in ('200', '200.0'):

View File

@@ -0,0 +1,91 @@
"""付呗支付结果确认:主动查单(轮询/复查兜底,不依赖回调)。"""
from __future__ import annotations
import logging
from typing import Any, Dict, Optional, Tuple
from jituan.constants import PAY_CHANNEL_FUBEI_WX_MINI
from jituan.services.club_payment_channel import get_fubei_config
from jituan.services import fubei_client
from jituan.services.mini_pay_router import get_pay_ledger
logger = logging.getLogger(__name__)
def is_fubei_out_trade_no(out_trade_no: str) -> bool:
ledger = get_pay_ledger(out_trade_no)
return bool(ledger and ledger.channel == PAY_CHANNEL_FUBEI_WX_MINI)
def query_fubei_trade(
out_trade_no: str,
club_id: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
"""
查询付呗订单。
非付呗单返回 None付呗单返回 data 字典(含 trade_state 等)。
"""
ledger = get_pay_ledger(out_trade_no)
if not ledger or ledger.channel != PAY_CHANNEL_FUBEI_WX_MINI:
return None
cid = (club_id or ledger.club_id or '').strip()
cfg = get_fubei_config(cid)
if not cfg or not cfg.app_id or not cfg.api_key:
logger.error('[FUBEI_QUERY] 配置缺失 club=%s out_trade_no=%s', cid, out_trade_no)
raise Exception('付呗查单配置缺失')
data = fubei_client.query_order(
gateway_url=cfg.gateway_url,
app_id=cfg.app_id,
app_secret=cfg.api_key,
merchant_order_sn=out_trade_no,
order_sn=ledger.provider_order_sn or '',
)
logger.info(
'[FUBEI_QUERY] out_trade_no=%s trade_state=%s',
out_trade_no, data.get('trade_state'),
)
return data
def fubei_trade_is_success(data: Optional[Dict[str, Any]]) -> bool:
if not data:
return False
state = str(data.get('trade_state') or '').upper()
return state == 'SUCCESS'
def fubei_success_payload(
out_trade_no: str,
club_id: Optional[str] = None,
) -> Optional[Tuple[str, Any]]:
"""
若是付呗单且已支付成功,返回 (transaction_id, total_fee)
若是付呗单但未成功,返回 ('', None) 且调用方可据此判断「支付中」;
若不是付呗单,返回 None继续走微信/支付宝查单)。
"""
try:
data = query_fubei_trade(out_trade_no, club_id)
except Exception as exc:
# 明确是付呗单但查单失败:不要回落到微信直连(会永远查不到)
ledger = get_pay_ledger(out_trade_no)
if ledger and ledger.channel == PAY_CHANNEL_FUBEI_WX_MINI:
logger.error('[FUBEI_QUERY] 付呗单查单异常 %s: %s', out_trade_no, exc, exc_info=True)
return ('', None)
return None
if data is None:
return None
if not fubei_trade_is_success(data):
return ('', None)
txn = str(
data.get('trade_no')
or data.get('platform_order_no')
or data.get('order_sn')
or out_trade_no
or 'FUBEI_PAID'
)
total_fee = data.get('total_fee') or data.get('cash_fee') or data.get('order_price')
return (txn, total_fee)

View File

@@ -1104,7 +1104,13 @@ def confirm_member_recharge_paid(dingdan_id, yonghuid):
club_id = getattr(order, 'club_id', None)
is_alipay = cache.get(f'alipay_pay_url_{dingdan_id}') is not None
if is_alipay:
from jituan.services.fubei_query import fubei_success_payload
fb = fubei_success_payload(dingdan_id, club_id)
if fb is not None:
txn, _total = fb
if not txn and _total is None:
raise MemberRechargeError('付呗支付结果尚未同步,请稍后再试', 'not_paid')
elif is_alipay:
# 支付宝订单主动查单alipay.trade.queryTRADE_SUCCESS/FINISHED 则触发履约
from jituan.services.alipay_pay import query_alipay_trade
trade_status = query_alipay_trade(dingdan_id, club_id)

View File

@@ -580,7 +580,17 @@ class PaymentVerifyView(APIView):
trade_state = None
transaction_id = None
if is_alipay:
# 付呗单优先:勿用微信直连 orderquery查不到会一直「支付中」
from jituan.services.fubei_query import fubei_success_payload
fb = fubei_success_payload(dingdanid, getattr(dingdan, 'ClubID', None))
if fb is not None:
txn, _fee = fb
if txn or _fee is not None:
trade_state = 'SUCCESS'
transaction_id = txn or None
else:
return Response({'code': 1, 'msg': '支付中', 'data': {'zhuangtai': 9}})
elif is_alipay:
# 支付宝订单主动查单alipay.trade.query
from jituan.services.alipay_pay import query_alipay_trade
alipay_status = query_alipay_trade(dingdanid, getattr(dingdan, 'ClubID', None))