支付与收款商户号分离:支付改读 pay_* 独立配置与证书目录,收款侧不动。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-27 05:41:56 +08:00
parent 89fa08e5de
commit db91626e7f
12 changed files with 531 additions and 78 deletions

View File

@@ -0,0 +1,22 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0017_dingdan_guanshi_shangjia_fencheng'),
]
operations = [
migrations.AddField(
model_name='dingdan',
name='pay_mch_id',
field=models.CharField(
blank=True,
db_index=True,
default='',
max_length=32,
verbose_name='下单支付商户号',
),
),
]

View File

@@ -64,6 +64,13 @@ class Dingdan(models.Model):
db_index=True,
verbose_name='微信支付订单号'
)
pay_mch_id = models.CharField(
max_length=32,
blank=True,
default='',
db_index=True,
verbose_name='下单支付商户号',
)
class Meta:
db_table = 'dingdan'

View File

@@ -17,6 +17,7 @@ from decimal import Decimal
from django.db import transaction
from django.conf import settings
from utils.xcx_sys_config import wx_cfg
from utils.pay_mch_config import pay_cfg, get_current_pay_mch_id, get_pay_wx_cfg
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
@@ -202,6 +203,7 @@ class CreateOrderView(APIView):
'tupian':shangpin_tupian,
'user1_id':f"Boss{request.user.yonghuid}",
'leixing_id': qiangdan_fields['leixing_id'],
'pay_mch_id': get_current_pay_mch_id(),
}
# 创建订单记录
@@ -260,9 +262,9 @@ class CreateOrderView(APIView):
try:
# 微信支付配置
APPID = wx_cfg.WEIXIN_APPID
MCHID = wx_cfg.WEIXIN_MCHID
KEY = wx_cfg.WEIXIN_SHANGHUMIYAO
NOTIFY_URL = wx_cfg.WEIXIN_NOTIFY_URL
MCHID = pay_cfg.WEIXIN_MCHID
KEY = pay_cfg.WEIXIN_SHANGHUMIYAO
NOTIFY_URL = pay_cfg.WEIXIN_NOTIFY_URL
if not all([APPID, MCHID, KEY, NOTIFY_URL]):
raise Exception('微信支付配置不完整')
@@ -509,7 +511,7 @@ class WechatPayNotifyView(APIView):
# 按key排序
sorted_keys = sorted(data.keys())
stringA = '&'.join([f"{k}={data[k]}" for k in sorted_keys])
stringSignTemp = f"{stringA}&key={wx_cfg.WEIXIN_SHANGHUMIYAO}"
stringSignTemp = f"{stringA}&key={pay_cfg.WEIXIN_SHANGHUMIYAO}"
calc_sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
logger.debug(f"计算签名: {calc_sign}, 微信签名: {sign}")
return calc_sign == sign
@@ -833,8 +835,8 @@ class PaymentVerifyView(APIView):
若查询失败,抛出异常
"""
appid = wx_cfg.WEIXIN_APPID
mch_id = wx_cfg.WEIXIN_MCHID
key = wx_cfg.WEIXIN_SHANGHUMIYAO
mch_id = pay_cfg.WEIXIN_MCHID
key = pay_cfg.WEIXIN_SHANGHUMIYAO
nonce_str = self._generate_nonce_str()
params = {
@@ -1058,6 +1060,7 @@ class VirtualCreateOrderView(APIView):
'tupian': shangpin_tupian,
'user1_id': f"Boss{current_user.yonghuid}",
'leixing_id': qiangdan_fields['leixing_id'],
'pay_mch_id': get_current_pay_mch_id(),
}
dingdan = Dingdan.objects.create(**order_data)
@@ -7130,7 +7133,8 @@ class AdTongYiTuiKuanPingTai(APIView):
weixin_refund_result = self.call_weixin_refund(
out_trade_no=dingdan_id,
jine=jine,
transaction_id=transaction_id
transaction_id=transaction_id,
pay_mch_id=getattr(dingdan_obj, 'pay_mch_id', '') or '',
)
if not weixin_refund_result.get('success'):
@@ -7217,20 +7221,24 @@ class AdTongYiTuiKuanPingTai(APIView):
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
def call_weixin_refund(self, out_trade_no, jine, transaction_id=None):
def call_weixin_refund(self, out_trade_no, jine, transaction_id=None, pay_mch_id=None):
"""
调用微信退款接口
:param out_trade_no: 商户订单号
:param jine: 退款金额
:param transaction_id: 微信支付订单号(优先使用)
:param pay_mch_id: 下单时的支付商户号(有则按原商户退)
:return: {'success': True/False, 'msg': '消息', 'refund_id': '退款单号'}
"""
try:
# 检查配置
required_settings = ['WEIXIN_APPID', 'WEIXIN_MCHID', 'WEIXIN_SHANGHUMIYAO', 'WEIXIN_CERT_PATH', 'WEIXIN_KEY_PATH']
for setting in required_settings:
if not hasattr(settings, setting) or not getattr(settings, setting):
return {'success': False, 'msg': f'缺少微信支付配置: {setting}'}
pcfg = get_pay_wx_cfg(pay_mch_id)
mch_id = pcfg.get('MCHID') or ''
key = pcfg.get('KEY') or ''
cert_path = pcfg.get('CERT_PATH') or ''
key_path = pcfg.get('KEY_PATH') or ''
appid = wx_cfg.WEIXIN_APPID
if not all([appid, mch_id, key, cert_path, key_path]):
return {'success': False, 'msg': '缺少微信支付配置'}
# 生成退款单号
refund_id = f"TK{int(time.time())}{random.randint(1000, 9999)}"
@@ -7238,13 +7246,13 @@ class AdTongYiTuiKuanPingTai(APIView):
# 构造请求参数
refund_data = {
'appid': wx_cfg.WEIXIN_APPID,
'mch_id': wx_cfg.WEIXIN_MCHID,
'appid': appid,
'mch_id': mch_id,
'nonce_str': hashlib.md5(str(time.time()).encode()).hexdigest(),
'out_refund_no': refund_id,
'total_fee': total_fee_in_fen,
'refund_fee': total_fee_in_fen,
'op_user_id': wx_cfg.WEIXIN_MCHID,
'op_user_id': mch_id,
'refund_account': 'REFUND_SOURCE_UNSETTLED_FUNDS'
}
# 优先使用 transaction_id否则用 out_trade_no
@@ -7256,7 +7264,7 @@ class AdTongYiTuiKuanPingTai(APIView):
# 生成签名
sorted_params = sorted(refund_data.items())
sign_string = '&'.join([f"{k}={v}" for k, v in sorted_params if v])
sign_string += f"&key={wx_cfg.WEIXIN_SHANGHUMIYAO}"
sign_string += f"&key={key}"
sign = hashlib.md5(sign_string.encode()).hexdigest().upper()
refund_data['sign'] = sign
@@ -7266,7 +7274,7 @@ class AdTongYiTuiKuanPingTai(APIView):
url,
data=xml_data.encode('utf-8'),
headers={'Content-Type': 'application/xml'},
cert=(wx_cfg.WEIXIN_CERT_PATH, wx_cfg.WEIXIN_KEY_PATH),
cert=(cert_path, key_path),
timeout=30
)
@@ -7473,7 +7481,11 @@ class AdTongYiTuiKuanPingTai(APIView):
# ========== 修改点开始 ==========
# 13. 先调用微信退款接口,成功后才更新数据库
weixin_refund_result = self.call_weixin_refund(dingdan_id, jine)
weixin_refund_result = self.call_weixin_refund(
dingdan_id,
jine,
pay_mch_id=getattr(dingdan_obj, 'pay_mch_id', '') or '',
)
if not weixin_refund_result.get('success'):
# 微信退款失败,直接返回错误,数据库不更新
@@ -7588,27 +7600,20 @@ class AdTongYiTuiKuanPingTai(APIView):
def call_weixin_refund(self, dingdan_id, jine):
def call_weixin_refund(self, dingdan_id, jine, pay_mch_id=None):
"""
调用微信退款接口
参考你提供的接口,但根据现有模型和配置调整
需要在settings.py中配置以下参数
WEIXIN_APPID = '你的小程序appid'
WEIXIN_MCHID = '你的商户号'
WEIXIN_SHANGHUMIYAO = '你的商户密钥'
WEIXIN_CERT_PATH = '证书路径/cert/apiclient_cert.pem'
WEIXIN_KEY_PATH = '证书路径/cert/apiclient_key.pem'
调用微信退款接口。pay_mch_id 有值时按原下单商户退款。
返回:{'success': True/False, 'msg': '消息'}
"""
try:
# 检查必要的配置
required_settings = ['WEIXIN_APPID', 'WEIXIN_MCHID', 'WEIXIN_SHANGHUMIYAO', 'WEIXIN_CERT_PATH',
'WEIXIN_KEY_PATH']
for setting in required_settings:
if not hasattr(settings, setting) or not getattr(settings, setting):
return {'success': False, 'msg': f'缺少微信支付配置: {setting}'}
pcfg = get_pay_wx_cfg(pay_mch_id)
mch_id = pcfg.get('MCHID') or ''
key = pcfg.get('KEY') or ''
cert_path = pcfg.get('CERT_PATH') or ''
key_path = pcfg.get('KEY_PATH') or ''
appid = wx_cfg.WEIXIN_APPID
if not all([appid, mch_id, key, cert_path, key_path]):
return {'success': False, 'msg': '缺少微信支付配置'}
# 生成退款单号
refund_id = f"TK{int(time.time())}{random.randint(1000, 9999)}"
@@ -7618,21 +7623,21 @@ class AdTongYiTuiKuanPingTai(APIView):
# 准备微信退款参数
refund_data = {
'appid': wx_cfg.WEIXIN_APPID,
'mch_id': wx_cfg.WEIXIN_MCHID,
'appid': appid,
'mch_id': mch_id,
'nonce_str': hashlib.md5(str(time.time()).encode()).hexdigest(),
'out_trade_no': dingdan_id, # 商户订单号我们的订单ID
'out_refund_no': refund_id, # 商户退款单号
'total_fee': total_fee_in_fen, # 订单总金额(分)
'refund_fee': total_fee_in_fen, # 退款金额(分),全额退款
'op_user_id': wx_cfg.WEIXIN_MCHID, # 操作员ID默认为商户号
'op_user_id': mch_id, # 操作员ID默认为商户号
'refund_account': 'REFUND_SOURCE_UNSETTLED_FUNDS' # 退款资金来源
}
# 生成签名
sorted_params = sorted(refund_data.items())
sign_string = '&'.join([f"{key}={value}" for key, value in sorted_params if value])
sign_string += f"&key={wx_cfg.WEIXIN_SHANGHUMIYAO}"
sign_string = '&'.join([f"{key_}={value}" for key_, value in sorted_params if value])
sign_string += f"&key={key}"
sign = hashlib.md5(sign_string.encode()).hexdigest().upper()
refund_data['sign'] = sign
@@ -7651,7 +7656,7 @@ class AdTongYiTuiKuanPingTai(APIView):
weixin_refund_url,
data=xml_data.encode('utf-8'),
headers=headers,
cert=(wx_cfg.WEIXIN_CERT_PATH, wx_cfg.WEIXIN_KEY_PATH),
cert=(cert_path, key_path),
timeout=timeout
)