Files
along_django/peizhi/pay_mch_views.py

170 lines
6.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.
"""后台 — 支付专用商户配置(与转账收款 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),
},
})