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


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM