wzhifuSDK.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. #coding:utf-8
  2. """
  3. Created on 2014-11-24
  4. @author: http://blog.csdn.net/yueguanghaidao,yihaibo@longtugame.com
  5. * 微信支付帮助库
  6. * ====================================================
  7. * 接口分三种类型:
  8. * 【请求型接口】--Wxpay_client_
  9. * 统一支付接口类--UnifiedOrder
  10. * 订单查询接口--OrderQuery
  11. * 退款申请接口--Refund
  12. * 退款查询接口--RefundQuery
  13. * 对账单接口--DownloadBill
  14. * 短链接转换接口--ShortUrl
  15. * 【响应型接口】--Wxpay_server_
  16. * 通用通知接口--Notify
  17. * Native支付——请求商家获取商品信息接口--NativeCall
  18. * 【其他】
  19. * 静态链接二维码--NativeLink
  20. * JSAPI支付--JsApi
  21. * =====================================================
  22. * 【CommonUtil】常用工具:
  23. * trimString(),设置参数时需要用到的字符处理函数
  24. * createNoncestr(),产生随机字符串,不长于32位
  25. * formatBizQueryParaMap(),格式化参数,签名过程需要用到
  26. * getSign(),生成签名
  27. * arrayToXml(),array转xml
  28. * xmlToArray(),xml转 array
  29. * postXmlCurl(),以post方式提交xml到对应的接口url
  30. * postXmlSSLCurl(),使用证书,以post方式提交xml到对应的接口url
  31. """
  32. import json
  33. import time
  34. import random
  35. import urllib2
  36. import hashlib
  37. import threading
  38. import urllib
  39. from urllib import quote
  40. import xml.etree.ElementTree as ET
  41. try:
  42. import pycurl
  43. from cStringIO import StringIO
  44. except ImportError:
  45. pycurl = None
  46. class WxPayConf_pub(object):
  47. """配置账号信息"""
  48. ##=======【基本信息设置】=====================================
  49. #微信公众号身份的唯一标识。审核通过后,在微信发送的邮件中查看
  50. APPID = "wxb299e10e65157301"
  51. #JSAPI接口中获取openid,审核后在公众平台开启开发模式后可查看
  52. APPSECRET = "20e278a60d52ad63822a07e49931435c"
  53. #受理商ID,身份标识
  54. MCHID = "1625494503"
  55. #商户支付密钥Key。审核通过后,在微信发送的邮件中查看
  56. KEY = "7ce0aeb3eeeb9406a88652f550d6275e"
  57. #=======【异步通知url设置】===================================
  58. #异步通知url,商户根据实际开发过程设定
  59. NOTIFY_URL = "https://wx.scxjc.club/api/wx/v3/signup/notify"
  60. #=======【JSAPI路径设置】===================================
  61. #获取access_token过程中的跳转uri,通过跳转将code传入jsapi支付页面
  62. #JS_API_CALL_URL = "http://www.huodongjia.com/pay/?showwxpaytitle=1"
  63. JS_API_CALL_URL = "http://baoming.siyusai.com/wxjspay/forwxjspay/"
  64. #=======【证书路径设置】=====================================
  65. #证书路径,注意应该填写绝对路径
  66. SSLCERT_PATH = "/data/web/m_website_dev/m_web/weixinpay/cert/apiclient_cert.pem"
  67. SSLKEY_PATH = "/data/web/m_website_dev/m_web/weixinpay/cert/apiclient_key.pem"
  68. #=======【curl超时设置】===================================
  69. CURL_TIMEOUT = 30
  70. #=======【HTTP客户端设置】===================================
  71. HTTP_CLIENT = "URLLIB" # ("URLLIB", "CURL")
  72. class Singleton(object):
  73. """单例模式"""
  74. _instance_lock = threading.Lock()
  75. def __new__(cls, *args, **kwargs):
  76. if not hasattr(cls, "_instance"):
  77. with cls._instance_lock:
  78. if not hasattr(cls, "_instance"):
  79. impl = cls.configure() if hasattr(cls, "configure") else cls
  80. instance = super(Singleton, cls).__new__(impl, *args, **kwargs)
  81. if not isinstance(instance, cls):
  82. instance.__init__(*args, **kwargs)
  83. cls._instance = instance
  84. return cls._instance
  85. class UrllibClient(object):
  86. """使用urlib2发送请求"""
  87. def get(self, url, second=30):
  88. return self.postXml(None, url, second)
  89. def post(self,url,data):
  90. req = urllib2.Request(url)
  91. data = urllib.urlencode(data)
  92. opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
  93. response = opener.open(req, data)
  94. return response.read()
  95. def postXml(self, xml, url, second=30):
  96. """不使用证书"""
  97. data = urllib2.urlopen(url, xml, timeout=second).read()
  98. return data
  99. def postXmlSSL(self, xml, url, second=30):
  100. """使用证书"""
  101. raise TypeError("please use CurlClient")
  102. class CurlClient(object):
  103. """使用Curl发送请求"""
  104. def __init__(self):
  105. self.curl = pycurl.Curl()
  106. self.curl.setopt(pycurl.SSL_VERIFYHOST, False)
  107. self.curl.setopt(pycurl.SSL_VERIFYPEER, False)
  108. #设置不输出header
  109. self.curl.setopt(pycurl.HEADER, False)
  110. def get(self, url, second=30):
  111. return self.postXmlSSL(None, url, second=second, cert=False, post=False)
  112. def postXml(self, xml, url, second=30):
  113. """不使用证书"""
  114. return self.postXmlSSL(xml, url, second=second, cert=False, post=True)
  115. def postXmlSSL(self, xml, url, second=30, cert=True, post=True):
  116. """使用证书"""
  117. self.curl.setopt(pycurl.URL, url)
  118. self.curl.setopt(pycurl.TIMEOUT, second)
  119. #设置证书
  120. #使用证书:cert 与 key 分别属于两个.pem文件
  121. #默认格式为PEM,可以注释
  122. if cert:
  123. self.curl.setopt(pycurl.SSLKEYTYPE, "PEM")
  124. self.curl.setopt(pycurl.SSLKEY, WxPayConf_pub.SSLKEY_PATH)
  125. self.curl.setopt(pycurl.SSLCERTTYPE, "PEM")
  126. self.curl.setopt(pycurl.SSLCERT, WxPayConf_pub.SSLCERT_PATH)
  127. #post提交方式
  128. if post:
  129. self.curl.setopt(pycurl.POST, True)
  130. self.curl.setopt(pycurl.POSTFIELDS, xml)
  131. buff = StringIO()
  132. self.curl.setopt(pycurl.WRITEFUNCTION, buff.write)
  133. self.curl.perform()
  134. return buff.getvalue()
  135. class HttpClient(Singleton):
  136. @classmethod
  137. def configure(cls):
  138. if pycurl is not None and WxPayConf_pub.HTTP_CLIENT != "URLLIB":
  139. return CurlClient
  140. else:
  141. return UrllibClient
  142. class Common_util_pub(object):
  143. """所有接口的基类"""
  144. def trimString(self, value):
  145. if value is not None and len(value) == 0:
  146. value = None
  147. return value
  148. def createNoncestr(self, length = 32):
  149. """产生随机字符串,不长于32位"""
  150. chars = "abcdefghijklmnopqrstuvwxyz0123456789"
  151. strs = []
  152. for x in range(length):
  153. strs.append(chars[random.randrange(0, len(chars))])
  154. return "".join(strs)
  155. def formatBizQueryParaMap(self, paraMap, urlencode):
  156. """格式化参数,签名过程需要使用"""
  157. slist = sorted(paraMap)
  158. buff = []
  159. for k in slist:
  160. v = quote(paraMap[k]) if urlencode else paraMap[k]
  161. buff.append("{0}={1}".format(k, v))
  162. return "&".join(buff)
  163. def getSign(self, obj):
  164. """生成签名"""
  165. #签名步骤一:按字典序排序参数,formatBizQueryParaMap已做
  166. String = self.formatBizQueryParaMap(obj, False)
  167. #签名步骤二:在string后加入KEY
  168. String = "{0}&key={1}".format(String,WxPayConf_pub.KEY)
  169. #签名步骤三:MD5加密
  170. String = hashlib.md5(String).hexdigest()
  171. #签名步骤四:所有字符转为大写
  172. result_ = String.upper()
  173. return result_
  174. def arrayToXml(self, arr):
  175. """array转xml"""
  176. xml = ["<xml>"]
  177. for k, v in arr.iteritems():
  178. if v.isdigit():
  179. xml.append("<{0}>{1}</{0}>".format(k, v))
  180. else:
  181. xml.append("<{0}>{1}</{0}>".format(k, v))
  182. xml.append("</xml>")
  183. return "".join(xml)
  184. def xmlToArray(self, xml):
  185. """将xml转为array"""
  186. array_data = {}
  187. root = ET.fromstring(xml)
  188. for child in root:
  189. value = child.text
  190. array_data[child.tag] = value
  191. return array_data
  192. def postXmlCurl(self, xml, url, second=30):
  193. """以post方式提交xml到对应的接口url"""
  194. return HttpClient().postXml(xml, url, second=second)
  195. def postXmlSSLCurl(self, xml, url, second=30):
  196. """使用证书,以post方式提交xml到对应的接口url"""
  197. return HttpClient().postXmlSSL(xml, url, second=second)
  198. class Wxpay_client_pub(Common_util_pub):
  199. """请求型接口的基类"""
  200. response = None #微信返回的响应
  201. url = None #接口链接
  202. curl_timeout = None #curl超时时间
  203. def __init__(self):
  204. self.parameters = {} #请求参数,类型为关联数组
  205. self.result = {} #返回参数,类型为关联数组
  206. def setParameter(self, parameter, parameterValue):
  207. """设置请求参数"""
  208. self.parameters[self.trimString(parameter)] = self.trimString(parameterValue)
  209. def createXml(self):
  210. """设置标配的请求参数,生成签名,生成接口参数xml"""
  211. return self.arrayToXml(self.parameters)
  212. def postXml(self):
  213. """post请求xml"""
  214. xml = self.createXml()
  215. self.response = self.postXmlCurl(xml, self.url, self.curl_timeout)
  216. return self.response
  217. def postXmlSSL(self):
  218. """使用证书post请求xml"""
  219. xml = self.createXml()
  220. self.response = self.postXmlSSLCurl(xml, self.url, self.curl_timeout)
  221. return self.response
  222. def getResult(self):
  223. """获取结果,默认不使用证书"""
  224. self.postXml()
  225. self.result = self.xmlToArray(self.response)
  226. return self.result
  227. class UnifiedOrder_pub(Wxpay_client_pub):
  228. """统一支付接口类"""
  229. def __init__(self, timeout=WxPayConf_pub.CURL_TIMEOUT):
  230. #设置接口链接
  231. self.url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
  232. #设置curl超时时间
  233. self.curl_timeout = timeout
  234. super(UnifiedOrder_pub, self).__init__()
  235. def createXml(self):
  236. """生成接口参数xml"""
  237. #检测必填参数
  238. if any(self.parameters[key] is None for key in ("out_trade_no", "total_fee")):
  239. raise ValueError("missing parameter")
  240. if self.parameters["trade_type"] == "JSAPI" and self.parameters["openid"] is None:
  241. raise ValueError("JSAPI need openid parameters")
  242. self.parameters["appid"] = WxPayConf_pub.APPID #公众账号ID
  243. self.parameters["mch_id"] = WxPayConf_pub.MCHID #商户号
  244. self.parameters["spbill_create_ip"] = "127.0.0.1" #终端ip
  245. self.parameters["nonce_str"] = self.createNoncestr() #随机字符串
  246. self.parameters["notify_url"] = WxPayConf_pub.NOTIFY_URL #随机字符串
  247. self.parameters["body"] = "培训报名费" #随机字符串
  248. if self.parameters.has_key("sign"):
  249. self.parameters.pop("sign")
  250. sign = self.getSign(self.parameters)
  251. print self.parameters
  252. self.parameters["sign"] = sign #签名
  253. return self.arrayToXml(self.parameters)
  254. def getPrepayId(self):
  255. """获取prepay_id"""
  256. self.postXml()
  257. self.result = self.xmlToArray(self.response)
  258. print self.result
  259. return self.result
  260. def geth5url(self):
  261. """获取prepay_id"""
  262. self.postXml()
  263. self.result = self.xmlToArray(self.response)
  264. prepay_id = self.result["mweb_url"]
  265. return prepay_id
  266. class Wxpay_server_pub(Common_util_pub):
  267. """响应型接口基类"""
  268. SUCCESS, FAIL = "SUCCESS", "FAIL"
  269. def __init__(self):
  270. self.data = {} #接收到的数据,类型为关联数组
  271. self.returnParameters = {} #返回参数,类型为关联数组
  272. def saveData(self, xml):
  273. """将微信的请求xml转换成关联数组,以方便数据处理"""
  274. self.data = self.xmlToArray(xml)
  275. def checkSign(self):
  276. """校验签名"""
  277. tmpData = dict(self.data) #make a copy to save sign
  278. del tmpData['sign']
  279. sign = self.getSign(tmpData) #本地签名
  280. print sign,111111111111
  281. print self.data["sign"],22222222222
  282. if self.data['sign'] == sign:
  283. return True
  284. return False
  285. def getData(self):
  286. """获取微信的请求数据"""
  287. return self.data
  288. def setReturnParameter(self, parameter, parameterValue):
  289. """设置返回微信的xml数据"""
  290. self.returnParameters[self.trimString(parameter)] = self.trimString(parameterValue)
  291. def createXml(self):
  292. """生成接口参数xml"""
  293. return self.arrayToXml(self.returnParameters)
  294. def returnXml(self):
  295. """将xml数据返回微信"""
  296. returnXml = self.createXml()
  297. return returnXml
  298. def get_wx_unifiedorder(out_trade_no,total_fee,openid,trade_type="JSAPI"):
  299. par_obj = UnifiedOrder_pub()
  300. par_obj.setParameter('out_trade_no',out_trade_no)
  301. par_obj.setParameter('total_fee',total_fee)
  302. par_obj.setParameter('openid',openid)
  303. par_obj.setParameter('trade_type',trade_type)
  304. par_obj.createXml()
  305. return par_obj.getPrepayId()
  306. def check_notify_valid(xml_params):
  307. """回调验证
  308. """
  309. wsp_notify = Wxpay_server_pub()
  310. wsp_notify.saveData(xml_params)
  311. return wsp_notify.checkSign(),wsp_notify.getData()
  312. if __name__ == "__main__":
  313. pass