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:在统一下单方法中,构造所需参数,其中,必须的参数有十个:

字段名变量名必填类型示例值描述
公众账号IDappidString(32)wxd678efh567hg6787微信支付分配的公众账号ID(企业号corpid即为此appId)
商户号mch_idString(32)1230000109微信支付分配的商户号
随机字符串nonce_strString(32)5K8264ILTKCH16CQ2502SI8ZNMTM67VS随机字符串,长度要求在32位以内。推荐随机数生成算法
签名signString(32)C380BEC2BFD727A4B6845133519F3AD6通过签名算法计算得出的签名值,详见签名生成算法
商品描述bodyString(128)腾讯充值中心-QQ会员充值

商品简单描述,该字段请按照规范传递,具体请见参数规定

商户订单号out_trade_noString(32)20150806125346商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。详见商户订单号
标价金额total_feeInt88订单总金额,单位为分,详见支付金额
终端IPspbill_create_ipString(16)123.12.12.123APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
通知地址notify_urlString(256)http://www.weixin.qq.com/wxpay/pay.php异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
交易类型trade_typeString(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_codeString(16)SUCCESS

SUCCESS/FAIL 

此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断

返回信息return_msgString(128)签名失败

返回信息,如非空,为错误原因 

签名失败 

参数格式校验错误

以下字段在return_code为SUCCESS的时候有返回 

字段名变量名必填类型示例值描述
公众账号IDappidString(32)wx8888888888888888调用接口提交的公众账号ID
商户号mch_idString(32)1900000109调用接口提交的商户号
设备号device_infoString(32)013467007045764自定义参数,可以为请求支付的终端设备号等
随机字符串nonce_strString(32)5K8264ILTKCH16CQ2502SI8ZNMTM67VS微信返回的随机字符串
签名signString(32)C380BEC2BFD727A4B6845133519F3AD6微信返回的签名值,详见签名算法
业务结果result_codeString(16)SUCCESSSUCCESS/FAIL
错误代码err_codeString(32)SYSTEMERROR详细参见下文错误列表
错误代码描述err_code_desString(128)系统错误错误信息描述

以下字段在return_code 和result_code都为SUCCESS的时候有返回 

字段名变量名必填类型示例值描述
交易类型trade_typeString(16)JSAPI交易类型,取值为:JSAPI,NATIVE,APP等,说明详见参数规定
预支付交易会话标识prepay_idString(64)wx201410272009395522657a690389285100微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
二维码链接code_urlString(64)URl:weixin://wxpay/s/An4baqwtrade_type为NATIVE时有返回,用于生成二维码,展示给用户进行扫码支付

 

错误码 

名称描述原因解决方案
NOAUTH商户无此接口权限商户未开通此接口权限请商户前往申请此接口权限
NOTENOUGH余额不足用户帐号余额不足用户帐号余额不足,请用户充值或更换支付卡后再支付
ORDERPAID商户订单已支付商户订单已支付,无需重复操作商户订单已支付,无需更多操作
ORDERCLOSED订单已关闭当前订单已关闭,无法支付当前订单已关闭,请重新下单
SYSTEMERROR系统错误系统超时系统异常,请用相同参数重新调用
APPID_NOT_EXISTAPPID不存在参数中缺少APPID请检查APPID是否正确
MCHID_NOT_EXISTMCHID不存在参数中缺少MCHID请检查MCHID是否正确
APPID_MCHID_NOT_MATCHappid和mch_id不匹配appid和mch_id不匹配请确认appid和mch_id是否匹配
LACK_PARAMS缺少参数缺少必要的请求参数请检查参数是否齐全
OUT_TRADE_NO_USED商户订单号重复同一笔交易不能多次提交请核实商户订单号是否重复提交
SIGNERROR签名错误参数签名结果不正确请检查签名参数和方法是否都符合签名算法要求
XML_FORMAT_ERRORXML格式错误XML格式错误请检查XML参数格式是否正确
REQUIRE_POST_METHOD请使用post方法未使用post传递参数 请检查请求参数是否通过post方法提交
POST_DATA_EMPTYpost数据为空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:根据订单号查询对应的订单记录状态,返回支付结果

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值