fix: 付呗支付复查/轮询改查付呗单,避免一直显示支付中
下单已走付呗但复查仍查微信直连,导致付成功不入账;现按流水查付呗并履约,同时加强回调日志。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -403,7 +403,16 @@ def confirm_czjilu_paid(dingdan_id, yonghuid, expected_leixing):
|
|||||||
paid = Decimal(str(order.jine))
|
paid = Decimal(str(order.jine))
|
||||||
club_id = getattr(order, 'club_id', None)
|
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.query),TRADE_SUCCESS/FINISHED 则触发履约
|
# 支付宝订单:主动查单(alipay.trade.query),TRADE_SUCCESS/FINISHED 则触发履约
|
||||||
from jituan.services.alipay_pay import query_alipay_trade
|
from jituan.services.alipay_pay import query_alipay_trade
|
||||||
trade_status = query_alipay_trade(dingdan_id, club_id)
|
trade_status = query_alipay_trade(dingdan_id, club_id)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
METHOD_MINA = 'openapi.payment.order.mina'
|
METHOD_MINA = 'openapi.payment.order.mina'
|
||||||
METHOD_REFUND = 'openapi.payment.order.refund'
|
METHOD_REFUND = 'openapi.payment.order.refund'
|
||||||
|
METHOD_QUERY = 'openapi.payment.order.query'
|
||||||
|
|
||||||
|
|
||||||
def _yuan_str(amount) -> str:
|
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]]:
|
def parse_callback_form(post_dict: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
||||||
"""解析付呗 form 回调,返回 (顶层参数含 sign, data 业务字典)。"""
|
"""解析付呗 form 回调,返回 (顶层参数含 sign, data 业务字典)。"""
|
||||||
params = {k: (v if not isinstance(v, list) else (v[0] if v else '')) for k, v in post_dict.items()}
|
params = {k: (v if not isinstance(v, list) else (v[0] if v else '')) for k, v in post_dict.items()}
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ class FubeiPayNotifyView(View):
|
|||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
try:
|
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 {}
|
post = request.POST.dict() if request.POST else {}
|
||||||
if not post and request.body:
|
if not post and request.body:
|
||||||
# 偶发 JSON
|
# 偶发 JSON
|
||||||
@@ -38,6 +43,8 @@ class FubeiPayNotifyView(View):
|
|||||||
except Exception:
|
except Exception:
|
||||||
post = {}
|
post = {}
|
||||||
|
|
||||||
|
logger.info('[FUBEI_NOTIFY] raw=%s post_keys=%s', raw_preview, list(post.keys()))
|
||||||
|
|
||||||
params, data = fubei_client.parse_callback_form(post)
|
params, data = fubei_client.parse_callback_form(post)
|
||||||
merchant_order_sn = str(data.get('merchant_order_sn') or '').strip()
|
merchant_order_sn = str(data.get('merchant_order_sn') or '').strip()
|
||||||
result_code = str(params.get('result_code') or '')
|
result_code = str(params.get('result_code') or '')
|
||||||
@@ -58,7 +65,12 @@ class FubeiPayNotifyView(View):
|
|||||||
return HttpResponse('fail', content_type='text/plain')
|
return HttpResponse('fail', content_type='text/plain')
|
||||||
|
|
||||||
if not fubei_client.verify_callback_sign(params, cfg.api_key):
|
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')
|
return HttpResponse('fail', content_type='text/plain')
|
||||||
|
|
||||||
if result_code not in ('200', '200.0'):
|
if result_code not in ('200', '200.0'):
|
||||||
|
|||||||
91
jituan/services/fubei_query.py
Normal file
91
jituan/services/fubei_query.py
Normal 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)
|
||||||
@@ -1104,7 +1104,13 @@ def confirm_member_recharge_paid(dingdan_id, yonghuid):
|
|||||||
club_id = getattr(order, 'club_id', None)
|
club_id = getattr(order, 'club_id', None)
|
||||||
is_alipay = cache.get(f'alipay_pay_url_{dingdan_id}') is not 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.query),TRADE_SUCCESS/FINISHED 则触发履约
|
# 支付宝订单:主动查单(alipay.trade.query),TRADE_SUCCESS/FINISHED 则触发履约
|
||||||
from jituan.services.alipay_pay import query_alipay_trade
|
from jituan.services.alipay_pay import query_alipay_trade
|
||||||
trade_status = query_alipay_trade(dingdan_id, club_id)
|
trade_status = query_alipay_trade(dingdan_id, club_id)
|
||||||
|
|||||||
@@ -580,7 +580,17 @@ class PaymentVerifyView(APIView):
|
|||||||
trade_state = None
|
trade_state = None
|
||||||
transaction_id = 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)
|
# 支付宝订单:主动查单(alipay.trade.query)
|
||||||
from jituan.services.alipay_pay import query_alipay_trade
|
from jituan.services.alipay_pay import query_alipay_trade
|
||||||
alipay_status = query_alipay_trade(dingdanid, getattr(dingdan, 'ClubID', None))
|
alipay_status = query_alipay_trade(dingdanid, getattr(dingdan, 'ClubID', None))
|
||||||
|
|||||||
Reference in New Issue
Block a user