WeChatResponse.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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 utils.exceptions import CustomError
  10. # 微信支付APP_ID
  11. WEIXIN_APP_ID = settings.WECHAT['appid']
  12. # 微信支付MCH_ID 【登录账号】
  13. WEIXIN_MCH_ID = settings.WECHAT['mchid']
  14. # 微信支付sign_type
  15. WEIXIN_SIGN_TYPE = 'MD5'
  16. # 服务器IP地址
  17. WEIXIN_SPBILL_CREATE_IP = settings.WECHAT['spbill_create_ip']
  18. # 微信支付用途
  19. WEIXIN_BODY = u'燎原云报修'
  20. # 微信KEY值 【API密钥】
  21. WEIXIN_KEY = settings.WECHAT['merchant_key']
  22. # 微信统一下单URL
  23. WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
  24. # 微信查询订单URL
  25. WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
  26. # 微信支付回调API
  27. WEIXIN_CALLBACK_API = settings.WECHAT['notify_url']
  28. class WeChatResponse():
  29. def __init__(self):
  30. self.params = {
  31. 'appid': WEIXIN_APP_ID,
  32. 'mch_id': WEIXIN_MCH_ID,
  33. 'nonce_str': '',
  34. 'sign_type': WEIXIN_SIGN_TYPE,
  35. 'sign': '',
  36. 'out_trade_no': '',
  37. }
  38. self.prepay_id = None
  39. def getAppString(self):
  40. data = {
  41. 'appid': self.params['appid'],
  42. 'partnerid': self.params['mch_id'],
  43. 'prepayid': self.prepay_id,
  44. 'package': "prepay_id={}".format(self.prepay_id),
  45. 'noncestr': generate_nonce_str(),
  46. 'timestamp': str(int(time.time()))
  47. }
  48. data['sign'] = generate_sign(data)
  49. return data
  50. # 查询订单
  51. def orderquery(self, out_trade_no):
  52. self.params['out_trade_no'] = out_trade_no
  53. self.params['nonce_str'] = generate_nonce_str()
  54. self.params['sign'] = generate_sign(self.params)
  55. data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
  56. headers = {'Content-Type': 'application/xml'}
  57. res = requests.post(WEIXIN_QUERY_ORDER_URL, data=data, headers=headers)
  58. if res.status_code != 200:
  59. raise CustomError(u'微信请求失败!')
  60. result = json.loads(json.dumps(xmltodict.parse(res.content)))
  61. if result['xml']['return_code'] != 'SUCCESS':
  62. raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
  63. if result['xml']['trade_state'] != 'SUCCESS':
  64. raise CustomError(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
  65. return result['xml']['total_fee']
  66. def unifiedOrder(self, out_trade_no, total_fee):
  67. self.params['out_trade_no'] = out_trade_no
  68. self.params['total_fee'] = int(round(total_fee * 100, 0))
  69. self.params['nonce_str'] = generate_nonce_str()
  70. self.params['body'] = WEIXIN_BODY
  71. self.params['spbill_create_ip'] = WEIXIN_SPBILL_CREATE_IP
  72. self.params['notify_url'] = WEIXIN_CALLBACK_API
  73. self.params['trade_type'] = 'NATIVE'
  74. self.params['sign'] = generate_sign(self.params)
  75. data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
  76. headers = {'Content-Type': 'application/xml'}
  77. res = requests.post(WEIXIN_UNIFIED_ORDER_URL, data=data, headers=headers)
  78. if res.status_code != 200:
  79. raise CustomError(u'微信请求失败!')
  80. result = json.loads(json.dumps(xmltodict.parse(res.content)))
  81. if result['xml']['return_code'] != 'SUCCESS':
  82. raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
  83. if result['xml']['result_code'] != 'SUCCESS':
  84. raise CustomError(u'微信交易失败![%s:%s]' % (result['xml']['err_code'], result['xml']['err_code_des']))
  85. self.prepay_id = result['xml']['prepay_id']
  86. return result['xml']['code_url']
  87. class WechatPayNotify():
  88. def __init__(self, params):
  89. self.params = params
  90. def handle(self):
  91. resp_dict = json.loads(json.dumps(xmltodict.parse(self.params)))['xml']
  92. return_code = resp_dict['return_code']
  93. if return_code != 'SUCCESS':
  94. return None
  95. if not validate_sign(resp_dict):
  96. return None
  97. return resp_dict
  98. @staticmethod
  99. def response_ok():
  100. return_info = {
  101. 'return_code': 'SUCCESS',
  102. 'return_msg': 'OK'
  103. }
  104. return generate_response_data(return_info)
  105. @staticmethod
  106. def response_fail():
  107. return_info = {
  108. 'return_code': 'FAIL',
  109. 'return_msg': 'FAIL'
  110. }
  111. return generate_response_data(return_info)
  112. def generate_nonce_str():
  113. """
  114. 生成随机字符串
  115. """
  116. return str(uuid.uuid4()).replace('-', '')
  117. def generate_sign(params):
  118. """
  119. 生成md5签名的参数
  120. """
  121. if 'sign' in params:
  122. params.pop('sign')
  123. src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % WEIXIN_KEY
  124. return md5(src.encode('utf-8')).hexdigest().upper()
  125. def validate_sign(resp_dict):
  126. """
  127. 验证微信返回的签名
  128. """
  129. if 'sign' not in resp_dict:
  130. return False
  131. wx_sign = resp_dict['sign']
  132. sign = generate_sign(resp_dict)
  133. if sign == wx_sign:
  134. return True
  135. return False
  136. def generate_response_data(resp_dict):
  137. """
  138. 字典转xml
  139. """
  140. return xmltodict.unparse({'xml': resp_dict}, pretty=True, full_document=False).encode('utf-8')