123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- # coding=utf-8
- import uuid
- import requests
- import json
- import xmltodict
- import time
- from hashlib import md5
- from django.conf import settings
- from util.splitaccount_tool import SplitAccountTool
- from util.exceptions import CustomError
- from apps.foundation.models import BizLog
- # 微信支付sign_type
- WEIXIN_SIGN_TYPE = 'MD5'
- # 服务器IP地址
- WEIXIN_SPBILL_CREATE_IP = '139.9.148.181'
- # 微信支付用途
- WEIXIN_BODY = u'小程序支付'
- # 微信统一下单URL
- WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
- # 微信查询订单URL
- WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
- # 微信支付回调API
- WEIXIN_CALLBACK_API = 'https://lsr.zzly.vip/api/wechat_notify/'
- class SplitAccountFuc(object):
- '''直连服务商 分账 文档https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml'''
- def __init__(self,appid, mchid, cert_serial_no, apiv3_key, proxy=None):
- self._appid = appid
- self._mchid = mchid
- self._core = SplitAccountTool(appid, mchid, cert_serial_no, apiv3_key, proxy=proxy)
- def splitaccount_order(self, transaction_id, out_order_no, receivers):
- '''
- 请求分账 微信订单支付成功后,服务商代特约商户发起分账请求,将结算后的钱分到分账接收方
- 注意:对同一笔订单最多能发起50次分账请求,每次请求最多分给50个接收方
- 此接口采用异步处理模式,即在接收到商户请求后,优先受理请求再异步处理,最终的分账结果可以通过查询分账接口获取
- 请求分账里边的openid 是需要先调用添加分账接收方接口添加分账关系
- '''
- path = "/v3/profitsharing/orders"
- params = {
- 'appid': self._appid,
- 'transaction_id': transaction_id, # 微信支付订单号
- 'out_order_no': out_order_no, # 商户系统内部的分账单号,在商户系统内部唯一,同一分账单号多次请求等同一次。只能是数字、大小写字母_-|*@
- 'receivers': [],
- 'unfreeze_unsplit': True # 是否解冻剩余未分金额 如果只分一次就填true
- }
- for item in receivers:
- receiver_item = {
- 'type': 'PERSONAL_OPENID',
- 'account': item['account'],
- 'amount': int(round(item['amount'], 0)), # 单位为分 只能为整数 不能超过原订单支付金额及最大分账比例金额
- 'description': item['description']
- }
- params['receivers'].append(receiver_item)
- code, message = self._core.request(path, SplitAccountTool.POST, data=params)
- result = json.loads(message)
- if code != 200:
- raise CustomError(u'[{}]分账失败!原因:{}'.format(out_order_no, result))
- return result
- def splitaccount_addreceiver(self, account):
- '''添加分账接收方'''
- path = "/v3/profitsharing/receivers/add"
- params = {
- 'appid': self._appid,
- 'type': "PERSONAL_OPENID",
- 'account': account, # 接收人的openid
- 'relation_type': "USER", # body子商户与接收方的关系
- }
- code, message = self._core.request(path, SplitAccountTool.POST, data=params)
- result = json.loads(message)
- if code != 200:
- raise CustomError(u'[{}]添加分账接收方失败!原因:{}'.format(account, result))
- def splitaccount_deletereceiver(self, account):
- '''删除分账接收方'''
- path = "/v3/profitsharing/receivers/delete"
- params = {
- 'appid': self._appid,
- 'type': "PERSONAL_OPENID",
- 'account': account, # 接收人的openid
- }
- code, message = self._core.request(path, SplitAccountTool.POST, data=params)
- result = json.loads(message)
- if code != 200:
- raise CustomError(u'[{}]删除分账接收方失败!原因:{}'.format(account, result))
- def splitaccount_orderquery(self, transaction_id, out_order_no):
- '''
- 查询分账结果
- transaction_id 微信支付订单号
- out_order_no 商户分账单号
- '''
- if transaction_id and out_order_no:
- path = '/v3/profitsharing/orders/%s?transaction_id=%s' % (out_order_no, transaction_id)
- else:
- raise CustomError(u'[%s]查询分账结果失败!原因:参数错误!' % out_order_no)
- code, message = self._core.request(path)
- result = json.loads(message)
- if code != 200:
- raise CustomError(u'[{}]查询分账结果失败!原因:{}'.format(out_order_no, result))
- return result
- def splitaccount_return(self):
- '''请求分账回退'''
- pass
- def splitaccount_returnquery(self):
- '''查询分账回退结果'''
- pass
- def splitaccount_unfreeze(self, transaction_id, out_order_no):
- '''解冻剩余资金'''
- path = "/v3/profitsharing/orders"
- params = {
- 'transaction_id': transaction_id, # 微信支付订单号
- 'out_order_no': out_order_no, # 商户系统内部的分账单号,在商户系统内部唯一,同一分账单号多次请求等同一次。只能是数字、大小写字母_-|*@
- 'description': "解冻资金"
- }
- code, message = self._core.request(path, SplitAccountTool.POST, data=params)
- result = json.loads(message)
- if code != 200:
- raise CustomError(u'[{}]解冻剩余资金失败!原因:{}'.format(out_order_no, result))
- return result
- def splitaccount_amountquery(self):
- '''查询剩余待分金额'''
- pass
- def splitaccount_configquery(self):
- '''查询最大分账比例'''
- pass
- def splitaccount_bill(self):
- '''申请分账账单'''
- pass
- class WeChatResponse():
- def __init__(self,appid, agent_num, agent_key):
- self.params = {
- "appid": appid,
- 'mch_id': agent_num,
- 'nonce_str': '',
- 'sign_type': WEIXIN_SIGN_TYPE,
- 'sign': '',
- 'out_trade_no': '',
- }
- self.prepay_id = None
- self.merchant_key = agent_key
- # 查询订单
- def orderquery(self, out_trade_no):
- self.params['out_trade_no'] = out_trade_no
- self.params['nonce_str'] = generate_nonce_str()
- self.params['sign'] = generate_sign(self.params, self.merchant_key)
- data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
- headers = {'Content-Type': 'application/xml'}
- res = requests.post(WEIXIN_QUERY_ORDER_URL, data=data, headers=headers)
- if res.status_code != 200:
- raise CustomError(u'微信请求失败!')
- result = json.loads(json.dumps(xmltodict.parse(res.content)))
- if result['xml']['return_code'] != 'SUCCESS':
- raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
- print(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
- if result['xml']['trade_state'] == 'NOTPAY':
- return result['xml']['total_fee']
- # raise CustomError(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
- # 其他状态,返回金额0
- return 0
- # return result['xml']['total_fee']
- class WechatPay():
- def __init__(self, appid, mch_id, merchant_key):
- self.params = {
- 'appid': appid,
- 'mch_id': mch_id,
- 'nonce_str': '',
- 'sign_type': WEIXIN_SIGN_TYPE,
- 'body': WEIXIN_BODY,
- 'out_trade_no': '',
- 'total_fee': '',
- 'spbill_create_ip': WEIXIN_SPBILL_CREATE_IP,
- 'notify_url': WEIXIN_CALLBACK_API + appid + '/',
- 'trade_type': 'JSAPI'
- }
- self.prepay_id = None
- self.merchant_key = merchant_key
- def getAppString(self):
- data = {
- 'appId': self.params['appid'],
- 'signType': WEIXIN_SIGN_TYPE,
- 'package': "prepay_id={}".format(self.prepay_id),
- 'nonceStr': generate_nonce_str(),
- 'timeStamp': str(int(time.time()))
- }
- data['paySign'] = generate_sign(data, self.merchant_key)
- data.pop('appId')
- return data
- def unifiedOrder(self,out_trade_no,total_fee, openid, profit_sharing):
- self.params['profit_sharing'] = profit_sharing # 是否分账参数 Y 需要分账 N 不分账 字母大写默认不分账
- self.params['out_trade_no'] = out_trade_no
- self.params['total_fee'] = int(round(total_fee, 0))
- self.params['openid'] = openid
- self.params['nonce_str'] = generate_nonce_str()
- self.params['sign'] = generate_sign(self.params, self.merchant_key)
- data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
- headers = {'Content-Type': 'application/xml'}
- res = requests.post(WEIXIN_UNIFIED_ORDER_URL, data=data, headers=headers)
- if res.status_code != 200:
- raise CustomError(u'微信请求失败!')
- result = json.loads(json.dumps(xmltodict.parse(res.content)))
- if result['xml']['return_code'] != 'SUCCESS':
- raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
- if result['xml']['result_code'] != 'SUCCESS':
- raise CustomError(u'微信交易失败![%s:%s]' % (result['xml']['err_code'],result['xml']['err_code_des']))
- self.prepay_id = result['xml']['prepay_id']
- return result['xml']
- class WechatPayNotify():
- def __init__(self,params, merchant_key):
- self.params = params
- self.merchant_key = merchant_key
- def handle(self):
- resp_dict = json.loads(json.dumps(xmltodict.parse(self.params)))['xml']
- return_code = resp_dict['return_code']
- if return_code != 'SUCCESS':
- return None
- if not validate_sign(resp_dict, self.merchant_key):
- return None
- return resp_dict
- @staticmethod
- def response_ok():
- return_info = {
- 'return_code': 'SUCCESS',
- 'return_msg': 'OK'
- }
- return generate_response_data(return_info)
- @staticmethod
- def response_fail():
- return_info = {
- 'return_code': 'FAIL',
- 'return_msg': 'FAIL'
- }
- return generate_response_data(return_info)
- def generate_nonce_str():
- """
- 生成随机字符串
- """
- return str(uuid.uuid4()).replace('-', '')
- def generate_sign(params, merchant_key):
- """
- 生成md5签名的参数
- """
- if 'sign' in params:
- params.pop('sign')
- src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % merchant_key
- return md5(src.encode('utf-8')).hexdigest().upper()
- def validate_sign(resp_dict, merchant_key):
- """
- 验证微信返回的签名
- """
- if 'sign' not in resp_dict:
- return False
- wx_sign = resp_dict['sign']
- sign = generate_sign(resp_dict, merchant_key)
- if sign == wx_sign:
- return True
- return False
- def generate_response_data(resp_dict):
- """
- 字典转xml
- """
- return xmltodict.unparse({'xml': resp_dict}, pretty=True, full_document=False).encode('utf-8')
|