123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- #coding:utf-8
- """
- Created on 2014-11-24
- @author: http://blog.csdn.net/yueguanghaidao,yihaibo@longtugame.com
- * 微信支付帮助库
- * ====================================================
- * 接口分三种类型:
- * 【请求型接口】--Wxpay_client_
- * 统一支付接口类--UnifiedOrder
- * 订单查询接口--OrderQuery
- * 退款申请接口--Refund
- * 退款查询接口--RefundQuery
- * 对账单接口--DownloadBill
- * 短链接转换接口--ShortUrl
- * 【响应型接口】--Wxpay_server_
- * 通用通知接口--Notify
- * Native支付——请求商家获取商品信息接口--NativeCall
- * 【其他】
- * 静态链接二维码--NativeLink
- * JSAPI支付--JsApi
- * =====================================================
- * 【CommonUtil】常用工具:
- * trimString(),设置参数时需要用到的字符处理函数
- * createNoncestr(),产生随机字符串,不长于32位
- * formatBizQueryParaMap(),格式化参数,签名过程需要用到
- * getSign(),生成签名
- * arrayToXml(),array转xml
- * xmlToArray(),xml转 array
- * postXmlCurl(),以post方式提交xml到对应的接口url
- * postXmlSSLCurl(),使用证书,以post方式提交xml到对应的接口url
- """
- import json
- import time
- import random
- import urllib2
- import hashlib
- import threading
- import urllib
- from urllib import quote
- import xml.etree.ElementTree as ET
- try:
- import pycurl
- from cStringIO import StringIO
- except ImportError:
- pycurl = None
- class WxPayConf_pub(object):
- """配置账号信息"""
- ##=======【基本信息设置】=====================================
- #微信公众号身份的唯一标识。审核通过后,在微信发送的邮件中查看
- APPID = "wxb299e10e65157301"
- #JSAPI接口中获取openid,审核后在公众平台开启开发模式后可查看
- APPSECRET = "20e278a60d52ad63822a07e49931435c"
- #受理商ID,身份标识
- MCHID = "1625494503"
- #商户支付密钥Key。审核通过后,在微信发送的邮件中查看
- KEY = "7ce0aeb3eeeb9406a88652f550d6275e"
-
- #=======【异步通知url设置】===================================
- #异步通知url,商户根据实际开发过程设定
- NOTIFY_URL = "https://wx.scxjc.club/api/wx/v3/signup/notify"
- #=======【JSAPI路径设置】===================================
- #获取access_token过程中的跳转uri,通过跳转将code传入jsapi支付页面
- #JS_API_CALL_URL = "http://www.huodongjia.com/pay/?showwxpaytitle=1"
- JS_API_CALL_URL = "http://baoming.siyusai.com/wxjspay/forwxjspay/"
- #=======【证书路径设置】=====================================
- #证书路径,注意应该填写绝对路径
- SSLCERT_PATH = "/data/web/m_website_dev/m_web/weixinpay/cert/apiclient_cert.pem"
- SSLKEY_PATH = "/data/web/m_website_dev/m_web/weixinpay/cert/apiclient_key.pem"
- #=======【curl超时设置】===================================
- CURL_TIMEOUT = 30
- #=======【HTTP客户端设置】===================================
- HTTP_CLIENT = "URLLIB" # ("URLLIB", "CURL")
- class Singleton(object):
- """单例模式"""
- _instance_lock = threading.Lock()
- def __new__(cls, *args, **kwargs):
- if not hasattr(cls, "_instance"):
- with cls._instance_lock:
- if not hasattr(cls, "_instance"):
- impl = cls.configure() if hasattr(cls, "configure") else cls
- instance = super(Singleton, cls).__new__(impl, *args, **kwargs)
- if not isinstance(instance, cls):
- instance.__init__(*args, **kwargs)
- cls._instance = instance
- return cls._instance
- class UrllibClient(object):
- """使用urlib2发送请求"""
- def get(self, url, second=30):
- return self.postXml(None, url, second)
- def post(self,url,data):
- req = urllib2.Request(url)
- data = urllib.urlencode(data)
- opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
- response = opener.open(req, data)
- return response.read()
- def postXml(self, xml, url, second=30):
- """不使用证书"""
- data = urllib2.urlopen(url, xml, timeout=second).read()
- return data
- def postXmlSSL(self, xml, url, second=30):
- """使用证书"""
- raise TypeError("please use CurlClient")
- class CurlClient(object):
- """使用Curl发送请求"""
- def __init__(self):
- self.curl = pycurl.Curl()
- self.curl.setopt(pycurl.SSL_VERIFYHOST, False)
- self.curl.setopt(pycurl.SSL_VERIFYPEER, False)
- #设置不输出header
- self.curl.setopt(pycurl.HEADER, False)
- def get(self, url, second=30):
- return self.postXmlSSL(None, url, second=second, cert=False, post=False)
- def postXml(self, xml, url, second=30):
- """不使用证书"""
- return self.postXmlSSL(xml, url, second=second, cert=False, post=True)
-
- def postXmlSSL(self, xml, url, second=30, cert=True, post=True):
- """使用证书"""
- self.curl.setopt(pycurl.URL, url)
- self.curl.setopt(pycurl.TIMEOUT, second)
- #设置证书
- #使用证书:cert 与 key 分别属于两个.pem文件
- #默认格式为PEM,可以注释
- if cert:
- self.curl.setopt(pycurl.SSLKEYTYPE, "PEM")
- self.curl.setopt(pycurl.SSLKEY, WxPayConf_pub.SSLKEY_PATH)
- self.curl.setopt(pycurl.SSLCERTTYPE, "PEM")
- self.curl.setopt(pycurl.SSLCERT, WxPayConf_pub.SSLCERT_PATH)
- #post提交方式
- if post:
- self.curl.setopt(pycurl.POST, True)
- self.curl.setopt(pycurl.POSTFIELDS, xml)
- buff = StringIO()
- self.curl.setopt(pycurl.WRITEFUNCTION, buff.write)
- self.curl.perform()
- return buff.getvalue()
- class HttpClient(Singleton):
- @classmethod
- def configure(cls):
- if pycurl is not None and WxPayConf_pub.HTTP_CLIENT != "URLLIB":
- return CurlClient
- else:
- return UrllibClient
-
- class Common_util_pub(object):
- """所有接口的基类"""
- def trimString(self, value):
- if value is not None and len(value) == 0:
- value = None
- return value
- def createNoncestr(self, length = 32):
- """产生随机字符串,不长于32位"""
- chars = "abcdefghijklmnopqrstuvwxyz0123456789"
- strs = []
- for x in range(length):
- strs.append(chars[random.randrange(0, len(chars))])
- return "".join(strs)
- def formatBizQueryParaMap(self, paraMap, urlencode):
- """格式化参数,签名过程需要使用"""
- slist = sorted(paraMap)
- buff = []
- for k in slist:
- v = quote(paraMap[k]) if urlencode else paraMap[k]
- buff.append("{0}={1}".format(k, v))
- return "&".join(buff)
- def getSign(self, obj):
- """生成签名"""
- #签名步骤一:按字典序排序参数,formatBizQueryParaMap已做
- String = self.formatBizQueryParaMap(obj, False)
- #签名步骤二:在string后加入KEY
- String = "{0}&key={1}".format(String,WxPayConf_pub.KEY)
- #签名步骤三:MD5加密
- String = hashlib.md5(String).hexdigest()
- #签名步骤四:所有字符转为大写
- result_ = String.upper()
- return result_
- def arrayToXml(self, arr):
- """array转xml"""
- xml = ["<xml>"]
- for k, v in arr.iteritems():
- if v.isdigit():
- xml.append("<{0}>{1}</{0}>".format(k, v))
- else:
- xml.append("<{0}>{1}</{0}>".format(k, v))
- xml.append("</xml>")
- return "".join(xml)
- def xmlToArray(self, xml):
- """将xml转为array"""
- array_data = {}
- root = ET.fromstring(xml)
- for child in root:
- value = child.text
- array_data[child.tag] = value
- return array_data
- def postXmlCurl(self, xml, url, second=30):
- """以post方式提交xml到对应的接口url"""
- return HttpClient().postXml(xml, url, second=second)
- def postXmlSSLCurl(self, xml, url, second=30):
- """使用证书,以post方式提交xml到对应的接口url"""
- return HttpClient().postXmlSSL(xml, url, second=second)
- class Wxpay_client_pub(Common_util_pub):
- """请求型接口的基类"""
- response = None #微信返回的响应
- url = None #接口链接
- curl_timeout = None #curl超时时间
- def __init__(self):
- self.parameters = {} #请求参数,类型为关联数组
- self.result = {} #返回参数,类型为关联数组
- def setParameter(self, parameter, parameterValue):
- """设置请求参数"""
- self.parameters[self.trimString(parameter)] = self.trimString(parameterValue)
- def createXml(self):
- """设置标配的请求参数,生成签名,生成接口参数xml"""
- return self.arrayToXml(self.parameters)
- def postXml(self):
- """post请求xml"""
- xml = self.createXml()
- self.response = self.postXmlCurl(xml, self.url, self.curl_timeout)
- return self.response
- def postXmlSSL(self):
- """使用证书post请求xml"""
- xml = self.createXml()
- self.response = self.postXmlSSLCurl(xml, self.url, self.curl_timeout)
- return self.response
- def getResult(self):
- """获取结果,默认不使用证书"""
- self.postXml()
- self.result = self.xmlToArray(self.response)
- return self.result
- class UnifiedOrder_pub(Wxpay_client_pub):
- """统一支付接口类"""
- def __init__(self, timeout=WxPayConf_pub.CURL_TIMEOUT):
- #设置接口链接
- self.url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
- #设置curl超时时间
- self.curl_timeout = timeout
- super(UnifiedOrder_pub, self).__init__()
- def createXml(self):
- """生成接口参数xml"""
- #检测必填参数
- if any(self.parameters[key] is None for key in ("out_trade_no", "total_fee")):
- raise ValueError("missing parameter")
- if self.parameters["trade_type"] == "JSAPI" and self.parameters["openid"] is None:
- raise ValueError("JSAPI need openid parameters")
- self.parameters["appid"] = WxPayConf_pub.APPID #公众账号ID
- self.parameters["mch_id"] = WxPayConf_pub.MCHID #商户号
- self.parameters["spbill_create_ip"] = "127.0.0.1" #终端ip
- self.parameters["nonce_str"] = self.createNoncestr() #随机字符串
- self.parameters["notify_url"] = WxPayConf_pub.NOTIFY_URL #随机字符串
- self.parameters["body"] = "培训报名费" #随机字符串
- if self.parameters.has_key("sign"):
- self.parameters.pop("sign")
- sign = self.getSign(self.parameters)
- print self.parameters
- self.parameters["sign"] = sign #签名
- return self.arrayToXml(self.parameters)
- def getPrepayId(self):
- """获取prepay_id"""
- self.postXml()
- self.result = self.xmlToArray(self.response)
- print self.result
- return self.result
- def geth5url(self):
- """获取prepay_id"""
- self.postXml()
- self.result = self.xmlToArray(self.response)
- prepay_id = self.result["mweb_url"]
- return prepay_id
- class Wxpay_server_pub(Common_util_pub):
- """响应型接口基类"""
- SUCCESS, FAIL = "SUCCESS", "FAIL"
- def __init__(self):
- self.data = {} #接收到的数据,类型为关联数组
- self.returnParameters = {} #返回参数,类型为关联数组
- def saveData(self, xml):
- """将微信的请求xml转换成关联数组,以方便数据处理"""
- self.data = self.xmlToArray(xml)
- def checkSign(self):
- """校验签名"""
- tmpData = dict(self.data) #make a copy to save sign
- del tmpData['sign']
- sign = self.getSign(tmpData) #本地签名
- print sign,111111111111
- print self.data["sign"],22222222222
- if self.data['sign'] == sign:
- return True
- return False
- def getData(self):
- """获取微信的请求数据"""
- return self.data
- def setReturnParameter(self, parameter, parameterValue):
- """设置返回微信的xml数据"""
- self.returnParameters[self.trimString(parameter)] = self.trimString(parameterValue)
- def createXml(self):
- """生成接口参数xml"""
- return self.arrayToXml(self.returnParameters)
- def returnXml(self):
- """将xml数据返回微信"""
- returnXml = self.createXml()
- return returnXml
- def get_wx_unifiedorder(out_trade_no,total_fee,openid,trade_type="JSAPI"):
- par_obj = UnifiedOrder_pub()
- par_obj.setParameter('out_trade_no',out_trade_no)
- par_obj.setParameter('total_fee',total_fee)
- par_obj.setParameter('openid',openid)
- par_obj.setParameter('trade_type',trade_type)
- par_obj.createXml()
- return par_obj.getPrepayId()
- def check_notify_valid(xml_params):
- """回调验证
- """
- wsp_notify = Wxpay_server_pub()
- wsp_notify.saveData(xml_params)
- return wsp_notify.checkSign(),wsp_notify.getData()
- if __name__ == "__main__":
- pass
|