客服平台退款兼容商户/虚拟支付,按订单支付方式或 DIANDAN_PAY_MODE 分支。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
164
yonghu/views.py
164
yonghu/views.py
@@ -9127,10 +9127,11 @@ from dingdan.utils import (
|
||||
import hmac
|
||||
class KefuPlatformRefundView(APIView):
|
||||
"""
|
||||
虚拟支付退款接口(代币订单,使用 refund_order 接口)
|
||||
业务逻辑与原 KefuPlatformRefundView 完全一致
|
||||
微信 API:POST /xpay/refund_order
|
||||
文档:https://developers.weixin.qq.com/minigame/dev/api-backend/open-api/virtual-payment/refund_order.html
|
||||
客服平台订单退款(兼容商户支付 / 虚拟支付)
|
||||
支付方式判定优先级:
|
||||
1) 下单时缓存 diandan_pay_mode_{订单号}
|
||||
2) settings.DIANDAN_PAY_MODE(默认 merchant)
|
||||
请求:POST /yonghu/kefupttk
|
||||
"""
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
parser_classes = [JSONParser]
|
||||
@@ -9178,29 +9179,45 @@ class KefuPlatformRefundView(APIView):
|
||||
if order.zhuangtai not in allowed_statuses:
|
||||
return Response({'code': 400, 'msg': '订单状态不允许退款'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 6. 获取退款所需关键信息
|
||||
# 6. 判定本单支付方式
|
||||
from django.core.cache import cache
|
||||
pay_mode = cache.get(f'diandan_pay_mode_{dingdan_id}')
|
||||
if pay_mode not in ('merchant', 'virtual'):
|
||||
pay_mode = getattr(settings, 'DIANDAN_PAY_MODE', 'merchant')
|
||||
if pay_mode not in ('merchant', 'virtual'):
|
||||
pay_mode = 'merchant'
|
||||
|
||||
jiedan_dashou_id = order.jiedan_dashou_id
|
||||
# 从订单扩展表获取老板 openid(退款API需要)
|
||||
laoban_openid = None
|
||||
laoban_id = order.pingtai_kuozhan.laoban_id if hasattr(order, 'pingtai_kuozhan') else None
|
||||
if laoban_id:
|
||||
laoban_user = UserMain.objects.filter(yonghuid=laoban_id).first()
|
||||
if laoban_user:
|
||||
laoban_openid = getattr(laoban_user, 'openid', None)
|
||||
wechat_transaction_id = getattr(order, 'wechat_transaction_id', None)
|
||||
|
||||
if not laoban_openid:
|
||||
return Response({'code': 400, 'msg': '未找到用户openid,无法退款'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
# 7. 按支付方式调用对应退款接口
|
||||
if pay_mode == 'virtual':
|
||||
laoban_openid = None
|
||||
laoban_id = order.pingtai_kuozhan.laoban_id if hasattr(order, 'pingtai_kuozhan') else None
|
||||
if laoban_id:
|
||||
laoban_user = UserMain.objects.filter(yonghuid=laoban_id).first()
|
||||
if laoban_user:
|
||||
laoban_openid = getattr(laoban_user, 'openid', None)
|
||||
if not laoban_openid:
|
||||
return Response({'code': 400, 'msg': '未找到用户openid,无法退款'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 7. 调用虚拟支付退款接口(refund_order)
|
||||
refund_result = self.call_virtual_refund(
|
||||
dingdan_id=order.dingdan_id,
|
||||
jine=order.jine,
|
||||
openid=laoban_openid,
|
||||
wechat_transaction_id=getattr(order, 'wechat_transaction_id', None)
|
||||
)
|
||||
refund_result = self.call_virtual_refund(
|
||||
dingdan_id=order.dingdan_id,
|
||||
jine=order.jine,
|
||||
openid=laoban_openid,
|
||||
wechat_transaction_id=wechat_transaction_id
|
||||
)
|
||||
else:
|
||||
refund_result = self.call_wechat_refund(
|
||||
dingdan_id=order.dingdan_id,
|
||||
jine=order.jine,
|
||||
transaction_id=wechat_transaction_id
|
||||
)
|
||||
|
||||
if refund_result['code'] != 0:
|
||||
logger.error(f"虚拟支付退款失败,订单 {dingdan_id},原因:{refund_result['msg']}")
|
||||
logger.error(
|
||||
f"平台退款失败,订单 {dingdan_id},pay_mode={pay_mode},原因:{refund_result['msg']}"
|
||||
)
|
||||
return Response({'code': 400, 'msg': refund_result['msg']}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# ========== 以下数据库操作与原逻辑完全一致(全部保留) ==========
|
||||
@@ -9225,6 +9242,9 @@ class KefuPlatformRefundView(APIView):
|
||||
|
||||
# 10. 更新退款记录表
|
||||
try:
|
||||
refund_beizhu = (
|
||||
'客服平台退款(虚拟支付)' if pay_mode == 'virtual' else '客服平台退款(商户支付)'
|
||||
)
|
||||
refund_record, created = Tuikuanjilu.objects.get_or_create(
|
||||
dingdan_id=dingdan_id,
|
||||
defaults={
|
||||
@@ -9236,13 +9256,14 @@ class KefuPlatformRefundView(APIView):
|
||||
'jine': order.jine,
|
||||
'dashou_fencheng': order.dashou_fencheng or 0,
|
||||
'jieshao': order.jieshao or '',
|
||||
'beizhu': '客服平台退款(虚拟支付)',
|
||||
'beizhu': refund_beizhu,
|
||||
'nicheng': order.nicheng or '',
|
||||
}
|
||||
)
|
||||
if not created:
|
||||
refund_record.sqzhuangtai = 1
|
||||
refund_record.chuliid = phone
|
||||
refund_record.beizhu = refund_beizhu
|
||||
if tuikuan_liyou:
|
||||
refund_record.liyou = tuikuan_liyou
|
||||
refund_record.save()
|
||||
@@ -9304,23 +9325,106 @@ class KefuPlatformRefundView(APIView):
|
||||
except Exception as e:
|
||||
logger.error(f"打手每日统计更新失败: {e}")
|
||||
|
||||
logger.info(f"虚拟支付退款成功,订单 {dingdan_id},退款单号 {refund_result.get('refund_id')}")
|
||||
logger.info(
|
||||
f"平台退款成功,订单 {dingdan_id},pay_mode={pay_mode},退款单号 {refund_result.get('refund_id')}"
|
||||
)
|
||||
return Response({'code': 0, 'msg': '退款成功'})
|
||||
|
||||
except Dingdan.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
except Exception as e:
|
||||
logger.error(f"虚拟支付退款接口异常: {str(e)}", exc_info=True)
|
||||
logger.error(f"平台退款接口异常: {str(e)}", exc_info=True)
|
||||
return Response({'code': 500, 'msg': '服务器内部错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
# ==================== 核心退款方法 ====================
|
||||
# ==================== 商户微信支付退款 ====================
|
||||
def call_wechat_refund(self, dingdan_id, jine, transaction_id=None):
|
||||
"""
|
||||
调用微信支付退款接口 secapi/pay/refund
|
||||
:return: {'code': 0, 'msg': '成功', 'refund_id': 'xxx'} 或 {'code': 非0, 'msg': '错误信息'}
|
||||
"""
|
||||
appid = wx_cfg.WEIXIN_APPID
|
||||
mch_id = wx_cfg.WEIXIN_MCHID
|
||||
key = wx_cfg.WEIXIN_SHANGHUMIYAO
|
||||
cert_path = wx_cfg.WEIXIN_CERT_PATH
|
||||
key_path = wx_cfg.WEIXIN_KEY_PATH
|
||||
|
||||
if not all([appid, mch_id, key, cert_path, key_path]):
|
||||
missing = [
|
||||
k for k, v in {
|
||||
'appid': appid, 'mch_id': mch_id, 'key': key,
|
||||
'cert_path': cert_path, 'key_path': key_path
|
||||
}.items() if not v
|
||||
]
|
||||
logger.error(f"微信支付配置缺失: {missing}")
|
||||
return {'code': 500, 'msg': f'系统支付配置缺失: {missing}'}
|
||||
|
||||
out_refund_no = self.generate_refund_no(dingdan_id)
|
||||
total_fee = int(float(jine) * 100)
|
||||
refund_fee = total_fee
|
||||
|
||||
params = {
|
||||
'appid': appid,
|
||||
'mch_id': mch_id,
|
||||
'nonce_str': self.generate_nonce_str(),
|
||||
'out_refund_no': out_refund_no,
|
||||
'total_fee': total_fee,
|
||||
'refund_fee': refund_fee,
|
||||
'refund_desc': '客服退款',
|
||||
}
|
||||
if transaction_id:
|
||||
params['transaction_id'] = transaction_id
|
||||
else:
|
||||
params['out_trade_no'] = dingdan_id
|
||||
|
||||
sorted_keys = sorted(params.keys())
|
||||
stringA = '&'.join([f"{k}={params[k]}" for k in sorted_keys])
|
||||
stringSignTemp = f"{stringA}&key={key}"
|
||||
sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
|
||||
params['sign'] = sign
|
||||
|
||||
xml_data = self.dict_to_xml(params)
|
||||
url = 'https://api.mch.weixin.qq.com/secapi/pay/refund'
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
url,
|
||||
data=xml_data,
|
||||
cert=(cert_path, key_path),
|
||||
timeout=10
|
||||
)
|
||||
result = xmltodict.parse(response.content)['xml']
|
||||
logger.info(f"微信商户退款响应: {result}")
|
||||
except Exception as e:
|
||||
logger.error(f"微信商户退款请求异常: {str(e)}", exc_info=True)
|
||||
return {'code': 500, 'msg': '微信退款请求异常'}
|
||||
|
||||
if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
|
||||
return {'code': 0, 'msg': 'success', 'refund_id': out_refund_no}
|
||||
|
||||
err_msg = result.get('err_code_des', result.get('return_msg', '未知错误'))
|
||||
logger.warning(f"微信商户退款失败: {err_msg}")
|
||||
return {'code': 400, 'msg': err_msg}
|
||||
|
||||
def generate_nonce_str(self):
|
||||
import string
|
||||
return ''.join(random.choices(string.ascii_letters + string.digits, k=32))
|
||||
|
||||
def generate_refund_no(self, dingdan_id):
|
||||
timestamp = str(int(time.time()))
|
||||
rand = ''.join(random.choices('0123456789', k=4))
|
||||
return f"{dingdan_id}REF{timestamp}{rand}"
|
||||
|
||||
def dict_to_xml(self, data):
|
||||
xml = ['<xml>']
|
||||
for k, v in data.items():
|
||||
xml.append(f'<{k}>{v}</{k}>')
|
||||
xml.append('</xml>')
|
||||
return ''.join(xml)
|
||||
|
||||
# ==================== 虚拟支付退款 ====================
|
||||
def call_virtual_refund(self, dingdan_id, jine, openid, wechat_transaction_id=None):
|
||||
"""
|
||||
调用虚拟支付退款接口 /xpay/refund_order
|
||||
:param dingdan_id: 商户订单号(order_id)
|
||||
:param jine: 退款金额(元)
|
||||
:param openid: 支付用户的 openid
|
||||
:param wechat_transaction_id: 微信支付交易单号(wx_order_id),若有则优先使用
|
||||
:return: {'code': 0, 'msg': '成功', 'refund_id': 'xxx'} 或 {'code': 非0, 'msg': '错误信息'}
|
||||
"""
|
||||
uri = "/xpay/refund_order"
|
||||
|
||||
Reference in New Issue
Block a user