WeChatResponse.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. # coding=utf-8
  2. import uuid
  3. import requests
  4. import json
  5. import xmltodict
  6. import time
  7. from hashlib import md5
  8. from django.conf import settings
  9. from apps.WechatApplet.models import WechatApplet
  10. from utils.exceptions import CustomError
  11. # 微信支付sign_type
  12. WEIXIN_SIGN_TYPE = 'MD5'
  13. # 服务器IP地址
  14. WEIXIN_SPBILL_CREATE_IP = '39.106.109.89'
  15. # 微信支付用途
  16. # 微信KEY值 【API密钥】
  17. # WEIXIN_KEY = settings.WECHAT['merchant_key']
  18. # 微信统一下单URL
  19. WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
  20. # 微信查询订单URL
  21. WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
  22. # 微信支付回调API
  23. WEIXIN_CALLBACK_API = 'https://print.zzliaoyuan.com/api/wechat_notify/'
  24. # pc端支付
  25. class WeChatResponse():
  26. def __init__(self,appid, agent_id=None):
  27. applet = getAPPID(appid, agent_id)
  28. self.params = {
  29. "appid": appid,
  30. 'mch_id': applet.agent_num,
  31. 'nonce_str': '',
  32. 'sign_type': WEIXIN_SIGN_TYPE,
  33. 'sign': '',
  34. 'out_trade_no': '',
  35. }
  36. self.prepay_id = None
  37. self.merchant_key = applet.agent_key
  38. # 查询订单
  39. def orderquery(self, out_trade_no):
  40. self.params['out_trade_no'] = out_trade_no
  41. self.params['nonce_str'] = generate_nonce_str()
  42. self.params['sign'] = generate_sign(self.params, self.merchant_key)
  43. data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
  44. headers = {'Content-Type': 'application/xml'}
  45. res = requests.post(WEIXIN_QUERY_ORDER_URL, data=data, headers=headers)
  46. if res.status_code != 200:
  47. raise CustomError(u'微信请求失败!')
  48. result = json.loads(json.dumps(xmltodict.parse(res.content)))
  49. if result['xml']['return_code'] != 'SUCCESS':
  50. raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
  51. print(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
  52. if result['xml']['trade_state'] == 'NOTPAY':
  53. return result['xml']['total_fee']
  54. # raise CustomError(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
  55. # 其他状态,返回金额0
  56. return 0
  57. # return result['xml']['total_fee']
  58. def getAPPID(appid, agent_id):
  59. applet = WechatApplet.objects.filter(authorizer_appid=appid, is_authorize=True)
  60. if agent_id:
  61. applet = applet.filter(agent_id=agent_id)
  62. if applet:
  63. applet = applet.first()
  64. else:
  65. raise CustomError('小程序appid认证失败!')
  66. return applet
  67. #小程序支付
  68. class WechatAppletPay():
  69. def __init__(self, appid, agent_id=None):
  70. applet = getAPPID(appid, agent_id)
  71. self.params = {
  72. "appid": appid,
  73. "body": applet.nick_name,
  74. "mch_id": applet.agent_num,
  75. "nonce_str": '',
  76. "notify_url": WEIXIN_CALLBACK_API + appid + '/',
  77. "openid": '', # 获取小程序openid
  78. "out_trade_no": '',
  79. "spbill_create_ip": WEIXIN_SPBILL_CREATE_IP,
  80. "total_fee": '',
  81. "trade_type": 'JSAPI',
  82. "sign":''
  83. }
  84. self.prepay_id = None
  85. self.merchant_key = applet.agent_key
  86. def weChatUnifiedOrder(self, openid, out_trade_no, total_fee):
  87. self.params['out_trade_no'] = out_trade_no
  88. self.params['total_fee'] = int(round(float(total_fee) * 100, 0))
  89. self.params['nonce_str'] = generate_nonce_str()
  90. self.params['openid'] = openid
  91. self.params['sign'] = generate_sign(self.params,self.merchant_key)
  92. # 拿到封装好的xml数据
  93. xml_data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
  94. # 请求微信统一下单URL
  95. headers = {'Content-Type': 'application/xml'}
  96. res = requests.post(WEIXIN_UNIFIED_ORDER_URL, data=xml_data, headers=headers)
  97. if res.status_code != 200:
  98. raise CustomError(u'微信请求失败!')
  99. # 回复数据为xml,将其转为字典
  100. content = json.loads(json.dumps(xmltodict.parse(res.content)))
  101. if content['xml']['return_code'] != 'SUCCESS':
  102. raise CustomError(u'微信通信失败![%s]' % content['xml']['return_msg'])
  103. if content['xml']['result_code'] != 'SUCCESS':
  104. raise CustomError(u'微信交易失败![%s:%s]' % (content['xml']['err_code'],content['xml']['err_code_des']))
  105. self.prepay_id = content['xml']['prepay_id']
  106. return self.getPaymentInfo()
  107. def getPaymentInfo(self):
  108. data = {
  109. 'appId': self.params['appid'],
  110. 'package': "prepay_id={}".format(self.prepay_id),
  111. 'nonceStr': generate_nonce_str(),
  112. 'timeStamp': str(int(time.time())),
  113. 'signType':'MD5'
  114. }
  115. data['paySign'] = generate_sign(data,self.merchant_key)
  116. return data
  117. class WechatPayNotify():
  118. def __init__(self, params):
  119. self.params = params
  120. def handle(self):
  121. resp_dict = json.loads(json.dumps(xmltodict.parse(self.params)))['xml']
  122. return_code = resp_dict['return_code']
  123. if return_code != 'SUCCESS':
  124. return None
  125. if not validate_sign(resp_dict):
  126. return None
  127. return resp_dict
  128. @staticmethod
  129. def response_ok():
  130. return_info = {
  131. 'return_code': 'SUCCESS',
  132. 'return_msg': 'OK'
  133. }
  134. return generate_response_data(return_info)
  135. @staticmethod
  136. def response_fail():
  137. return_info = {
  138. 'return_code': 'FAIL',
  139. 'return_msg': 'FAIL'
  140. }
  141. return generate_response_data(return_info)
  142. def generate_nonce_str():
  143. """
  144. 生成随机字符串
  145. """
  146. return str(uuid.uuid4()).replace('-', '')
  147. def generate_sign(params,merchant_key):
  148. """
  149. 生成md5签名的参数
  150. """
  151. if 'sign' in params:
  152. params.pop('sign')
  153. src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % merchant_key
  154. return md5(src.encode('utf-8')).hexdigest().upper()
  155. def validate_sign(resp_dict):
  156. """
  157. 验证微信返回的签名
  158. """
  159. if 'sign' not in resp_dict:
  160. return False
  161. wx_sign = resp_dict['sign']
  162. sign = generate_sign(resp_dict, '')
  163. if sign == wx_sign:
  164. return True
  165. return False
  166. def generate_response_data(resp_dict):
  167. """
  168. 字典转xml
  169. """
  170. return xmltodict.unparse({'xml': resp_dict}, pretty=True, full_document=False).encode('utf-8')