WechatPayObject.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import datetime
  2. import hashlib
  3. import time
  4. from urllib.parse import quote
  5. import requests
  6. import xmltodict
  7. from Ansjer.config import BASE_DIR
  8. from django.conf import settings
  9. WX_APPID = settings.WX_APPID
  10. WX_MCHID = settings.WX_MCHID
  11. WX_APIKEY = settings.WX_APIKEY
  12. WX_CERT = settings.WX_CERT
  13. WX_KEY = settings.WX_KEY
  14. class WechatPayObject:
  15. """配置账号信息"""
  16. # 微信公众号身份的唯一标识。审核通过后,在微信发送的邮件中查看
  17. def __init__(self):
  18. # 开发者调用支付统一下单API生成预交易单
  19. self.APPID = WX_APPID
  20. # 商户id
  21. self.MCHID = WX_MCHID
  22. # 异步通知url,商户根据实际开发过程设定
  23. self.NOTIFY_URL = 'test'
  24. self.TRADE_TYPE = 'APP'
  25. self.APIKEY = WX_APIKEY
  26. self.url = 'https://api.mch.weixin.qq.com/pay/unifiedorder' # 微信请求url
  27. self.refund_url = 'https://api.mch.weixin.qq.com/secapi/pay/refund' # 退款url
  28. self.downloadbill_url = 'https://api.mch.weixin.qq.com/pay/downloadbill' # 账单url
  29. self.error = None
  30. self.params = None
  31. def get_parameter(self, order_id, body, total_fee, spbill_create_ip, notify_url, attach):
  32. self.params = {
  33. 'appid': self.APPID, # appid
  34. 'mch_id': self.MCHID, # 商户号
  35. 'nonce_str': self.getNonceStr(),
  36. 'body': body, # 商品描述
  37. 'out_trade_no': str(order_id), # 商户订单号
  38. 'total_fee': str(int(total_fee)),
  39. 'spbill_create_ip': spbill_create_ip, # 127.0.0.1
  40. 'trade_type': self.TRADE_TYPE, # 交易类型
  41. 'notify_url': notify_url, # 微信支付结果异步通知地址
  42. 'receipt': 'Y',
  43. 'attach' : attach
  44. }
  45. return self.params
  46. def getNonceStr(self, length=32):
  47. """生成随机字符串"""
  48. import random
  49. chars = "abcdefghijklmnopqrstuvwxyz0123456789"
  50. strs = []
  51. for x in range(length):
  52. strs.append(chars[random.randrange(0, len(chars))])
  53. return "".join(strs)
  54. def key_value_url(self, value, urlencode):
  55. """
  56. 将键值对转为 key1=value1&key2=value2
  57. 对参数按照key=value的格式,并按照参数名ASCII字典序排序
  58. """
  59. slist = sorted(value)
  60. buff = []
  61. for k in slist:
  62. v = quote(value[k]) if urlencode else value[k]
  63. buff.append("{0}={1}".format(k, v))
  64. return "&".join(buff)
  65. def get_sign(self, params):
  66. """
  67. 生成sign
  68. 拼接API密钥
  69. """
  70. stringA = self.key_value_url(params, False)
  71. stringSignTemp = stringA + '&key=' + self.APIKEY # APIKEY, API密钥,需要在商户后台设置
  72. sign = (hashlib.md5(stringSignTemp.encode("utf-8")).hexdigest()).upper()
  73. params['sign'] = sign
  74. return params
  75. def get_req_xml(self):
  76. """
  77. 拼接XML
  78. """
  79. self.get_sign(self.params)
  80. xml = "<xml>"
  81. for k, v in self.params.items():
  82. # v = v.encode('utf8')
  83. # k = k.encode('utf8')
  84. xml += '<' + k + '>' + v + '</' + k + '>'
  85. xml += "</xml>"
  86. return xml.encode("utf-8")
  87. def get_prepay_id(self):
  88. """
  89. 请求获取prepay_id
  90. """
  91. xml = self.get_req_xml()
  92. respone = requests.post(self.url, xml, headers={'Content-Type': 'application/xml'})
  93. msg = respone.text.encode('ISO-8859-1').decode('utf-8')
  94. xmlresp = xmltodict.parse(msg)
  95. if xmlresp['xml']['return_code'] == 'SUCCESS':
  96. if xmlresp['xml']['result_code'] == 'SUCCESS':
  97. prepay_id = xmlresp['xml']['prepay_id']
  98. self.params['prepay_id'] = prepay_id
  99. self.params['packageId'] = "Sign=WXPay"
  100. self.params['timestamp'] = str(int(time.time()))
  101. return self.params
  102. else:
  103. return 'failure'
  104. else:
  105. return 'failure'
  106. def re_finall(self,orderid = None):
  107. """得到prepay_id后再次签名,返回给终端参数.
  108. """
  109. self.get_prepay_id()
  110. if self.error:
  111. return False
  112. sign_again_params = {
  113. 'appid': self.params['appid'],
  114. 'noncestr': self.params['nonce_str'],
  115. 'package': self.params['packageId'],
  116. 'partnerid': self.params['mch_id'],
  117. 'timestamp': self.params['timestamp'],
  118. 'prepayid': self.params['prepay_id']
  119. }
  120. self.get_sign(sign_again_params)
  121. sign_again_params['sign'] = sign_again_params['sign']
  122. sign_again_params['packageId'] = sign_again_params['package']
  123. sign_again_params['out_trade_no'] = orderid
  124. return sign_again_params # 返回给app
  125. def get_notifypay(self, data):
  126. success = self.get_sign(data)
  127. # print('success', success)
  128. if success:
  129. success.pop("sign", None)
  130. success.pop("sign_type", None)
  131. # return success
  132. return True
  133. else:
  134. return False
  135. def weixinpay_call_back(self, data):
  136. args = str(data, 'utf-8')
  137. if args is None:
  138. return None
  139. print(args)
  140. # 验证平台签名
  141. resp_dict = self.handle_wx_response_xml(args)
  142. if resp_dict is None:
  143. print('签名验证失败!!!')
  144. return None
  145. return resp_dict
  146. def handle_wx_response_xml(self, params):
  147. """
  148. 处理微信支付返回的xml格式数据
  149. """
  150. try:
  151. resp_dict = xmltodict.parse(params)['xml']
  152. if not resp_dict or len(resp_dict) < 1:
  153. print('resp_dict is zero+++++++++')
  154. return None
  155. return_code = resp_dict.get('return_code')
  156. if return_code == 'SUCCESS': # 仅仅判断通信标识成功,非交易标识成功,交易需判断result_code
  157. return resp_dict
  158. except Exception as e:
  159. print(e)
  160. return None
  161. return None
  162. @staticmethod
  163. def xml_to_dict(params):
  164. """
  165. 拼接XML
  166. """
  167. if not isinstance(params, dict):
  168. return None
  169. xml = "<xml>"
  170. for k, v in params.items():
  171. # v = v.encode('utf8')
  172. # k = k.encode('utf8')
  173. xml += '<' + k + '>' + v + '</' + k + '>'
  174. xml += "</xml>"
  175. return xml
  176. def refund(self, out_trade_no, out_refund_no, total_fee, refund_fee):
  177. params = {
  178. 'appid': self.APPID,
  179. 'mch_id': self.MCHID,
  180. 'nonce_str': self.getNonceStr(),
  181. 'out_trade_no': out_trade_no, # 商户订单号
  182. 'out_refund_no': out_refund_no, # 商户退款单号
  183. 'total_fee': total_fee, # 订单金额
  184. 'refund_fee': refund_fee # 退款金额
  185. }
  186. # doc: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
  187. string_sign = "&".join(['{}={}'.format(k, params[k]) for k in sorted(params)] + ['key={}'.format(self.APIKEY)])
  188. params['sign'] = hashlib.md5(string_sign.encode('utf8')).hexdigest().upper() # MD5签名
  189. # 数据拼接成xml格式
  190. xml = "<xml>{}</xml>".format("".join(['<{k}>{v}</{k}>'.format(k=k, v=v) for k, v in params.items()]))
  191. cert = BASE_DIR + WX_CERT
  192. key = BASE_DIR + WX_KEY
  193. r = requests.post(
  194. url=self.refund_url, headers={'Content-Type': 'text/xml'}, data=xml.encode('utf-8'),
  195. cert=(cert, key), verify=True)
  196. response = xmltodict.parse(r.text)
  197. if response['xml']['result_code'] == 'SUCCESS':
  198. return True
  199. else:
  200. return False
  201. def download_bill(self, date):
  202. params = {
  203. 'appid': self.APPID, # 公众账号ID
  204. 'mch_id': self.MCHID, # 商户号
  205. 'nonce_str': self.getNonceStr(), # 随机字符串
  206. 'bill_date': date, # 对账单日期
  207. 'bill_type': 'ALL', # 账单类型
  208. }
  209. string_sign = "&".join(['{}={}'.format(k, params[k]) for k in sorted(params)] + ['key={}'.format(self.APIKEY)])
  210. params['sign'] = hashlib.md5(string_sign.encode('utf8')).hexdigest().upper() # MD5签名
  211. xml = "<xml>{}</xml>".format("".join(['<{k}>{v}</{k}>'.format(k=k, v=v) for k, v in params.items()])) # 数据拼接成xml格式
  212. r = requests.post(url=self.downloadbill_url, headers={'Content-Type': 'text/xml'}, data=xml.encode('utf-8'))
  213. order_text_list = r.text.split('\r\n')
  214. key_list = order_text_list[0].split(',')
  215. order_list = []
  216. for item in order_text_list[1:-3]:
  217. order_list.append(dict(zip(key_list, item.split(','))))
  218. return order_list