WechatPayObject.py 8.6 KB

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