本文實例講述了Python使用微信SDK實現的微信支付功能。分享給大家供大家參考,具體如下:
最近一段時間一直在搞微信平臺開發,v3.37版本微信支付接口變化賊大,所以就看著php的demo移植為Python版,為了保持一致,所以接口方法基本都沒有變,這樣的好處就是不用寫demo了,看著微信官方的demo照葫蘆畫瓢就可以了。
代碼放到github下載地址:https://github.com/Skycrab/wzhifuSDK
我主要測試了JsApi調用方式,其它的調用方式并沒有測試,如果你發現了bug,請多多pull request,我將不甚感激。
方便觀看,代碼貼于此。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
|
#coding:utf-8 """ Created on 2014-11-24 @author: http://blog.csdn.net/yueguanghaidao * 微信支付幫助庫 * ==================================================== * 接口分三種類型: * 【請求型接口】--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 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 = "wx8888888888888888" #JSAPI接口中獲取openid,審核后在公眾平臺開啟開發模式后可查看 APPSECRET = "48888888888888888888888888888887" #受理商ID,身份標識 MCHID = "18888887" #商戶支付密鑰Key。審核通過后,在微信發送的郵件中查看 KEY = "48888888888888888888888888888886" #=======【異步通知url設置】=================================== #異步通知url,商戶根據實際開發過程設定 NOTIFY_URL = "http://******.com/payback" #=======【JSAPI路徑設置】=================================== #獲取access_token過程中的跳轉uri,通過跳轉將code傳入jsapi支付頁面 JS_API_CALL_URL = "http://******.com/pay/?showwxpaytitle=1" #=======【證書路徑設置】===================================== #證書路徑,注意應該填寫絕對路徑 SSLCERT_PATH = "/******/cacert/apiclient_cert.pem" SSLKEY_PATH = "/******/cacert/apiclient_key.pem" #=======【curl超時設置】=================================== CURL_TIMEOUT = 30 #=======【HTTP客戶端設置】=================================== HTTP_CLIENT = "CURL" # ("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) 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 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.SSLKEY_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}><![CDATA[{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 JsApi_pub(Common_util_pub): """JSAPI支付——H5網頁端調起支付接口""" code = None #code碼,用以獲取openid openid = None #用戶的openid parameters = None #jsapi參數,格式為json prepay_id = None #使用統一支付接口得到的預支付id curl_timeout = None #curl超時時間 def __init__( self , timeout = WxPayConf_pub.CURL_TIMEOUT): self .curl_timeout = timeout def createOauthUrlForCode( self , redirectUrl): """生成可以獲得code的url""" urlObj = {} urlObj[ "appid" ] = WxPayConf_pub.APPID urlObj[ "redirect_uri" ] = redirectUrl urlObj[ "response_type" ] = "code" urlObj[ "scope" ] = "snsapi_base" urlObj[ "state" ] = "STATE#wechat_redirect" bizString = self .formatBizQueryParaMap(urlObj, False ) return "https://open.weixin.qq.com/connect/oauth2/authorize?" + bizString def createOauthUrlForOpenid( self ): """生成可以獲得openid的url""" urlObj = {} urlObj[ "appid" ] = WxPayConf_pub.APPID urlObj[ "secret" ] = WxPayConf_pub.APPSECRET urlObj[ "code" ] = self .code urlObj[ "grant_type" ] = "authorization_code" bizString = self .formatBizQueryParaMap(urlObj, False ) return "https://api.weixin.qq.com/sns/oauth2/access_token?" + bizString def getOpenid( self ): """通過curl向微信提交code,以獲取openid""" url = self .createOauthUrlForOpenid() data = HttpClient().get(url) self .openid = json.loads(data)[ "openid" ] return self .openid def setPrepayId( self , prepayId): """設置prepay_id""" self .prepay_id = prepayId def setCode( self , code): """設置code""" self .code = code def getParameters( self ): """設置jsapi的參數""" jsApiObj = {} jsApiObj[ "appId" ] = WxPayConf_pub.APPID timeStamp = int (time.time()) jsApiObj[ "timeStamp" ] = "{0}" . format (timeStamp) jsApiObj[ "nonceStr" ] = self .createNoncestr() jsApiObj[ "package" ] = "prepay_id={0}" . format ( self .prepay_id) jsApiObj[ "signType" ] = "MD5" jsApiObj[ "paySign" ] = self .getSign(jsApiObj) self .parameters = json.dumps(jsApiObj) return self .parameters 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""" self .parameters[ "appid" ] = WxPayConf_pub.APPID #公眾賬號ID self .parameters[ "mch_id" ] = WxPayConf_pub.MCHID #商戶號 self .parameters[ "nonce_str" ] = self .createNoncestr() #隨機字符串 self .parameters[ "sign" ] = self .getSign( self .parameters) #簽名 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" , "body" , "total_fee" , "notify_url" , "trade_type" )): 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[ "sign" ] = self .getSign( self .parameters) #簽名 return self .arrayToXml( self .parameters) def getPrepayId( self ): """獲取prepay_id""" self .postXml() self .result = self .xmlToArray( self .response) prepay_id = self .result[ "prepay_id" ] return prepay_id class OrderQuery_pub(Wxpay_client_pub): """訂單查詢接口""" def __init__( self , timeout = WxPayConf_pub.CURL_TIMEOUT): #設置接口鏈接 self .url = "https://api.mch.weixin.qq.com/pay/orderquery" #設置curl超時時間 self .curl_timeout = timeout super (OrderQuery_pub, self ).__init__() def createXml( self ): """生成接口參數xml""" #檢測必填參數 if any ( self .parameters[key] is None for key in ( "out_trade_no" , "transaction_id" )): raise ValueError( "missing parameter" ) self .parameters[ "appid" ] = WxPayConf_pub.APPID #公眾賬號ID self .parameters[ "mch_id" ] = WxPayConf_pub.MCHID #商戶號 self .parameters[ "nonce_str" ] = self .createNoncestr() #隨機字符串 self .parameters[ "sign" ] = self .getSign( self .parameters) #簽名 return self .arrayToXml( self .parameters) class Refund_pub(Wxpay_client_pub): """退款申請接口""" def __init__( self , timeout = WxPayConf_pub.CURL_TIMEOUT): #設置接口鏈接 self .url = "https://api.mch.weixin.qq.com/secapi/pay/refund" #設置curl超時時間 self .curl_timeout = timeout super (Refund_pub, self ).__init__() def createXml( self ): """生成接口參數xml""" if any ( self .parameters[key] is None for key in ( "out_trade_no" , "out_refund_no" , "total_fee" , "refund_fee" , "op_user_id" )): raise ValueError( "missing parameter" ) self .parameters[ "appid" ] = WxPayConf_pub.APPID #公眾賬號ID self .parameters[ "mch_id" ] = WxPayConf_pub.MCHID #商戶號 self .parameters[ "nonce_str" ] = self .createNoncestr() #隨機字符串 self .parameters[ "sign" ] = self .getSign( self .parameters) #簽名 return self .arrayToXml( self .parameters) def getResult( self ): """ 獲取結果,使用證書通信(需要雙向證書)""" self .postXmlSSL() self .result = self .xmlToArray( self .response) return self .result class RefundQuery_pub(Wxpay_client_pub): """退款查詢接口""" def __init__( self , timeout = WxPayConf_pub.CURL_TIMEOUT): #設置接口鏈接 self .url = "https://api.mch.weixin.qq.com/pay/refundquery" #設置curl超時時間 self .curl_timeout = timeout super (RefundQuery_pub, self ).__init__() def createXml( self ): """生成接口參數xml""" if any ( self .parameters[key] is None for key in ( "out_refund_no" , "out_trade_no" , "transaction_id" , "refund_id" )): raise ValueError( "missing parameter" ) self .parameters[ "appid" ] = WxPayConf_pub.APPID #公眾賬號ID self .parameters[ "mch_id" ] = WxPayConf_pub.MCHID #商戶號 self .parameters[ "nonce_str" ] = self .createNoncestr() #隨機字符串 self .parameters[ "sign" ] = self .getSign( self .parameters) #簽名 return self .arrayToXml( self .parameters) def getResult( self ): """ 獲取結果,使用證書通信(需要雙向證書)""" self .postXmlSSL() self .result = self .xmlToArray( self .response) return self .result class DownloadBill_pub(Wxpay_client_pub): """對賬單接口""" def __init__( self , timeout = WxPayConf_pub.CURL_TIMEOUT): #設置接口鏈接 self .url = "https://api.mch.weixin.qq.com/pay/downloadbill" #設置curl超時時間 self .curl_timeout = timeout super (DownloadBill_pub, self ).__init__() def createXml( self ): """生成接口參數xml""" if any ( self .parameters[key] is None for key in ( "bill_date" , )): raise ValueError( "missing parameter" ) self .parameters[ "appid" ] = WxPayConf_pub.APPID #公眾賬號ID self .parameters[ "mch_id" ] = WxPayConf_pub.MCHID #商戶號 self .parameters[ "nonce_str" ] = self .createNoncestr() #隨機字符串 self .parameters[ "sign" ] = self .getSign( self .parameters) #簽名 return self .arrayToXml( self .parameters) def getResult( self ): """獲取結果,默認不使用證書""" self .postXml() self .result = self .xmlToArray( self .response) return self .result class ShortUrl_pub(Wxpay_client_pub): """短鏈接轉換接口""" def __init__( self , timeout = WxPayConf_pub.CURL_TIMEOUT): #設置接口鏈接 self .url = "https://api.mch.weixin.qq.com/tools/shorturl" #設置curl超時時間 self .curl_timeout = timeout super (ShortUrl_pub, self ).__init__() def createXml( self ): """生成接口參數xml""" if any ( self .parameters[key] is None for key in ( "long_url" , )): raise ValueError( "missing parameter" ) self .parameters[ "appid" ] = WxPayConf_pub.APPID #公眾賬號ID self .parameters[ "mch_id" ] = WxPayConf_pub.MCHID #商戶號 self .parameters[ "nonce_str" ] = self .createNoncestr() #隨機字符串 self .parameters[ "sign" ] = self .getSign( self .parameters) #簽名 return self .arrayToXml( self .parameters) def getShortUrl( self ): """獲取prepay_id""" self .postXml() prepay_id = self .result[ "short_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) #本地簽名 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 class Notify_pub(Wxpay_server_pub): """通用通知接口""" class NativeCall_pub(Wxpay_server_pub): """請求商家獲取商品信息接口""" def createXml( self ): """生成接口參數xml""" if self .returnParameters[ "return_code" ] = = self .SUCCESS: self .returnParameters[ "appid" ] = WxPayConf_pub.APPID #公眾賬號ID self .returnParameters[ "mch_id" ] = WxPayConf_pub.MCHID #商戶號 self .returnParameters[ "nonce_str" ] = self .createNoncestr() #隨機字符串 self .returnParameters[ "sign" ] = self .getSign( self .returnParameters) #簽名 return self .arrayToXml( self .returnParameters) def getProductId( self ): """獲取product_id""" product_id = self .data[ "product_id" ] return product_id class NativeLink_pub(Common_util_pub): """靜態鏈接二維碼""" url = None #靜態鏈接 def __init__( self ): self .parameters = {} #靜態鏈接參數 def setParameter( self , parameter, parameterValue): """設置參數""" self .parameters[ self .trimString(parameter)] = self .trimString(parameterValue) def createLink( self ): if any ( self .parameters[key] is None for key in ( "product_id" , )): raise ValueError( "missing parameter" ) self .parameters[ "appid" ] = WxPayConf_pub.APPID #公眾賬號ID self .parameters[ "mch_id" ] = WxPayConf_pub.MCHID #商戶號 time_stamp = int (time.time()) self .parameters[ "time_stamp" ] = "{0}" . format (time_stamp) #時間戳 self .parameters[ "nonce_str" ] = self .createNoncestr() #隨機字符串 self .parameters[ "sign" ] = self .getSign( self .parameters) #簽名 bizString = self .formatBizQueryParaMap( self .parameters, false) self .url = "weixin://wxpay/bizpayurl?" + bizString def getUrl( self ): """返回鏈接""" self .createLink() return self .url def test(): c = HttpClient() assert c.get( "http://www.baidu.com" )[: 15 ] = = "<!DOCTYPE html>" c2 = HttpClient() assert id (c) = = id (c2) if __name__ = = "__main__" : test() |
希望本文所述對大家Python程序設計有所幫助。