幸运28全民计划软件,微信支付笔记--native【扫码支付】

浏览:439 发布日期:2019/08/13 分类:技术分享 关键字: 微信支付 native 支付
扫码支付文档:http://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1

支付扫码有两种模式:

模式一:模式一开发前,商户必须在公众平台后台设置支付回调URL。URL实现的功能:接收用户扫码后微信支付系统回调的productid和openid;

模式二:模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。幸运28全民计划软件商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。

模式二的流程比较简单,所以我使用模式二【不用通过微信公众平台设置回调】
开发流程:

第一步,下载sdk--php版--【包含doc,example(可参考),lib(重要)】
lib是微信支付sdk的核心文件包,可以放在扩展包vendor下

二维码的生成,可以通过composer require endroid/qrcode 2.5.1或者直接用微信example里面的phpqrcode类

example下WxPay.Config.php这个是配置文件--可以移动到lib文件下或者你自己想一个位置,自己重构一个。

第二步,参考example里面的native.php---这个就是扫码的例子,例子里面包含两种模式的例子【模式一不再提供支付方式--来自native.php例子说明】,开始使用模式二支付方式【在这之前,我稍微看了微信支付文档的一些流程图描述】,通过例子,可以知道,引用example里面的WxPay.NativePay.php,打开该文件,有三个方法,两个是模式一的【GetPrePayUrl,ToUrlParams】,一个模式二的【GetPayUrl】,因此可以先建一个控制器【如WxPay.php】,直接把模式二的方法搬到我们的控制器下【记得引入你lib文件所在的命名空间】,这里说明一下这个方法【GetPayUrl】,其实就是生成一个微信支付的链接【类似:weixin://******】,再回到native.php,这时候,就可以把native.php中的操作封装成一个方法,然后返回一个code_url,再根据这个生成二维码
到这里,前面的都是比较简单,可以直接搬就可以【稍微的修改】
第三步,写同步和异步操作,微信这里没有callback回调函数,所以采用的订单查询返回值,再进行改状态【同步回调,前端使用轮询,向微信服务器惊喜查询订单的支付状态--SUCCESS】,这个查询不需要进行签名验证,只需要注意一点,当查询到状态是支付成功【必须是成功才改状态】的时候后,给自己的订单改状态,异步回调,这个通过    $testxml = file_get_contents("php://input");
        //将xml转化为json格式
        $jsonxml = json_encode(simplexml_load_string($testxml, 'SimpleXMLElement', LIBXML_NOCDATA));
        //转成数组
        $result = json_decode($jsonxml, true);
