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:根据订单号查询对应的订单记录状态,返回支付结果