Files
Django/jituan/services/mini_pay_router.py
XingQue 5762f00d40 feat: 分俱乐部付呗收款通道,默认仍走微信直连
小程序前端仍传 wechat;后台可切换 wechat/fubei,支持配置多套付呗并退款按原通道路由。

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

153 lines
5.2 KiB
Python
Raw 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.
"""小程序「微信支付」通道路由:按俱乐部选择微信直连或付呗;前端仍传 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