获取到这个$result,就算返回的$result['result_code']==SUCCESS,也要做签名验证【这样比较安全,存在一种可能,别人知道你的异步回调,那么他就可以通过构造参数,模拟发送数据给你这个接口,为了避免这个,我做了签名验证】
签名验证【官网:http://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3】
大体如下:这个是tp3的使用方法,具体可以修改一些类库引入,就可以用到tp5中 /**
     * 微信回调通知
     * 数据存在被伪造的可能[非法的xml数据来源,即黑客如果知道这个接口,那么他就能通过订单号进行伪造数据,
     * 所以验证分三步进行,一步一步验证数据的安全]
     * 1.先查看数据中的订单是否存在或者已经付款,那么就直接返回false
     * 这里可以返回xml数据,假如来源是微信,
     * 那么就告诉微信我已经知道用户付款了,不用再向我发起请求;
     * 不返回xml数据,那么微信就会
     * 在15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m
     * 向我们发起10次请求通知我们用户已经付款
     * 下面的方法直接返回false,那么正常情况下,我们会收到10次异步通知
     * 2.1验证携带的参数,我们自定义的参数,如果错误--则不用再进行验证签名函数
     * 这一步其实也可以不用的,只是为了减少错误的数据的执行流程,一旦出错那么就直接返回false
     * 2.2开始验证签名的正确性,
     * xml中所有的数据,除了sign值,其他按照ksort()排序,
     * 再通过http_build_query组合成新的字符串
     * 然后再加上支付密钥组成新的字符串
     * 之后将字符串加密:md5和sha256 [具体看你的配置值使用哪种加密方式]
     * 3.确认签名正确,正式更新订单的状态
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function wxNotify()
    {
        //获取返回的xml
        $testxml = file_get_contents("php://input");
        //将xml转化为json格式
        $jsonxml = json_encode(simplexml_load_string($testxml, 'SimpleXMLElement', LIBXML_NOCDATA));
        //转成数组
        $result = json_decode($jsonxml, true);
        if ($result) {

            //如果成功返回了
            vendor("wxpay.log");
            $logHandler = new \CLogFileHandler(LOG_PATH . "/Wxpay/" . date('Y-m-d') . 'wxnotify.log');
            \Log::Init($logHandler, 15);
            \Log::INFO(json_encode(['time'=>date("Y-m-d H:i:s",time()),'data'=>$result,'ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
            if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
                //进行改变订单状态等操作。幸运28全民计划软件。。。 ['return_msg'=>['transaction_id','out_trade_no','attach','total_fee']]
                //TODO: 1.检查订单的支付状态,如果以付款则,直接返回xml数据,或者直接返回false|true
                $_where = ['order_no' => $result['out_trade_no'], 'states' => 4];
                $order = M("order_list")->where($_where)->find();

                if (!$order) {
                    //写入日志-$result['out_trade_no']
                    \Log::WARN(json_encode(['wxNotify-TODO-1' => '订单号:' . $result['out_trade_no']
                        . "不存在或已经付款", 'ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
                    return false;
                }

                //TODO: 2校验签名--保障数据是来源微信的服务器;
                //TODO: 2.1:校验参数----查看return_msg中是否有我们发送给微信服务器端的数据attach[商家数据包]
                if (isset($result['attach']) && $result['attach'] != "wwwahai574com") {
                    //写入日志-attach数据包错误
                    \Log::ERROR(json_encode(['wxNotify-TODO-2.1' => "attach数据包错误", 'ip' => get_client_ip()]));
                    return false;
                }

                //TODO: 2.2:验证签名
                //获取返回的所以参数
                //这里是要把微信返给我们的所有值,先删除sign的值,其他值 按ASCII从小到大排序;
                $xmlSign = "我是默认的sign,如果没有获取到微信返回的sign值,那么就写入日志";
                foreach ($result as $k => $v) {
                    if ($k == 'sign') {
                        $xmlSign = $result[$k];
                        unset($result[$k]);
                    };
                }
                //排序
                ksort($result);
                $sign = http_build_query($result);
                //md5处理
                vendor("wxpay.WxPay#Config");
                $config = new \WxPayConfig();
                $string = $sign . '&key=' . $config->GetKey();
                //加密的方式:下面2选1 Wxpay.Config.php中的配置是HMAC-SHA256,所以不用md5加密
                //如果是“MD5”,则:$sign = md5($string);
                $sign = hash_hmac("sha256",$string ,$config->GetKey());
                //转大写
                $sign = strtoupper($sign);
                //\Log::INFO(json_encode(['string'=>$string,'sign'=>$sign],JSON_UNESCAPED_UNICODE));
                //验签名。默认支持MD5
                //验证加密后的32位值和 微信返回的sign 是否一致!!!
                if ($sign === $xmlSign) {
                    //TODO: 3修改订单为付款状态
                    \Log::INFO(json_encode(['wxNotify-TODO-3'=>'验证签名正确,开始更新订单的状态','string'=>$string,'sign'=>$sign],JSON_UNESCAPED_UNICODE));
                    //更新订单状态,本来应该给微信返回值说明支付成功的,不返回值,微信就只会触发多次请求,
                    //反正查询条件必须是未支付的订单才会改状态,所以多次请求,只有第一次真正成功才会写入数据库
                    $data['states'] = 0;
                    $data['completetime'] = date("Y-m-d H:i:s", time());
                    M("order_list")->where($_where)->save($data);
                    //\Log::INFO(json_encode(['wxNotify-tody-3'=>'验证签名正确,结束更新订单的状态'],JSON_UNESCAPED_UNICODE));
                }else{
                    \Log::ERROR(json_encode(['wxNotify-TODO-3' => "签名错误",'xml中的sign'=>$xmlSign,'xml组成的sign'=>$sign, '访问者ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
                }

            }
        }
    }
控制器中的代码---测试通过了---具体的可以在进行修改--这个只是粗略的写出来--主要是为了说明流程:/**
     * 发起微信支付 -- 统一下单
     * 流程:WxPayController->GetPayUrl()
     * ->WxPayApi->unifiedOrder()
     * ->WxPayUnifiedOrder->WxPayDatabase->setSign()
     * 生成签名值
     * @throws \WxPayException
     */
    public function payOrder()
    {
        if(empty($this->udata)){
            $this->redirect('Login/index');
        }
        $order = M('Order_list')
            ->where(array('mw_order_list.order_no' => $_GET['order_id'], 'mw_order_list.states' => 4))
            ->join("mw_order_info on mw_order_list.id = mw_order_info.order_id")
            ->join("mw_product on mw_product.id = mw_order_info.product_id")
            ->field('mw_order_list.id,mw_order_list.states,mw_order_list.order_no,mw_order_list.name,mw_order_list.tel,mw_order_list.address,mw_order_list.total,mw_product.thum_url,mw_product.title,mw_product.img_url,mw_order_info.pro_title,mw_order_info.qty,mw_order_info.shop_price,mw_order_info.total as shop_total_price')
            ->select();
        //查询订单id是否已经入库,入库则调起统一下单返回code_url,模板上面将code_url生成二维码,用户扫码支付
        if ($order) {
            $wx = new WxPayController();
            vendor("wxpay.WxPay#Api");
            //$out_trade_no = "wxpayby574" . date("YmdHis");
            $out_trade_no = $_GET['order_id'];
            $input = new \WxPayUnifiedOrder();
            $input->SetBody($order[0]['title']);
            $input->SetAttach("wwwahai574com");
            $input->SetOut_trade_no($out_trade_no);
            $input->SetTotal_fee($order[0]['total'] * 100);//单位是分
            $input->SetTime_start(date("YmdHis"));
            $input->SetTime_expire(date("YmdHis", time() + 600));
            $input->SetGoods_tag($order[0]['title']);
            //获取微信通知的url
            $input->SetNotify_url("http://www.ahai574.com/Home/Car/wxNotify");
            $input->SetTrade_type("NATIVE");
            $input->SetProduct_id($_GET['order_id']);
            $result = $wx->GetPayUrl($input);
            //dump($result);
            $url = $result["code_url"];
            $this->assign('out_trade_no', $out_trade_no);
            $this->assign('code_url', $url);
            $this->assign("orderInfo", $order);
        } else {
            $this->error("订单不存在或已经完成付款,请勿重复操作!");
        }

        $this->display();
    }

    /**
     * 微信回调通知
     * 数据存在被伪造的可能[非法的xml数据来源,即黑客如果知道这个接口,那么他就能通过订单号进行伪造数据,
     * 所以验证分三步进行,一步一步验证数据的安全]
     * 1.先查看数据中的订单是否存在或者已经付款,那么就直接返回false
     * 这里可以返回xml数据,假如来源是微信,
     * 那么就告诉微信我已经知道用户付款了,不用再向我发起请求;
     * 不返回xml数据,那么微信就会
     * 在15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m
     * 向我们发起10次请求通知我们用户已经付款
     * 下面的方法直接返回false,那么正常情况下,我们会收到10次异步通知
     * 2.1验证携带的参数,我们自定义的参数,如果错误--则不用再进行验证签名函数
     * 这一步其实也可以不用的,只是为了减少错误的数据的执行流程,一旦出错那么就直接返回false
     * 2.2开始验证签名的正确性,
     * xml中所有的数据,除了sign值,其他按照ksort()排序,
     * 再通过http_build_query组合成新的字符串
     * 然后再加上支付密钥组成新的字符串
     * 之后将字符串加密:md5和sha256 [具体看你的配置值使用哪种加密方式]
     * 3.确认签名正确,正式更新订单的状态
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function wxNotify()
    {
        //获取返回的xml
        $testxml = file_get_contents("php://input");
        //将xml转化为json格式
        $jsonxml = json_encode(simplexml_load_string($testxml, 'SimpleXMLElement', LIBXML_NOCDATA));
        //转成数组
        $result = json_decode($jsonxml, true);
        if ($result) {

            //如果成功返回了
            vendor("wxpay.log");
            $logHandler = new \CLogFileHandler(LOG_PATH . "/Wxpay/" . date('Y-m-d') . 'wxnotify.log');
            \Log::Init($logHandler, 15);
            \Log::INFO(json_encode(['time'=>date("Y-m-d H:i:s",time()),'data'=>$result,'ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
            if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
幸运28全民计划软件                //进行改变订单状态等操作。。幸运28全民计划软件。。 ['return_msg'=>['transaction_id','out_trade_no','attach','total_fee']]
                //TODO: 1.检查订单的支付状态,如果以付款则,直接返回xml数据,或者直接返回false|true
                $_where = ['order_no' => $result['out_trade_no'], 'states' => 4];
                $order = M("order_list")->where($_where)->find();

                if (!$order) {
                    //写入日志-$result['out_trade_no']
                    \Log::WARN(json_encode(['wxNotify-TODO-1' => '订单号:' . $result['out_trade_no']
                        . "不存在或已经付款", 'ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
                    return false;
                }

                //TODO: 2校验签名--保障数据是来源微信的服务器;
                //TODO: 2.1:校验参数----查看return_msg中是否有我们发送给微信服务器端的数据attach[商家数据包]
                if (isset($result['attach']) && $result['attach'] != "wwwahai574com") {
                    //写入日志-attach数据包错误
                    \Log::ERROR(json_encode(['wxNotify-TODO-2.1' => "attach数据包错误", 'ip' => get_client_ip()]));
                    return false;
                }

                //TODO: 2.2:验证签名
                //获取返回的所以参数
                //这里是要把微信返给我们的所有值,先删除sign的值,其他值 按ASCII从小到大排序;
                $xmlSign = "我是默认的sign,如果没有获取到微信返回的sign值,那么就写入日志";
                foreach ($result as $k => $v) {
                    if ($k == 'sign') {
                        $xmlSign = $result[$k];
                        unset($result[$k]);
                    };
                }
                //排序
                ksort($result);
                $sign = http_build_query($result);
                //md5处理
                vendor("wxpay.WxPay#Config");
                $config = new \WxPayConfig();
                $string = $sign . '&key=' . $config->GetKey();
                //加密的方式:下面2选1 Wxpay.Config.php中的配置是HMAC-SHA256,所以不用md5加密
                //如果是“MD5”,则:$sign = md5($string);
                $sign = hash_hmac("sha256",$string ,$config->GetKey());
                //转大写
                $sign = strtoupper($sign);
                //\Log::INFO(json_encode(['string'=>$string,'sign'=>$sign],JSON_UNESCAPED_UNICODE));
                //验签名。默认支持MD5
                //验证加密后的32位值和 微信返回的sign 是否一致!!!
                if ($sign === $xmlSign) {
                    //TODO: 3修改订单为付款状态
                    \Log::INFO(json_encode(['wxNotify-TODO-3'=>'验证签名正确,开始更新订单的状态','string'=>$string,'sign'=>$sign],JSON_UNESCAPED_UNICODE));
                    //更新订单状态,本来应该给微信返回值说明支付成功的,不返回值,微信就只会触发多次请求,
                    //反正查询条件必须是未支付的订单才会改状态,所以多次请求,只有第一次真正成功才会写入数据库
                    $data['states'] = 0;
                    $data['completetime'] = date("Y-m-d H:i:s", time());
                    M("order_list")->where($_where)->save($data);
                    //\Log::INFO(json_encode(['wxNotify-tody-3'=>'验证签名正确,结束更新订单的状态'],JSON_UNESCAPED_UNICODE));
                }else{
                    \Log::ERROR(json_encode(['wxNotify-TODO-3' => "签名错误",'xml中的sign'=>$xmlSign,'xml组成的sign'=>$sign, '访问者ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
                }

            }
        }
    }


    //查询订单支付状态---虽然微信的交易号优先,但是微信的交易号需要通过异步通知才能获取到,
    //异步查询订单状态:传值是商家自己的交易号【订单号】
    public function orderQuery()
    {
        if(empty($this->udata)){
            $this->redirect('Login/index');
        }
        if ((isset($_REQUEST["transaction_id"]) && $_REQUEST["transaction_id"] != ""
                && !preg_match("/^[0-9a-zA-Z]{10,64}$/i", $_REQUEST["transaction_id"], $matches))
            || (isset($_REQUEST["out_trade_no"]) && $_REQUEST["out_trade_no"] != ""
                && !preg_match("/^[0-9a-zA-Z]{10,64}$/i", $_REQUEST["out_trade_no"], $matches))) {
            $this->ajaxReturn(['result' => false, 'errorMsg' => '微信订单号和商家订单号均为空!']);
            exit();
        }
        vendor("wxpay.WxPay#Api");
        vendor("wxpay.WxPay#Config");
        vendor("wxpay.log");
        $logHandler = new \CLogFileHandler(LOG_PATH . "/Wxpay/" . date('Y-m-d') . 'orderQuery.log');
        \Log::Init($logHandler, 15);


        if (isset($_REQUEST["out_trade_no"]) && $_REQUEST["out_trade_no"] != "") {
            try {
                $out_trade_no = $_REQUEST["out_trade_no"];
                $input = new \WxPayOrderQuery();
                $input->SetOut_trade_no($out_trade_no);
                $config = new \WxPayConfig();
                $data = \WxPayApi::orderQuery($config, $input);
                //如果查询成功,验证订单号,总价格是否正确,正确则改状态,前端再进行跳转
                \Log::INFO(json_encode(['orderQueryTime'=>date("Y-m-d H:i:s",time()),'msg'=>$data,'ip'=>get_client_ip()],JSON_UNESCAPED_UNICODE));
                if($data['return_code'] == 'SUCCESS' && isset($data['trade_state']) && $data['trade_state'] == 'SUCCESS')
                {
                    $result = $this->changeOrderStatus($data['out_trade_no']);
                    if($result['result']){
                        //\Log::INFO(json_encode(['paidTime'=>date("Y-m-d H:i:s",time()),'msg'=>'订单支付完成','ip'=>get_client_ip()],JSON_UNESCAPED_UNICODE));
                        $this->ajaxReturn(['result' => true, 'msg' => "订单已经付款"]);
                    }else{
                        $this->ajaxReturn(['result' => false, 'msg' => "找不到订单"]);
                    }
                }else{
                    $this->ajaxReturn(['result' => false, 'msg' => "订单未支付!"]);
                }
            } catch (Exception $e) {
                \Log::ERROR(json_encode($e,JSON_UNESCAPED_UNICODE));
                $this->ajaxReturn(['result' => false, 'errorMsg' => '微信查询失败!']);
            }
            exit();
        }
        $this->ajaxReturn(['result' => false, 'msg' => "订单号不能为空!"]);
    }

    //查询订单是 支付状态的时候,修改订单的付款状态
    //
    private function changeOrderStatus($order_no)
    {
        $_where['order_no'] = $order_no;
        $find = M("order_list")->where($_where)->find();
        if($find){
            $_where['status'] = 4;
            $data['states'] = 0;
            $data['completetime'] = date("Y-m-d H:i:s", time());
            $order = M("order_list")->where($_where)->save($data);
            if($order){
                return ['result'=>true,'msg'=>'订单完成更新!'];
            }else{
                return ['result'=>true,'msg'=>'订单已经更新了!'];
            }
        }else{
            return ['result'=>false,'msg'=>"查找不到订单!"];
        }

    }

    /**
     * 把url生成二维码
     * @throws \WxPayException
     */
    public function buildQrcode()
    {
        $url = urldecode($_GET['url']);
        if (substr($url, 0, 6) == "weixin") {
            Vendor("phpqrcode.phpqrcode");
            $level = 3;
            $size = 4;
            $errorCorrectionLevel = intval($level);//容错级别
            $matrixPointSize = intval($size);//生成图片大小
            \QRcode::png($url, false, $errorCorrectionLevel, $matrixPointSize, 2);
        } else {
            header('HTTP/1.1 404 Not Found');
        }
    }
题外话:老板把以前别人写的网站【tp3】,让我接入微信支付,然后客户要使用第三方的支付,然后第三方支付只提供一个word的文档,好吧,按照流程写了之后,一直是签名错误,而签名的方式和微信的签名类似,搞了一天,任然是报签名错误,第三方,又是由客户自己提供的【完全不知道是谁的,没有官网,只能靠猜测排查问题--第三方不提供服务,只提供文档^V^】,最后想起了之前有一位同事说他之前遇到一个网站是asp做的,然后登录使用的加密方式是md5【16位】,而php的md5是32位;然后下班之后,突然想起,第三方使用的是java的md5,于是百度查了一些java的md5发现java与php的md5是不一样的,只能发问题给老板是不是md5的问题,让老板->客户->第三方->客户->老板->我,然后....,今天先记录这些。
最佳答案
评论( 相关
后面还有条评论,点击查看>>