Files
Django/jituan/views_payment_channel.py
T

242 lines
9.8 KiB
Python

"""俱乐部支付通道后台管理(付呗配置 CRUD + 选用 wechat/fubei)。"""
from __future__ import annotations
import logging
from rest_framework.parsers import JSONParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from backend.utils import verify_kefu_permission
from jituan.constants import (
CLUB_ID_DEFAULT,
MINI_PAY_CHANNEL_FUBEI,
MINI_PAY_CHANNEL_WECHAT,
PAY_CHANNEL_FUBEI_WX_MINI,
)
from jituan.models import Club, ClubPaymentChannel
from jituan.services.club_payment_channel import (
encrypt_secret_field,
invalidate_club_payment_cache,
)
from jituan.services.mini_pay_router import get_club_mini_pay_channel, set_club_mini_pay_channel
from jituan.services.admin_context import can_manage_admin_assignments
logger = logging.getLogger(__name__)
def _mask_secret(plain: str) -> str:
s = (plain or '').strip()
if not s:
return ''
if len(s) <= 8:
return '****'
return s[:4] + '****' + s[-4:]
def _serialize_channel(row: ClubPaymentChannel, reveal_secret: bool = False) -> dict:
from jituan.services.club_payment_channel import decrypt_secret_field
secret = decrypt_secret_field(row.api_key_enc) if row.api_key_enc else ''
extra = dict(row.extra_json or {})
return {
'id': row.id,
'club_id': row.club_id,
'channel': row.channel,
'name': row.name,
'enabled': row.enabled,
'is_default': row.is_default,
'sort_order': row.sort_order,
'sandbox': row.sandbox,
'app_id': row.app_id,
'mch_id': row.mch_id,
'mini_app_id': row.mini_app_id,
'store_id': extra.get('store_id') or extra.get('fubei_store_id') or '',
'merchant_id': extra.get('merchant_id') or row.mch_id or '',
'api_key_masked': _mask_secret(secret),
'api_key': secret if reveal_secret else '',
'api_key_configured': bool(secret),
'notify_url': row.notify_url,
'gateway_url': row.gateway_url,
'remark': row.remark,
'extra_json': extra,
}
class ClubPaymentChannelManageView(APIView):
"""POST /jituan/houtai/payment-channel-manage
actions:
- get: 当前俱乐部 mini_pay_channel + 付呗通道列表
- set_mini_pay_channel: {mini_pay_channel: wechat|fubei}
- save_fubei: 新增或更新付呗配置(按 id 或 name)
- set_default / enable / disable
- delete
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
try:
username = (request.data.get('phone') or request.data.get('username') or '').strip()
if not username:
return Response({'code': 401, 'msg': '缺少账号'}, status=401)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
if not can_manage_admin_assignments(request.user):
return Response({'code': 403, 'msg': '仅集团超管可管理支付通道'}, status=403)
action = (request.data.get('action') or 'get').strip()
club_id = (request.data.get('club_id') or '').strip() or CLUB_ID_DEFAULT
club = Club.query.filter(club_id=club_id).first()
if not club:
return Response({'code': 1, 'msg': f'俱乐部 {club_id} 不存在'})
if action == 'get':
rows = ClubPaymentChannel.query.filter(
club_id=club_id, channel=PAY_CHANNEL_FUBEI_WX_MINI,
).order_by('-is_default', 'sort_order', 'id')
return Response({
'code': 0,
'data': {
'club_id': club_id,
'mini_pay_channel': get_club_mini_pay_channel(club_id),
'fubei_channels': [_serialize_channel(r) for r in rows],
},
})
if action == 'set_mini_pay_channel':
ch = (request.data.get('mini_pay_channel') or '').strip().lower()
try:
set_club_mini_pay_channel(club_id, ch)
except ValueError as exc:
return Response({'code': 400, 'msg': str(exc)})
if ch == MINI_PAY_CHANNEL_FUBEI:
fb = ClubPaymentChannel.query.filter(
club_id=club_id, channel=PAY_CHANNEL_FUBEI_WX_MINI, enabled=1,
).first()
if not fb:
return Response({
'code': 0,
'msg': '已切换为付呗,但尚未配置付呗通道,请先保存付呗配置',
'data': {'mini_pay_channel': ch},
'warning': True,
})
return Response({
'code': 0,
'msg': '已切换小程序收款通道',
'data': {'mini_pay_channel': ch},
})
if action == 'save_fubei':
return self._save_fubei(request, club_id)
if action in ('enable', 'disable', 'set_default', 'delete'):
row_id = request.data.get('id')
row = ClubPaymentChannel.query.filter(
id=row_id, club_id=club_id, channel=PAY_CHANNEL_FUBEI_WX_MINI,
).first()
if not row:
return Response({'code': 1, 'msg': '配置不存在'})
if action == 'delete':
row.delete()
invalidate_club_payment_cache(club_id, PAY_CHANNEL_FUBEI_WX_MINI)
return Response({'code': 0, 'msg': '已删除'})
if action == 'enable':
row.enabled = 1
row.save(update_fields=['enabled'])
elif action == 'disable':
row.enabled = 0
row.save(update_fields=['enabled'])
elif action == 'set_default':
ClubPaymentChannel.objects.filter(
club_id=club_id, channel=PAY_CHANNEL_FUBEI_WX_MINI, is_default=True,
).exclude(id=row.id).update(is_default=False)
row.is_default = True
row.enabled = 1
row.save(update_fields=['is_default', 'enabled'])
invalidate_club_payment_cache(club_id, PAY_CHANNEL_FUBEI_WX_MINI)
return Response({'code': 0, 'msg': 'ok', 'data': _serialize_channel(row)})
return Response({'code': 400, 'msg': f'未知 action: {action}'})
except Exception as exc:
logger.error('payment-channel-manage 异常: %s', exc, exc_info=True)
return Response({'code': 500, 'msg': str(exc)})
def _save_fubei(self, request, club_id: str):
name = (request.data.get('name') or '主付呗').strip() or '主付呗'
row_id = request.data.get('id')
app_id = (request.data.get('app_id') or '').strip()
api_key = request.data.get('api_key')
store_id = request.data.get('store_id')
merchant_id = (request.data.get('merchant_id') or request.data.get('mch_id') or '').strip()
mini_app_id = (request.data.get('mini_app_id') or '').strip()
remark = (request.data.get('remark') or '').strip()
if not app_id:
return Response({'code': 400, 'msg': '请填写付呗开放平台 app_id'})
if store_id in (None, ''):
return Response({'code': 400, 'msg': '请填写门店 store_id'})
row = None
if row_id:
row = ClubPaymentChannel.query.filter(
id=row_id, club_id=club_id, channel=PAY_CHANNEL_FUBEI_WX_MINI,
).first()
if not row:
row = ClubPaymentChannel.query.filter(
club_id=club_id, channel=PAY_CHANNEL_FUBEI_WX_MINI, name=name,
).first()
creating = row is None
if creating:
row = ClubPaymentChannel(
club_id=club_id,
channel=PAY_CHANNEL_FUBEI_WX_MINI,
name=name,
enabled=1,
is_default=True,
)
row.app_id = app_id
row.mch_id = merchant_id
row.mini_app_id = mini_app_id
row.remark = remark
if api_key is not None and str(api_key).strip():
row.api_key_enc = encrypt_secret_field(str(api_key).strip())
elif creating:
return Response({'code': 400, 'msg': '新建付呗配置必须填写接口密钥'})
extra = dict(row.extra_json or {})
try:
extra['store_id'] = int(store_id)
except (TypeError, ValueError):
return Response({'code': 400, 'msg': 'store_id 必须是数字'})
if merchant_id:
extra['merchant_id'] = merchant_id
row.extra_json = extra
# 设为默认时取消同通道其它默认
if request.data.get('is_default', True):
ClubPaymentChannel.objects.filter(
club_id=club_id, channel=PAY_CHANNEL_FUBEI_WX_MINI, is_default=True,
).exclude(id=getattr(row, 'id', None) or 0).update(is_default=False)
row.is_default = True
row.enabled = 1
row.save()
invalidate_club_payment_cache(club_id, PAY_CHANNEL_FUBEI_WX_MINI)
# 可选:保存后同时切到付呗
if request.data.get('activate'):
set_club_mini_pay_channel(club_id, MINI_PAY_CHANNEL_FUBEI)
return Response({
'code': 0,
'msg': '付呗配置已保存',
'data': {
'channel': _serialize_channel(row),
'mini_pay_channel': get_club_mini_pay_channel(club_id),
},
})