支付与收款商户号分离:支付改读 pay_* 独立配置与证书目录,收款侧不动。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
169
peizhi/pay_mch_views.py
Normal file
169
peizhi/pay_mch_views.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""后台 — 支付专用商户配置(与转账收款 wxmch* 完全分离,权限 8080a)"""
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from django.db import transaction
|
||||
from rest_framework import permissions
|
||||
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from peizhi.models import XcxSysPeizhi
|
||||
from peizhi.xcx_config_views import _authorize_miniapp_config, _mask_secret
|
||||
from utils.pay_mch_config import pay_cert_base_dir, pay_cfg
|
||||
from utils.xcx_sys_config import clear_xcx_sys_peizhi_cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_SAFE_NAME = re.compile(r'[^A-Za-z0-9._-]+')
|
||||
|
||||
|
||||
def _get_or_create_row():
|
||||
row = XcxSysPeizhi.objects.first()
|
||||
if row is None:
|
||||
row = XcxSysPeizhi.objects.create()
|
||||
return row
|
||||
|
||||
|
||||
def _serialize_pay(row, hide_secret=True):
|
||||
v2 = (row.pay_api_v2_key or '') if row else ''
|
||||
v3 = (row.pay_api_v3_key or '') if row else ''
|
||||
cert = (row.pay_cert_path or '') if row else ''
|
||||
key = (row.pay_key_path or '') if row else ''
|
||||
return {
|
||||
'pay_mch_id': (row.pay_mch_id or '') if row else pay_cfg.PAY_MCH_ID,
|
||||
'pay_api_v2_key': _mask_secret(v2) if hide_secret else v2,
|
||||
'pay_api_v2_key_set': bool(v2),
|
||||
'pay_api_v3_key': _mask_secret(v3) if hide_secret else v3,
|
||||
'pay_api_v3_key_set': bool(v3),
|
||||
'pay_notify_url': (row.pay_notify_url or '') if row else '',
|
||||
'pay_cert_path': cert,
|
||||
'pay_key_path': key,
|
||||
'pay_cert_serial_no': (row.pay_cert_serial_no or '') if row else '',
|
||||
'pay_cert_exists': bool(cert and os.path.isfile(cert)),
|
||||
'pay_key_exists': bool(key and os.path.isfile(key)),
|
||||
'cert_base_dir': pay_cert_base_dir(),
|
||||
'effective_mch_id': pay_cfg.PAY_MCH_ID,
|
||||
}
|
||||
|
||||
|
||||
class PayMchQueryView(APIView):
|
||||
"""POST /peizhi/paymchhq — 查询支付商户配置"""
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
parser_classes = [JSONParser]
|
||||
|
||||
def post(self, request):
|
||||
ok, err = _authorize_miniapp_config(request)
|
||||
if not ok:
|
||||
return err
|
||||
row = XcxSysPeizhi.objects.first()
|
||||
return Response({'code': 0, 'data': _serialize_pay(row)})
|
||||
|
||||
|
||||
class PayMchUpdateView(APIView):
|
||||
"""POST /peizhi/paymchgx — 更新支付商户配置"""
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
parser_classes = [JSONParser]
|
||||
|
||||
def post(self, request):
|
||||
ok, err = _authorize_miniapp_config(request)
|
||||
if not ok:
|
||||
return err
|
||||
|
||||
data = request.data
|
||||
with transaction.atomic():
|
||||
row = _get_or_create_row()
|
||||
if 'pay_mch_id' in data:
|
||||
row.pay_mch_id = str(data.get('pay_mch_id') or '').strip()[:32]
|
||||
if 'pay_notify_url' in data:
|
||||
row.pay_notify_url = str(data.get('pay_notify_url') or '').strip()[:300]
|
||||
if 'pay_cert_serial_no' in data:
|
||||
row.pay_cert_serial_no = str(data.get('pay_cert_serial_no') or '').strip()[:128]
|
||||
if 'pay_cert_path' in data:
|
||||
row.pay_cert_path = str(data.get('pay_cert_path') or '').strip()[:500]
|
||||
if 'pay_key_path' in data:
|
||||
row.pay_key_path = str(data.get('pay_key_path') or '').strip()[:500]
|
||||
|
||||
v2 = data.get('pay_api_v2_key')
|
||||
if v2 is not None and str(v2).strip() and '****' not in str(v2):
|
||||
row.pay_api_v2_key = str(v2).strip()[:128]
|
||||
v3 = data.get('pay_api_v3_key')
|
||||
if v3 is not None and str(v3).strip() and '****' not in str(v3):
|
||||
row.pay_api_v3_key = str(v3).strip()[:128]
|
||||
|
||||
row.save()
|
||||
|
||||
clear_xcx_sys_peizhi_cache()
|
||||
return Response({'code': 0, 'msg': '保存成功', 'data': _serialize_pay(row)})
|
||||
|
||||
|
||||
class PayMchUploadView(APIView):
|
||||
"""
|
||||
POST /peizhi/paymchscwj — 上传支付退款证书/密钥
|
||||
form-data: username, file_type(cert|key), file
|
||||
可选 mch_id(默认用当前 pay_mch_id)
|
||||
"""
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
parser_classes = [MultiPartParser, FormParser]
|
||||
|
||||
ALLOWED_TYPES = {
|
||||
'cert': 'apiclient_cert.pem',
|
||||
'key': 'apiclient_key.pem',
|
||||
}
|
||||
|
||||
def post(self, request):
|
||||
ok, err = _authorize_miniapp_config(request)
|
||||
if not ok:
|
||||
return err
|
||||
|
||||
file_type = str(request.data.get('file_type') or '').strip()
|
||||
upload = request.FILES.get('file')
|
||||
mch_id = str(request.data.get('mch_id') or request.data.get('pay_mch_id') or '').strip()
|
||||
|
||||
if file_type not in self.ALLOWED_TYPES:
|
||||
return Response({'code': 400, 'msg': 'file_type 仅支持 cert / key'})
|
||||
if not upload:
|
||||
return Response({'code': 400, 'msg': '请上传文件'})
|
||||
|
||||
row = _get_or_create_row()
|
||||
if not mch_id:
|
||||
mch_id = (row.pay_mch_id or pay_cfg.PAY_MCH_ID or '').strip()
|
||||
if not mch_id:
|
||||
return Response({'code': 400, 'msg': '请先填写支付商户号再上传证书'})
|
||||
|
||||
dest_name = self.ALLOWED_TYPES[file_type]
|
||||
folder = os.path.join(pay_cert_base_dir(), mch_id)
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
dest_path = os.path.join(folder, dest_name)
|
||||
|
||||
try:
|
||||
with open(dest_path, 'wb') as f:
|
||||
for chunk in upload.chunks():
|
||||
f.write(chunk)
|
||||
except OSError as e:
|
||||
logger.error('上传支付证书失败 mch=%s err=%s', mch_id, e, exc_info=True)
|
||||
return Response({'code': 500, 'msg': f'写入失败: {e}'})
|
||||
|
||||
if file_type == 'cert':
|
||||
row.pay_cert_path = dest_path
|
||||
row.save(update_fields=['pay_cert_path', 'update_time'])
|
||||
else:
|
||||
row.pay_key_path = dest_path
|
||||
row.save(update_fields=['pay_key_path', 'update_time'])
|
||||
|
||||
if not (row.pay_mch_id or '').strip():
|
||||
row.pay_mch_id = mch_id
|
||||
row.save(update_fields=['pay_mch_id', 'update_time'])
|
||||
|
||||
clear_xcx_sys_peizhi_cache()
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': '上传成功',
|
||||
'data': {
|
||||
'mch_id': mch_id,
|
||||
'file_type': file_type,
|
||||
'path': dest_path,
|
||||
**_serialize_pay(row),
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user