"""付呗支付结果确认:主动查单(轮询/复查兜底,不依赖回调)。 规则: - 流水表标明付呗 → 只查付呗(绝不误查微信直连) - 无流水且俱乐部当前为付呗 → 先尝试付呗查单;查无单再回落微信 - 其它 → 返回 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)