WechatPayObject.py 7.5 KB

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