wechatpay.py 6.0 KB

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