小程序前端仍传 wechat;后台可切换 wechat/fubei,支持配置多套付呗并退款按原通道路由。 Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
5.2 KiB
Python
153 lines
5.2 KiB
Python
"""小程序「微信支付」通道路由:按俱乐部选择微信直连或付呗;前端仍传 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
|