轉載請註明原文地址:http://www.cnblogs.com/ygj0930/p/7649207.htmlphp
核心代碼github地址:https://github.com/ygj0930/Python-WeiXinNativePayhtml
一:項目準備git
官方資料閱讀:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5github
微信支付須要用到微信公衆平臺帳號、微信商戶帳號。算法
註冊完成後,咱們須要在公衆平臺、商戶平臺找到如下信息:數據庫
# ========支付相關配置信息=========== _APP_ID = ""; # 公衆帳號appid _MCH_ID = ""; # 商戶號 _API_KEY = ""; # 微信商戶平臺(pay.weixin.qq.com) -->帳戶設置 -->API安全 -->密鑰設置,設置完成後把密鑰複製到這裏
而後,還須要配置如下信息:api
_UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; #url是微信下單api _NOTIFY_URL = ""; # 微信支付結果回調接口,須要改成你的服務器上處理結果回調的方法路徑 _CREATE_IP = 你的服務器地址; # 發起支付請求的ip
二:編寫統一下單工具類安全
統一下單API資料閱讀:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1服務器
1:全局常量配置所需信息微信
# ========支付相關配置信息=========== _APP_ID = ""; # 公衆帳號appid _MCH_ID = ""; # 商戶號 _API_KEY = ""; # 微信商戶平臺(pay.weixin.qq.com) -->帳戶設置 -->API安全 -->密鑰設置,設置完成後把密鑰複製到這裏 _UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; #url是微信下單api _NOTIFY_URL = ""; # 微信支付結果回調接口,須要改成你的服務器上處理結果回調的方法路徑 _CREATE_IP = 你的服務器地址; # 發起支付請求的ip
而後定義統一下單方法。
2:在統一下單方法中,構造所需參數,其中,必須的參數有十個:
字段名 | 變量名 | 必填 | 類型 | 示例值 | 描述 |
---|---|---|---|---|---|
公衆帳號ID | appid | 是 | String(32) | wxd678efh567hg6787 | 微信支付分配的公衆帳號ID(企業號corpid即爲此appId) |
商戶號 | mch_id | 是 | String(32) | 1230000109 | 微信支付分配的商戶號 |
隨機字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 隨機字符串,長度要求在32位之內。推薦隨機數生成算法 |
簽名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 經過簽名算法計算得出的簽名值,詳見簽名生成算法 |
商品描述 | body | 是 | String(128) | 騰訊充值中心-QQ會員充值 | 商品簡單描述,該字段請按照規範傳遞,具體請見參數規定 |
商戶訂單號 | out_trade_no | 是 | String(32) | 20150806125346 | 商戶系統內部訂單號,要求32個字符內,只能是數字、大小寫字母_-|*@ ,且在同一個商戶號下惟一。詳見商戶訂單號 |
標價金額 | total_fee | 是 | Int | 88 | 訂單總金額,單位爲分,詳見支付金額 |
終端IP | spbill_create_ip | 是 | String(16) | 123.12.12.123 | APP和網頁支付提交用戶端ip,Native支付填調用微信支付API的機器IP。 |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 異步接收微信支付結果通知的回調地址,通知url必須爲外網可訪問的url,不能攜帶參數。 |
交易類型 | trade_type | 是 | String(16) | JSAPI | 取值以下:JSAPI,NATIVE,APP等,說明詳見參數規定 |
appid = self._APP_ID mch_id = self._MCH_ID key = self._API_KEY nonce_str = str(int(round(time.time() * 1000)))+str(random.randint(1,999))+string.join(random.sample(['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'], 5)).replace(" ","") #拼接出隨機的字符串便可,我這裏是用 時間+隨機數字+5個隨機字母 spbill_create_ip = self._CREATE_IP notify_url = self._NOTIFY_URL trade_type = "NATIVE" #掃碼支付類型 params = {} params['appid'] = appid params['mch_id'] = mch_id params['nonce_str'] = nonce_str params['out_trade_no'] = 訂單號參數.encode('utf-8') #客戶端生成並傳過來,參數必須用utf8編碼,不然報錯【訂單號不是在服務器生成,而是在客戶端生成的,不然客戶端沒法根據訂單號輪詢支付結果】 params['total_fee'] = 價格參數 #單位是分,必須是整數 params['spbill_create_ip'] = spbill_create_ip params['notify_url'] = notify_url params['body'] = 商品名參數.encode('utf-8') #中文必須用utf-8編碼,不然xml格式錯誤 params['trade_type'] = trade_type
根據以上9個參數,生成簽名:
#生成簽名 ret = [] for k in sorted(params.keys()): if (k != 'sign') and (k != '') and (params[k] is not None): ret.append('%s=%s' % (k, params[k])) params_str = '&'.join(ret) params_str = '%(params_str)s&key=%(partner_key)s'%{ 'params_str': params_str, 'partner_key': key} #這裏須要設置系統編碼爲utf-8,不然下面md5加密會報參數錯誤 reload(sys) sys.setdefaultencoding('utf8') params_str = hashlib.md5(params_str.encode('utf-8')).hexdigest() sign = params_str.upper() params['sign'] = sign
3:把上面10個參數拼接成XML格式字符串【微信統一下單API只接收和回傳XML格式的數據】
#拼接參數的xml字符串 request_xml_str = '<xml>' for key, value in params.items(): if isinstance(value, basestring): request_xml_str = '%s<%s><![CDATA[%s]]></%s>' % (request_xml_str, key, value, key, ) else: request_xml_str = '%s<%s>%s</%s>' % (request_xml_str, key, value, key, ) request_xml_str = '%s</xml>' % request_xml_str
4:向微信統一下單API發出請求,傳遞參數過去,得到回傳結果後提取數據
#向微信支付發出請求,接收回傳數據 res = urllib2.Request(self._UFDODER_URL, data=request_xml_str) res_data = urllib2.urlopen(res) #打開響應流 res_read = res_data.read() #讀取響應流中數據 doc = xmltodict.parse(res_read) #數據是xml格式的,轉爲dict return_code = doc['xml']['return_code'] #根據dict的層級,從頂層開始逐級訪問提取所需內容 if return_code=="SUCCESS": result_code = doc['xml']['result_code'] if result_code=="SUCCESS": code_url = doc['xml']['code_url'] return code_url else: err_des = doc['xml']['err_code_des'] print "errdes==========="+err_des else: fail_des = doc['xml']['return_msg'] print "fail des============="+fail_des
返回結果
字段名 | 變量名 | 必填 | 類型 | 示例值 | 描述 |
---|---|---|---|---|---|
返回狀態碼 | return_code | 是 | String(16) | SUCCESS | SUCCESS/FAIL 此字段是通訊標識,非交易標識,交易是否成功須要查看result_code來判斷 |
返回信息 | return_msg | 否 | String(128) | 簽名失敗 | 返回信息,如非空,爲錯誤緣由 簽名失敗 參數格式校驗錯誤 |
如下字段在return_code爲SUCCESS的時候有返回
字段名 | 變量名 | 必填 | 類型 | 示例值 | 描述 |
---|---|---|---|---|---|
公衆帳號ID | appid | 是 | String(32) | wx8888888888888888 | 調用接口提交的公衆帳號ID |
商戶號 | mch_id | 是 | String(32) | 1900000109 | 調用接口提交的商戶號 |
設備號 | device_info | 否 | String(32) | 013467007045764 | 自定義參數,能夠爲請求支付的終端設備號等 |
隨機字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 微信返回的隨機字符串 |
簽名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 微信返回的簽名值,詳見簽名算法 |
業務結果 | result_code | 是 | String(16) | SUCCESS | SUCCESS/FAIL |
錯誤代碼 | err_code | 否 | String(32) | SYSTEMERROR | 詳細參見下文錯誤列表 |
錯誤代碼描述 | err_code_des | 否 | String(128) | 系統錯誤 | 錯誤信息描述 |
如下字段在return_code 和result_code都爲SUCCESS的時候有返回
字段名 | 變量名 | 必填 | 類型 | 示例值 | 描述 |
---|---|---|---|---|---|
交易類型 | trade_type | 是 | String(16) | JSAPI | 交易類型,取值爲:JSAPI,NATIVE,APP等,說明詳見參數規定 |
預支付交易會話標識 | prepay_id | 是 | String(64) | wx201410272009395522657a690389285100 | 微信生成的預支付會話標識,用於後續接口調用中使用,該值有效期爲2小時 |
二維碼連接 | code_url | 否 | String(64) | URl:weixin://wxpay/s/An4baqw | trade_type爲NATIVE時有返回,用於生成二維碼,展現給用戶進行掃碼支付 |
錯誤碼
名稱 | 描述 | 緣由 | 解決方案 |
---|---|---|---|
NOAUTH | 商戶無此接口權限 | 商戶未開通此接口權限 | 請商戶前往申請此接口權限 |
NOTENOUGH | 餘額不足 | 用戶賬號餘額不足 | 用戶賬號餘額不足,請用戶充值或更換支付卡後再支付 |
ORDERPAID | 商戶訂單已支付 | 商戶訂單已支付,無需重複操做 | 商戶訂單已支付,無需更多操做 |
ORDERCLOSED | 訂單已關閉 | 當前訂單已關閉,沒法支付 | 當前訂單已關閉,請從新下單 |
SYSTEMERROR | 系統錯誤 | 系統超時 | 系統異常,請用相同參數從新調用 |
APPID_NOT_EXIST | APPID不存在 | 參數中缺乏APPID | 請檢查APPID是否正確 |
MCHID_NOT_EXIST | MCHID不存在 | 參數中缺乏MCHID | 請檢查MCHID是否正確 |
APPID_MCHID_NOT_MATCH | appid和mch_id不匹配 | appid和mch_id不匹配 | 請確認appid和mch_id是否匹配 |
LACK_PARAMS | 缺乏參數 | 缺乏必要的請求參數 | 請檢查參數是否齊全 |
OUT_TRADE_NO_USED | 商戶訂單號重複 | 同一筆交易不能屢次提交 | 請覈實商戶訂單號是否重複提交 |
SIGNERROR | 簽名錯誤 | 參數簽名結果不正確 | 請檢查簽名參數和方法是否都符合簽名算法要求 |
XML_FORMAT_ERROR | XML格式錯誤 | XML格式錯誤 | 請檢查XML參數格式是否正確 |
REQUIRE_POST_METHOD | 請使用post方法 | 未使用post傳遞參數 | 請檢查請求參數是否經過post方法提交 |
POST_DATA_EMPTY | post數據爲空 | post數據不能爲空 | 請檢查post數據是否爲空 |
NOT_UTF8 | 編碼格式錯誤 | 未使用指定編碼格式 | 請使用UTF-8編碼格式 |
三:編寫Controller層方法,主要有三個
1:處理客戶端的二維碼請求的方法
def getWeChatQRCode(self, **kwargs): order_id = kwargs.get('order_id') #獲取客戶端生成的訂單號 goodsName = kwargs.get('goodsName') #獲取商品信息 goodsPrice = int(float(kwargs.get('goodsPrice')) * 100) #獲取價格,單位是分,須要是整數 toolUtil = PayToolUtil() code_url=toolUtil.getPayUrl(order_id,goodsName,goodsPrice) #調用統一下單方法,得到支付訂單的url連接 if code_url: res_info = code_url # 若是成功得到支付連接,則寫入一條訂單記錄 #todo:本身的後臺邏輯 else: res_info = "二維碼失效" #獲取url失敗,則二維碼信息爲失效 #根據res_info生成二維碼,使用qrcode模塊 qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_H, box_size=10, border=1 ) qr.add_data(res_info) #二維碼所含信息 img = qr.make_image() #生成二維碼圖片 byte_io = BytesIO() img.save(byte_io, 'PNG') #存入字節流 byte_io.seek(0) return http.send_file(byte_io, mimetype='image/png') #把字節流返回給客戶端,解析獲得二維碼
2:處理微信支付平臺支付結果回調
支付回調資料閱讀:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7
支付完成後,微信會把相關支付結果和用戶信息發送給商戶,商戶須要接收處理,並返回應答。
商戶系統對於支付結果通知的內容必定要作簽名驗證,並校驗返回的訂單金額是否與商戶側的訂單金額一致,防止數據泄漏致使出現「假通知」,形成資金損失。
def weChatQRCodeNotify(self, request, *args,**kwargs): order_result_xml = http.request.httprequest.stream.read() #從請求流提取數據 doc = xmltodict.parse(order_result_xml) #解析獲得的xml字符串,轉爲dict out_trade_no = doc['xml']['out_trade_no'] #提取返回數據中的訂單號 #todo:提取簽名、支付金額等,驗證簽名是否正確、金額是否正確 #思路:在前面獲取二維碼時,生成了一條訂單記錄,訂單應該保存下訂單號、簽名、金額等信息。在這裏,根據回傳的訂單號查詢數據庫,獲得對應的簽名、金額進行驗證便可 #最後,別忘了應答微信支付平臺,防止重複發送數據 return ''' <xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml> '''
3:處理客戶端輪詢請求
咱們在客戶端發起二維碼請求後,得到二維碼圖片,向微信支付平臺下了一張訂單,那麼這張訂單的支付狀態,客戶端怎麼知道呢?用輪詢。
客戶端生成一個隨機字符串,做爲訂單號,把訂單號做爲參數,發起二維碼請求,同時,另開一個線程,用這個訂單號不斷輪詢服務器,查詢該訂單號對應的訂單記錄的支付狀態,得到返回值後檢查返回值:若是支付成功,則客戶端執行後續操做(如:售賣機出貨、頁面跳轉之類邏輯)並中止輪詢;若是失敗,也執行相應的用戶提醒,如(餘額不足等)並中止輪詢。
def weChatQRCodeHadPay(self, **kwargs): order_id = kwargs.get('order_id') #獲取訂單號 #todo:根據訂單號查詢對應的訂單記錄狀態,返回支付結果