微信公众号支付开发细节

开发背景

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

开发流程

     一、微信支付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="width: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

 

 

 

posted @ 2017-04-11 17:58  泥土里的绽放  阅读(2666)  评论(1编辑  收藏  举报