轉載請注明原文地址:http://www.cnblogs.com/ygj0930/p/7649207.html
核心代碼github地址:https://github.com/ygj0930/Python-WeiXinNativePay
一:項目准備
官方資料閱讀:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
微信支付需要用到微信公眾平台賬號、微信商戶賬號。
注冊完成后,我們需要在公眾平台、商戶平台找到以下信息:
# ========支付相關配置信息=========== _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
二:編寫統一下單工具類
統一下單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:根據訂單號查詢對應的訂單記錄狀態,返回支付結果