Python實現微信掃碼支付模式二(NativePay)

轉載請註明原文地址: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:根據訂單號查詢對應的訂單記錄狀態,返回支付結果