wechatpay.py 5.9 KB

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