Files
Django/jituan/services/fubei_query.py
XingQue dacd55bb1e fix: 统一付呗/微信直连官方查单确认,轮询幂等入账
复查与充值轮询按通道查官方单:付呗单查付呗、直连查微信;履约仍幂等防多加,未完成返回业务码便于继续轮询。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 20:57:36 +08:00

131 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""付呗支付结果确认:主动查单(轮询/复查兜底,不依赖回调)。
规则:
- 流水表标明付呗 → 只查付呗(绝不误查微信直连)
- 无流水且俱乐部当前为付呗 → 先尝试付呗查单;查无单再回落微信
- 其它 → 返回 None由调用方走微信/支付宝
"""
from __future__ import annotations
import logging
from typing import Any, Dict, Optional, Tuple
from jituan.constants import (
CLUB_ID_DEFAULT,
MINI_PAY_CHANNEL_FUBEI,
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_club_mini_pay_channel, 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 _should_try_fubei(out_trade_no: str, club_id: Optional[str]) -> Tuple[bool, str, str]:
"""
返回 (是否应付呗查单, club_id, provider_order_sn)。
有付呗流水 → 强制付呗;无流水但俱乐部选用付呗 → 尝试付呗。
"""
cid = (club_id or '').strip() or CLUB_ID_DEFAULT
ledger = get_pay_ledger(out_trade_no)
if ledger and ledger.channel == PAY_CHANNEL_FUBEI_WX_MINI:
return True, (ledger.club_id or cid), (ledger.provider_order_sn or '')
if ledger:
# 明确记了其它通道,不查付呗
return False, cid, ''
if get_club_mini_pay_channel(cid) == MINI_PAY_CHANNEL_FUBEI:
return True, cid, ''
return False, cid, ''
def query_fubei_trade(
out_trade_no: str,
club_id: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
"""
查询付呗订单。
不应查付呗时返回 None
应查付呗时返回 data含 trade_state查单业务失败时抛 Exception。
"""
try_fb, cid, order_sn = _should_try_fubei(out_trade_no, club_id)
if not try_fb:
return None
cfg = get_fubei_config(cid)
if not cfg or not cfg.app_id or not cfg.api_key:
# 俱乐部标了付呗但配置不齐:有流水则报错;仅「尝试」则回落微信
ledger = get_pay_ledger(out_trade_no)
if ledger and ledger.channel == PAY_CHANNEL_FUBEI_WX_MINI:
logger.error('[FUBEI_QUERY] 配置缺失 club=%s out_trade_no=%s', cid, out_trade_no)
raise Exception('付呗查单配置缺失')
return None
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=order_sn,
)
logger.info(
'[FUBEI_QUERY] out_trade_no=%s club=%s trade_state=%s',
out_trade_no, cid, 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]]:
"""
返回值约定(供复查/轮询统一使用):
- None不是付呗单或不应走付呗调用方继续微信/支付宝
- ('', None):是付呗路径,但尚未支付成功(支付中)
- (transaction_id, total_fee):付呗已支付成功
"""
try_fb, _, _ = _should_try_fubei(out_trade_no, club_id)
if not try_fb:
return None
forced = is_fubei_out_trade_no(out_trade_no)
try:
data = query_fubei_trade(out_trade_no, club_id)
except Exception as exc:
msg = str(exc)
logger.error('[FUBEI_QUERY] 查单异常 %s: %s', out_trade_no, exc, exc_info=True)
# 无流水的「尝试」查询:订单不存在等可回落微信直连
if not forced and any(k in msg for k in ('不存在', '无此', '未找到', 'ORDER', 'order')):
return None
# 有流水的付呗单:绝不能回落微信
return ('', None)
if data is None:
return None if not forced else ('', 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)