"""小程序「微信支付」通道路由:按俱乐部选择微信直连或付呗;前端仍传 pay_method=wechat。""" from __future__ import annotations import logging from decimal import Decimal from typing import Any, Dict, Optional from django.conf import settings from jituan.constants import ( CLUB_ID_DEFAULT, FUBEI_BIZ_CZJILU, FUBEI_BIZ_ORDER, MINI_PAY_CHANNEL_FUBEI, MINI_PAY_CHANNEL_WECHAT, PAY_CHANNEL_FUBEI_WX_MINI, ) from jituan.models import Club, PayChannelLedger from jituan.services.club_payment_channel import get_fubei_config, invalidate_club_payment_cache from jituan.services import fubei_client logger = logging.getLogger(__name__) def get_club_mini_pay_channel(club_id: Optional[str] = None) -> str: """返回 wechat / fubei;缺省 wechat(现网行为)。""" cid = (club_id or '').strip() or CLUB_ID_DEFAULT club = Club.query.filter(club_id=cid).first() if not club: return MINI_PAY_CHANNEL_WECHAT ch = (getattr(club, 'mini_pay_channel', None) or MINI_PAY_CHANNEL_WECHAT).strip().lower() if ch == MINI_PAY_CHANNEL_FUBEI: return MINI_PAY_CHANNEL_FUBEI return MINI_PAY_CHANNEL_WECHAT def set_club_mini_pay_channel(club_id: str, channel: str) -> None: cid = (club_id or '').strip() or CLUB_ID_DEFAULT ch = (channel or '').strip().lower() if ch not in (MINI_PAY_CHANNEL_WECHAT, MINI_PAY_CHANNEL_FUBEI): raise ValueError('mini_pay_channel 仅支持 wechat / fubei') club = Club.query.filter(club_id=cid).first() if not club: raise ValueError(f'俱乐部 {cid} 不存在') club.mini_pay_channel = ch club.save(update_fields=['mini_pay_channel']) invalidate_club_payment_cache(cid) def _default_fubei_notify_url(club_id: Optional[str] = None) -> str: base = 'https://www.abas.asia' club = Club.query.filter(club_id=(club_id or CLUB_ID_DEFAULT)).first() if club and (club.h5_domain or '').strip(): base = club.h5_domain.strip().rstrip('/') return f'{base}/hqhd/dingdan/fubei-notify/' def _remember_ledger( *, out_trade_no: str, club_id: str, channel: str, channel_config_id: Optional[int], biz_type: str, provider_order_sn: str, amount_yuan, ) -> None: row = PayChannelLedger.query.filter(out_trade_no=out_trade_no).first() fields = { 'club_id': club_id or CLUB_ID_DEFAULT, 'channel': channel, 'channel_config_id': channel_config_id, 'biz_type': biz_type or '', 'provider_order_sn': provider_order_sn or '', 'amount_yuan': Decimal(str(amount_yuan or 0)), } if row: for k, v in fields.items(): setattr(row, k, v) row.save() else: PayChannelLedger.objects.create(out_trade_no=out_trade_no, **fields) def get_pay_ledger(out_trade_no: str) -> Optional[PayChannelLedger]: if not out_trade_no: return None return PayChannelLedger.query.filter(out_trade_no=out_trade_no).first() def maybe_create_fubei_jsapi_params( *, club_id: Optional[str], out_trade_no: str, amount_yuan, body: str, openid: str, notify_url: str = '', biz_type: str = FUBEI_BIZ_ORDER, ) -> Optional[Dict[str, Any]]: """若俱乐部选用付呗则下单并返回 wx.requestPayment 参数;否则返回 None(走原微信逻辑)。""" cid = (club_id or '').strip() or CLUB_ID_DEFAULT if get_club_mini_pay_channel(cid) != MINI_PAY_CHANNEL_FUBEI: return None cfg = get_fubei_config(cid) if not cfg or not cfg.app_id or not cfg.api_key: raise Exception('俱乐部已切换付呗收款,但付呗开放平台 ID/密钥未配置完整') store_id = cfg.store_id if store_id in (None, '', 0, '0'): raise Exception('俱乐部已切换付呗收款,但门店 store_id 未配置') if not openid: raise Exception('付呗支付缺少用户 openid') cb = (notify_url or '').strip() or (cfg.notify_url or '').strip() or _default_fubei_notify_url(cid) # 强制走付呗专用回调(原微信 XML 回调无法解析付呗 form) if 'fubei-notify' not in cb: cb = _default_fubei_notify_url(cid) attach = f'biz={biz_type or FUBEI_BIZ_ORDER}' data = fubei_client.mina_prepay( gateway_url=cfg.gateway_url, app_id=cfg.app_id, app_secret=cfg.api_key, merchant_order_sn=out_trade_no, sub_openid=openid, total_fee=amount_yuan, store_id=int(store_id), call_back_url=cb, body=body or '', attach=attach, ) sign_params = data.get('sign_params') or {} if isinstance(sign_params, str): import json sign_params = json.loads(sign_params) pay_params = fubei_client.extract_jsapi_pay_params(sign_params) _remember_ledger( out_trade_no=out_trade_no, club_id=cid, channel=PAY_CHANNEL_FUBEI_WX_MINI, channel_config_id=cfg.config_id, biz_type=biz_type or FUBEI_BIZ_ORDER, provider_order_sn=str(data.get('order_sn') or ''), amount_yuan=amount_yuan, ) logger.info( '[FUBEI_PAY] club=%s out_trade_no=%s biz=%s order_sn=%s', cid, out_trade_no, biz_type, data.get('order_sn'), ) return pay_params