zoukankan      html  css  js  c++  java
  • 微信公众号支付开发细节

    开发背景

      微信长按二维码识别支付的功能被取消了,所以项目改为使用公众号支付。默认读者已经获得 微信公众号的开发权限和在微信开通了商户号

    开发流程

         一、微信支付DEMO功能简要介绍

       微信平台上有支付的DEMO,我們可以下載下來直接使用。下面我們來分析一下DEMO中主要文件的作用。

          example/jsapi.php主要是微信公众号支付

                example/native.php主要是微信扫码支付,其中包括模式一和模式二。(模式一:先扫码,再生成订单。模式二:先生成订单,再扫码)

                example/notify.php主要是用来处理回调信息的。为什么要处理回调信息呢?我们可以这样的思考:当我们支付成功之后,微信肯定要告诉我们支付是否      成功。那么微信要告诉谁呢?要告诉那个函数呢?这个就需要我们自己来设置了,我们给微信一个回调地址,那么微信就会把支付结果信息发送到这个地址上去。光      接收到信息是不够的,我们呢,还需要函数来处理这些信息。notify.php里面就包括了处理函数。

         

        二、微信公众号支付及其流程 

               我先从头到尾的按照线性顺序来屡一下流程吧。具体的可以看下图:

            

                  要用微信公众号支付,首先我们要做的就是调用jsapi.php。

         jsapi.php具体内容如下:一些地方添加有注释帮助大家理解

    <?php 
    ini_set('date.timezone','Asia/Shanghai');
    //error_reporting(E_ERROR);
    //这个是引用所需的文件
    require_once "../lib/WxPay.Api.php"; require_once "WxPay.JsApiPay.php"; require_once 'log.php'; //初始化日志 $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log'); $log = Log::Init($logHandler, 15); //打印输出数组信息 function printf_info($data) { foreach($data as $key=>$value){ echo "<font color='#00ff55;'>$key</font> : $value <br/>"; } } //①、获取用户openid $tools = new JsApiPay(); $openId = $tools->GetOpenid(); //②、统一下单 $input = new WxPayUnifiedOrder(); $input->SetBody("test");//一般填写用户的订单号 $input->SetAttach("test");//一般填写用户的订单号 $input->SetOut_trade_no(WxPayConfig::MCHID.date("YmdHis"));//填写用户的订单号 $input->SetTotal_fee("1");//支付费用 $input->SetTime_start(date("YmdHis")); $input->SetTime_expire(date("YmdHis", time() + 600)); $input->SetGoods_tag("test"); $input->SetNotify_url("http://paysdk.weixin.qq.com/example/notify.php");//刚才讲的回调函数所在的地址,要自己设定 $input->SetTrade_type("JSAPI"); $input->SetOpenid($openId); // $order = WxPayApi::unifiedOrder($input); echo '<font color="#f00"><b>统一下单支付单信息</b></font><br/>'; printf_info($order);//打印订单的详细信息 $jsApiParameters = $tools->GetJsApiParameters($order); //获取共享收货地址js函数参数 $editAddress = $tools->GetEditAddressParameters(); //③、在支持成功回调通知中处理成功之后的事宜,见 notify.php /** * 注意: * 1、当你的回调地址不可访问的时候,回调通知会失败,可以通过查询订单来确认支付是否成功 * 2、jsapi支付时需要填入用户openid,WxPay.JsApiPay.php中有获取openid流程 (文档可以参考微信公众平台“网页授权接口”, * 参考http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html) */ ?> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>微信支付样例-支付</title> <script type="text/javascript"> //调用微信JS api 支付 function jsApiCall() { WeixinJSBridge.invoke( 'getBrandWCPayRequest', <?php echo $jsApiParameters; ?>, function(res){ WeixinJSBridge.log(res.err_msg); alert(res.err_code+res.err_desc+res.err_msg); } ); }
      //点击立即支付的时候执行的函数
    function callpay() { if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', jsApiCall, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', jsApiCall); document.attachEvent('onWeixinJSBridgeReady', jsApiCall); } }else{ jsApiCall(); } } </script> <script type="text/javascript"> //获取共享地址 function editAddress() { WeixinJSBridge.invoke( 'editAddress', <?php echo $editAddress; ?>, function(res){ var value1 = res.proviceFirstStageName; var value2 = res.addressCitySecondStageName; var value3 = res.addressCountiesThirdStageName; var value4 = res.addressDetailInfo; var tel = res.telNumber; alert(value1 + value2 + value3 + value4 + ":" + tel); } ); } //这段代码的功能是在支付之前,弹出一个地址填写框 window.onload = function(){ if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', editAddress, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', editAddress); document.attachEvent('onWeixinJSBridgeReady', editAddress); } }else{ editAddress(); } }; </script> </head> <body> <br/> <font color="#9ACD32"><b>该笔订单支付金额为<span style="color:#f00;font-size:50px">1分</span>钱</b></font><br/><br/> <div align="center"> <button style="210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer; color:white; font-size:16px;" type="button" onclick="callpay()" >立即支付</button> </div> </body> </html>

          我们先讲一下获取用户openid这段代码$openId = $tools->GetOpenid();吧。

          //①、获取用户openid
          $tools = new JsApiPay();
          $openId = $tools->GetOpenid();
          JsApiPay是Wxpay.JsApipay.php中的一个类
    $tools = new JsApiPay();这个新建了一个JsApiPay类,然后执行GetOpenid()这个函数。这个函数代码在下面:
        /**
         * 
         * 通过跳转获取用户的openid,跳转流程如下:
         * 1、设置自己需要调回的url及其其他参数,跳转到微信服务器https://open.weixin.qq.com/connect/oauth2/authorize
         * 2、微信服务处理完成之后会跳转回用户redirect_uri地址,此时会带上一些参数,如:code
         * 
         * @return 用户的openid
         */
        public function GetOpenid()
        {
            //通过code获得openid
            if (!isset($_GET['code'])){
                //触发微信返回code码
                $baseUrl = urlencode('http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].$_SERVER['QUERY_STRING']);
                $url = $this->__CreateOauthUrlForCode($baseUrl);
                Header("Location: $url");
                exit();
            } else {
                //获取code码,以获取openid
                $code = $_GET['code'];
                $openid = $this->getOpenidFromMp($code);
                return $openid;
            }
        }

        isset用来判断是不是存在$_GET['code']这个变量,如果不在的话,就执行了if语句,如果存在,就执行了else语句。

        当我们第一次调用GetOpenid()的时候当然不存在$_GET['code']这个变量,所以就执行了if语句。接下来我们看看if语句的内容:

        $baseUrl是用来获取你当前的url,为什么要获取当前的url呢?我们先看这一段代码$url = $this->__CreateOauthUrlForCode($baseUrl);它调用了一个函数,函数的具体内容如下:

    	private function __CreateOauthUrlForCode($redirectUrl)
    	{
    		$urlObj["appid"] = WxPayConfig::APPID;
    		$urlObj["redirect_uri"] = $redirectUrl;
    		$urlObj["response_type"] = "code";
    		$urlObj["scope"] = "snsapi_base";
    		$urlObj["state"] = "STATE"."#wechat_redirect";
    		$bizString = $this->ToUrlParams($urlObj);
    		return "https://open.weixin.qq.com/connect/oauth2/authorize?".$bizString;
    	}
    

        我们可以看到有设置一些变量,比如appid、response_type、scope、state等。appid就是你配置的APPID,$urlObj["response_type"]='code'也就是说它会返回一些值,这些值的类型是code类型,看到这里有没有发现一些奇怪的地方?对,刚才有个if语句判断是否存在$_GET['code']这个变量而现在它给我们返回了code类型的数据。是不是巧合呢?当然不是巧合,别忘了还有一个$urlObj["redirect_uri"] = $redirectUrl;是把我们刚才获取的当前的url作为一个变量传递过去。传到哪里?传到https://api.weixin.qq.com/sns/oauth2/access_token这个地方,然后呢,微信会进行验证,如果你给它的数据没错的话,那么它会返回相关的数据给这个变量$urlObj["redirect_uri"]。

        这样就又跳到了下面这个函数的地方,不过不同的是这次我们有了code,于是我们就通过$openid = $this->getOpenidFromMp($code);这个函数获得到了openID.

        public function GetOpenid()
        {
            //通过code获得openid
            if (!isset($_GET['code'])){
                //触发微信返回code码
                $baseUrl = urlencode('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'].$_SERVER['QUERY_STRING']);
                $url = $this->__CreateOauthUrlForCode($baseUrl);
                Header("Location: $url");
                exit();
            } else {
                //获取code码,以获取openid
                $code = $_GET['code'];
                $openid = $this->getOpenidFromMp($code);
                return $openid;
            }
        }

        获取openID之后,如果前面的配置信息没有什么错误的话,那么我们就会弹出这个页面了。

        这就说明我们做的成功了。

        其中有一点要说明一下,因为它在获取openID的时候,来来回回的跳了好多次,所以$baseUrl 里面的一些参数变量会丢失,所以建议大家自己写url,同时也可传递一些参数。这个问题大家要格外的注意一下

        接着我们再谈谈回调函数的问题:以下是处理回调的函数代码也就是notify.php

    <?php
    ini_set('date.timezone','Asia/Shanghai');
    error_reporting(E_ERROR);
    
    require_once "../lib/WxPay.Api.php"; require_once '../lib/WxPay.Notify.php'; require_once 'log.php'; //初始化日志 $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log'); $log = Log::Init($logHandler, 15); class PayNotifyCallBack extends WxPayNotify { //查询订单 public function Queryorder($transaction_id) { $input = new WxPayOrderQuery(); $input->SetTransaction_id($transaction_id); $result = WxPayApi::orderQuery($input); Log::DEBUG("query:" . json_encode($result)); if(array_key_exists("return_code", $result) && array_key_exists("result_code", $result) && $result["return_code"] == "SUCCESS" && $result["result_code"] == "SUCCESS") { return true; } return false; } //重写回调处理函数 public function NotifyProcess($data, &$msg) { Log::DEBUG("call back:" . json_encode($data)); $notfiyOutput = array(); if(!array_key_exists("transaction_id", $data)){ $msg = "输入参数不正确"; return false; } //查询订单,判断订单真实性 if(!$this->Queryorder($data["transaction_id"])){ $msg = "订单查询失败"; return false; } return true; } } Log::DEBUG("begin notify"); $notify = new PayNotifyCallBack(); $notify->Handle(false);

        我们直接看$notify->Handle(false);这个是回调函数的入口。但是我们在这段代码中并没有找到handle()函数,这是为什么呢?因为PayNotifyCallBack类继承了WxPayNotify类,所以我们到lib/WxPay.Notify.php中WxPayNotify中去找就可以了。

        其中代码如下:

    <?php
    /**
     * 
     * 回调基础类
     * @author widyhu
     *
     */
    class WxPayNotify extends WxPayNotifyReply
    {
        /**
         * 
         * 回调入口
         * @param bool $needSign  是否需要签名输出
         */
        final public function Handle($needSign = true)
        {
            $msg = "OK";
            //当返回false的时候,表示notify中调用NotifyCallBack回调失败获取签名校验失败,此时直接回复失败
            $result = WxpayApi::notify(array($this, 'NotifyCallBack'), $msg);
            if($result == false){
                $this->SetReturn_code("FAIL");
                $this->SetReturn_msg($msg);
                $this->ReplyNotify(false);
                return;
            } else {
                //该分支在成功回调到NotifyCallBack方法,处理完成之后流程
                $this->SetReturn_code("SUCCESS");
                $this->SetReturn_msg("OK");
            }
            $this->ReplyNotify($needSign);
        }
        
        /**
         * 
         * 回调方法入口,子类可重写该方法
         * 注意:
         * 1、微信回调超时时间为2s,建议用户使用异步处理流程,确认成功之后立刻回复微信服务器
         * 2、微信服务器在调用失败或者接到回包为非确认包的时候,会发起重试,需确保你的回调是可以重入
         * @param array $data 回调解释出的参数
         * @param string $msg 如果回调处理失败,可以将错误信息输出到该方法
         * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调
         */
        public function NotifyProcess($data, &$msg)
        {
            //TODO 用户基础该类之后需要重写该方法,成功的时候返回true,失败返回false
            return true;
        }
        
        /**
         * 
         * notify回调方法,该方法中需要赋值需要输出的参数,不可重写
         * @param array $data
         * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调
         */
        final public function NotifyCallBack($data)
        {
            $msg = "OK";
            $result = $this->NotifyProcess($data, $msg);
            
            if($result == true){
                $this->SetReturn_code("SUCCESS");
                $this->SetReturn_msg("OK");
            } else {
                $this->SetReturn_code("FAIL");
                $this->SetReturn_msg($msg);
            }
            return $result;
        }
        
        /**
         * 
         * 回复通知
         * @param bool $needSign 是否需要签名输出
         */
        final private function ReplyNotify($needSign = true)
        {
            //如果需要签名
            if($needSign == true && 
                $this->GetReturn_code($return_code) == "SUCCESS")
            {
                $this->SetSign();
            }
            WxpayApi::replyNotify($this->ToXml());
        }
    }

        可以看到$result = WxpayApi::notify(array($this, 'NotifyCallBack'), $msg);这句话是最关键的,它会调用NotifyCallBack函数,然后NotifyCallBack函数会调用$result = $this->NotifyProcess($data, $msg);函数,其中public function NotifyProcess($data, &$msg);默认是返回true的,我们可以在这个函数里写自己要操作的逻辑,比如把订单插入到数据库中等操作。到此,就完成了整个流程

        那$data这个数据是什么呢?

        {

        "appid":"",

        "attach":"",

        "bank_type":"",

        "cash_fee":"1",

        "fee_type":"CNY",

        "is_subscribe":"Y",

        "mch_id":"",

        "nonce_str":"",

        "openid":"",

        "out_trade_no":"",

        "result_code":"SUCCESS",

        "return_code":"",

        "sign":"",

        "time_end":"20170409184335",

        "total_fee":"1",

        "trade_type":"JSAPI",

        "transaction_id":""}

    那就到此结束啦!!

    2017.4.18日补充:

    我们同时需要在这个地方填写支付授权目录,如果支付授权目录填写错了的话,会报错redirect_uri,具体过程请参考下面的链接文档:

    http://www.cnqn.com/archives/180482.html

    http://www.thinkphp.cn/code/1620.html

     
  • 相关阅读:
    day84
    模型层之单表操作
    Django的模板层
    Django框架导读
    创建Django项目
    名称空间2.0path
    js基础之BOM和DOM
    LG5003 跳舞的线
    20191003 「HZOJ NOIP2019 Round #8」20191003模拟
    LG3092 「USACO2013NOV」No Change 状压DP
  • 原文地址:https://www.cnblogs.com/cjjjj/p/6693198.html
Copyright © 2011-2022 走看看