WeChatResponse.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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 = settings.PAY_SUBJECT
  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. # pc端支付
  29. class WeChatResponse():
  30. def __init__(self):
  31. self.params = {
  32. 'appid': WEIXIN_APP_ID,
  33. 'mch_id': WEIXIN_MCH_ID,
  34. 'nonce_str': '',
  35. 'sign_type': WEIXIN_SIGN_TYPE,
  36. 'sign': '',
  37. 'out_trade_no': '',
  38. }
  39. self.prepay_id = None
  40. def getAppString(self):
  41. data = {
  42. 'appid': self.params['appid'],
  43. 'partnerid': self.params['mch_id'],
  44. 'prepayid': self.prepay_id,
  45. 'package': "prepay_id={}".format(self.prepay_id),
  46. 'noncestr': generate_nonce_str(),
  47. 'timestamp': str(int(time.time()))
  48. }
  49. data['sign'] = generate_sign(data)
  50. return data
  51. # 查询订单
  52. def orderquery(self, out_trade_no):
  53. self.params['out_trade_no'] = out_trade_no
  54. self.params['nonce_str'] = generate_nonce_str()
  55. self.params['sign'] = generate_sign(self.params)
  56. data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
  57. headers = {'Content-Type': 'application/xml'}
  58. res = requests.post(WEIXIN_QUERY_ORDER_URL, data=data, headers=headers)
  59. if res.status_code != 200:
  60. raise CustomError(u'微信请求失败!')
  61. result = json.loads(json.dumps(xmltodict.parse(res.content)))
  62. if result['xml']['return_code'] != 'SUCCESS':
  63. raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
  64. if result['xml']['trade_state'] != 'SUCCESS':
  65. raise CustomError(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
  66. return result['xml']['total_fee']
  67. def unifiedOrder(self, out_trade_no, total_fee):
  68. self.params['out_trade_no'] = out_trade_no
  69. self.params['total_fee'] = int(round(total_fee * 100, 0))
  70. self.params['nonce_str'] = generate_nonce_str()
  71. self.params['body'] = WEIXIN_BODY
  72. self.params['spbill_create_ip'] = WEIXIN_SPBILL_CREATE_IP
  73. self.params['notify_url'] = WEIXIN_CALLBACK_API
  74. self.params['trade_type'] = 'NATIVE'
  75. self.params['sign'] = generate_sign(self.params)
  76. data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
  77. headers = {'Content-Type': 'application/xml'}
  78. res = requests.post(WEIXIN_UNIFIED_ORDER_URL, data=data, headers=headers)
  79. if res.status_code != 200:
  80. raise CustomError(u'微信请求失败!')
  81. result = json.loads(json.dumps(xmltodict.parse(res.content)))
  82. if result['xml']['return_code'] != 'SUCCESS':
  83. raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
  84. if result['xml']['result_code'] != 'SUCCESS':
  85. raise CustomError(u'微信交易失败![%s:%s]' % (result['xml']['err_code'], result['xml']['err_code_des']))
  86. self.prepay_id = result['xml']['prepay_id']
  87. return result['xml']['code_url']
  88. #小程序支付
  89. class WechatAppletPay():
  90. def __init__(self):
  91. self.params = {
  92. "appid": settings.WEAPP['appid'],
  93. "body": WEIXIN_BODY,
  94. "mch_id": WEIXIN_MCH_ID,
  95. "nonce_str": '',
  96. "notify_url": WEIXIN_CALLBACK_API,
  97. "openid": '', # 获取小程序openid
  98. "out_trade_no": '',
  99. "spbill_create_ip": WEIXIN_SPBILL_CREATE_IP,
  100. "total_fee": '',
  101. "trade_type": 'JSAPI',
  102. "sign":''
  103. }
  104. self.prepay_id = None
  105. def weChatUnifiedOrder(self, openid, out_trade_no, total_fee):
  106. self.params['out_trade_no'] = out_trade_no
  107. self.params['total_fee'] = int(round(float(total_fee) * 100, 0))
  108. self.params['nonce_str'] = generate_nonce_str()
  109. self.params['openid'] = openid
  110. self.params['sign'] = generate_sign(self.params)
  111. # 拿到封装好的xml数据
  112. xml_data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
  113. # 请求微信统一下单URL
  114. headers = {'Content-Type': 'application/xml'}
  115. res = requests.post(WEIXIN_UNIFIED_ORDER_URL, data=xml_data, headers=headers)
  116. if res.status_code != 200:
  117. raise CustomError(u'微信请求失败!')
  118. # 回复数据为xml,将其转为字典
  119. content = json.loads(json.dumps(xmltodict.parse(res.content)))
  120. if content['xml']['return_code'] != 'SUCCESS':
  121. raise CustomError(u'微信通信失败![%s]' % content['xml']['return_msg'])
  122. if content['xml']['result_code'] != 'SUCCESS':
  123. raise CustomError(u'微信交易失败![%s:%s]' % (content['xml']['err_code'],content['xml']['err_code_des']))
  124. self.prepay_id = content['xml']['prepay_id']
  125. return self.getPaymentInfo()
  126. def getPaymentInfo(self):
  127. data = {
  128. 'appId': self.params['appid'],
  129. 'package': "prepay_id={}".format(self.prepay_id),
  130. 'nonceStr': generate_nonce_str(),
  131. 'timeStamp': str(int(time.time())),
  132. 'signType':'MD5'
  133. }
  134. data['paySign'] = generate_sign(data)
  135. return data
  136. class WechatPayNotify():
  137. def __init__(self, params):
  138. self.params = params
  139. def handle(self):
  140. resp_dict = json.loads(json.dumps(xmltodict.parse(self.params)))['xml']
  141. return_code = resp_dict['return_code']
  142. if return_code != 'SUCCESS':
  143. return None
  144. if not validate_sign(resp_dict):
  145. return None
  146. return resp_dict
  147. @staticmethod
  148. def response_ok():
  149. return_info = {
  150. 'return_code': 'SUCCESS',
  151. 'return_msg': 'OK'
  152. }
  153. return generate_response_data(return_info)
  154. @staticmethod
  155. def response_fail():
  156. return_info = {
  157. 'return_code': 'FAIL',
  158. 'return_msg': 'FAIL'
  159. }
  160. return generate_response_data(return_info)
  161. def generate_nonce_str():
  162. """
  163. 生成随机字符串
  164. """
  165. return str(uuid.uuid4()).replace('-', '')
  166. def generate_sign(params):
  167. """
  168. 生成md5签名的参数
  169. """
  170. if 'sign' in params:
  171. params.pop('sign')
  172. src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % WEIXIN_KEY
  173. return md5(src.encode('utf-8')).hexdigest().upper()
  174. def validate_sign(resp_dict):
  175. """
  176. 验证微信返回的签名
  177. """
  178. if 'sign' not in resp_dict:
  179. return False
  180. wx_sign = resp_dict['sign']
  181. sign = generate_sign(resp_dict)
  182. if sign == wx_sign:
  183. return True
  184. return False
  185. def generate_response_data(resp_dict):
  186. """
  187. 字典转xml
  188. """
  189. return xmltodict.unparse({'xml': resp_dict}, pretty=True, full_document=False).encode('utf-8')