|
@@ -0,0 +1,228 @@
|
|
|
+# coding=utf-8
|
|
|
+
|
|
|
+import uuid
|
|
|
+import requests
|
|
|
+import json
|
|
|
+import xmltodict
|
|
|
+import time
|
|
|
+from hashlib import md5
|
|
|
+from django.conf import settings
|
|
|
+
|
|
|
+from utils.exceptions import CustomError
|
|
|
+
|
|
|
+# 微信支付APP_ID
|
|
|
+WEIXIN_APP_ID = settings.WECHAT['appid']
|
|
|
+# 微信支付MCH_ID 【登录账号】
|
|
|
+WEIXIN_MCH_ID = settings.WECHAT['mchid']
|
|
|
+# 微信支付sign_type
|
|
|
+WEIXIN_SIGN_TYPE = 'MD5'
|
|
|
+# 服务器IP地址
|
|
|
+WEIXIN_SPBILL_CREATE_IP = settings.WECHAT['spbill_create_ip']
|
|
|
+# 微信支付用途
|
|
|
+WEIXIN_BODY = settings.PAY_SUBJECT
|
|
|
+# 微信KEY值 【API密钥】
|
|
|
+WEIXIN_KEY = settings.WECHAT['merchant_key']
|
|
|
+# 微信统一下单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 = settings.WECHAT['notify_url']
|
|
|
+
|
|
|
+# pc端支付
|
|
|
+class WeChatResponse():
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self.params = {
|
|
|
+ 'appid': WEIXIN_APP_ID,
|
|
|
+ 'mch_id': WEIXIN_MCH_ID,
|
|
|
+ 'nonce_str': '',
|
|
|
+ 'sign_type': WEIXIN_SIGN_TYPE,
|
|
|
+ 'sign': '',
|
|
|
+ 'out_trade_no': '',
|
|
|
+ }
|
|
|
+ self.prepay_id = None
|
|
|
+
|
|
|
+ def getAppString(self):
|
|
|
+ data = {
|
|
|
+ 'appid': self.params['appid'],
|
|
|
+ 'partnerid': self.params['mch_id'],
|
|
|
+ 'prepayid': self.prepay_id,
|
|
|
+ 'package': "prepay_id={}".format(self.prepay_id),
|
|
|
+ 'noncestr': generate_nonce_str(),
|
|
|
+ 'timestamp': str(int(time.time()))
|
|
|
+ }
|
|
|
+ data['sign'] = generate_sign(data)
|
|
|
+ return data
|
|
|
+
|
|
|
+ # 查询订单
|
|
|
+ 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)
|
|
|
+ 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'])
|
|
|
+ if result['xml']['trade_state'] != 'SUCCESS':
|
|
|
+ raise CustomError(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
|
|
|
+
|
|
|
+ return result['xml']['total_fee']
|
|
|
+
|
|
|
+ def unifiedOrder(self, out_trade_no, total_fee):
|
|
|
+ self.params['out_trade_no'] = out_trade_no
|
|
|
+ self.params['total_fee'] = int(round(total_fee * 100, 0))
|
|
|
+ self.params['nonce_str'] = generate_nonce_str()
|
|
|
+ self.params['body'] = WEIXIN_BODY
|
|
|
+ self.params['spbill_create_ip'] = WEIXIN_SPBILL_CREATE_IP
|
|
|
+ self.params['notify_url'] = WEIXIN_CALLBACK_API
|
|
|
+ self.params['trade_type'] = 'NATIVE'
|
|
|
+ self.params['sign'] = generate_sign(self.params)
|
|
|
+ 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']['code_url']
|
|
|
+
|
|
|
+#小程序支付
|
|
|
+class WechatAppletPay():
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self.params = {
|
|
|
+ "appid": settings.WEAPP['appid'],
|
|
|
+ "body": WEIXIN_BODY,
|
|
|
+ "mch_id": WEIXIN_MCH_ID,
|
|
|
+ "nonce_str": '',
|
|
|
+ "notify_url": WEIXIN_CALLBACK_API,
|
|
|
+ "openid": '', # 获取小程序openid
|
|
|
+ "out_trade_no": '',
|
|
|
+ "spbill_create_ip": WEIXIN_SPBILL_CREATE_IP,
|
|
|
+ "total_fee": '',
|
|
|
+ "trade_type": 'JSAPI',
|
|
|
+ "sign":''
|
|
|
+ }
|
|
|
+ self.prepay_id = None
|
|
|
+
|
|
|
+ def weChatUnifiedOrder(self, openid, out_trade_no, total_fee):
|
|
|
+ self.params['out_trade_no'] = out_trade_no
|
|
|
+ self.params['total_fee'] = int(round(float(total_fee) * 100, 0))
|
|
|
+ self.params['nonce_str'] = generate_nonce_str()
|
|
|
+ self.params['openid'] = openid
|
|
|
+ self.params['sign'] = generate_sign(self.params)
|
|
|
+ # 拿到封装好的xml数据
|
|
|
+ xml_data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
|
|
|
+ # 请求微信统一下单URL
|
|
|
+ headers = {'Content-Type': 'application/xml'}
|
|
|
+ res = requests.post(WEIXIN_UNIFIED_ORDER_URL, data=xml_data, headers=headers)
|
|
|
+
|
|
|
+ if res.status_code != 200:
|
|
|
+ raise CustomError(u'微信请求失败!')
|
|
|
+
|
|
|
+ # 回复数据为xml,将其转为字典
|
|
|
+ content = json.loads(json.dumps(xmltodict.parse(res.content)))
|
|
|
+
|
|
|
+ if content['xml']['return_code'] != 'SUCCESS':
|
|
|
+ raise CustomError(u'微信通信失败![%s]' % content['xml']['return_msg'])
|
|
|
+ if content['xml']['result_code'] != 'SUCCESS':
|
|
|
+ raise CustomError(u'微信交易失败![%s:%s]' % (content['xml']['err_code'],content['xml']['err_code_des']))
|
|
|
+
|
|
|
+ self.prepay_id = content['xml']['prepay_id']
|
|
|
+
|
|
|
+ return self.getPaymentInfo()
|
|
|
+
|
|
|
+ def getPaymentInfo(self):
|
|
|
+ data = {
|
|
|
+ 'appId': self.params['appid'],
|
|
|
+ 'package': "prepay_id={}".format(self.prepay_id),
|
|
|
+ 'nonceStr': generate_nonce_str(),
|
|
|
+ 'timeStamp': str(int(time.time())),
|
|
|
+ 'signType':'MD5'
|
|
|
+ }
|
|
|
+ data['paySign'] = generate_sign(data)
|
|
|
+ return data
|
|
|
+
|
|
|
+class WechatPayNotify():
|
|
|
+ def __init__(self, params):
|
|
|
+ self.params = params
|
|
|
+
|
|
|
+ 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):
|
|
|
+ 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):
|
|
|
+ """
|
|
|
+ 生成md5签名的参数
|
|
|
+ """
|
|
|
+ if 'sign' in params:
|
|
|
+ params.pop('sign')
|
|
|
+ src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % WEIXIN_KEY
|
|
|
+ return md5(src.encode('utf-8')).hexdigest().upper()
|
|
|
+
|
|
|
+
|
|
|
+def validate_sign(resp_dict):
|
|
|
+ """
|
|
|
+ 验证微信返回的签名
|
|
|
+ """
|
|
|
+ if 'sign' not in resp_dict:
|
|
|
+ return False
|
|
|
+ wx_sign = resp_dict['sign']
|
|
|
+ sign = generate_sign(resp_dict)
|
|
|
+ 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')
|