微信支付(JsApi)总结

个人认为最佳的学习方式是抛开内容繁重的文档,先去https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1 下载个php的demo 边阅读源码变看文档 这种方式我觉得最有效 顺便吐槽下Java居然只有刷卡支付的栗子。。。

这篇博文不会很详细的介绍demo的源码 其实我们有时候业务并不需要使用微信支付的全部功能

还有例如Wxpay.Data.php这个文件 这个文件接近3000行
其实就是维护一个内部数组

protected $values = array();

同设置get set为这个数组设置参数,例如签名等等 大约有几十个参数
最后将这个数组转为xml等等给微信服务器做交互等等 如果要全部说清楚 那么你和看微信文档没什么区别 反而难上手

如果你用的是php 那么你把这个demo作为sdk我觉得是最快的方法,然后再阅读下本博文,基本就可以做出微信支付
如果你用的是java等语言 那么阅读完本博文之后还需继续啃下各个签名算法,然后再啃啃文档。所以一定程度上说,php是最好的语言,也不无道理哈哈(不喜勿喷),可能因为实现的不方便,微信才没有给出jsapi的Java实现版吧

1,设置基本的参数 在Wxpay.Config.php里可以看到

如果你没有认证的可以微信支付的公众号 不要紧 可以使用demo自带的四个参数 支付的钱是转到微信测试账号里 金额设置为0.01即可 土豪随意

有四个参数要设置 这里就不说了

//=======【基本信息设置】=====================================
    //
    /**
     * TODO: 修改这里配置为您自己申请的商户信息
     * 微信公众号信息配置
     * 
     * APPID:绑定支付的APPID(必须配置,开户邮件中可查看)
     * 
     * MCHID:商户号(必须配置,开户邮件中可查看)
     * 
     * KEY:商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置)
     * 设置地址:https://pay.weixin.qq.com/index.php/account/api_cert
     * 
     * APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置),
     * 获取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
     * @var string
     */

2,认证并获取获取用户openid

通过向微信公众平台认证 获取一个code 通过这个code来换取用户的openid
openid其实就是微信用户在你的公众号上的唯一标识
demo的源码如下
首先检测你当前(也就是你要执行支付的页面)有没有收到get参数
如果有的话 就通过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;
        }
    }

如果没有get到code的话 将当前(或者你要执行支付页面的其他url)拼接成一个baseurl
调用__CreateOauthUrlForCode方法 并跳转到生成出来的url
我们来看看生成认证url的方法

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;
    }

其实就是将一段参数拼接后 访问到微信的认证url
注意redirect_uri 就是你上一步传入的baseurl
你跳转到微信的认证url后 微信会经过认证 考核你的各种条件(公众号有没有这个权限啊,网页授权有没有开啊等等)
认证成功后 会重定向回我们的baseurl 并带上一个get参数 ?code=xxxx
然后我们这时我们再进行下一步,就有code可以用了 走有code的路线

看下GetOpenidFromMp 就是用code换openid的方法
和__CreateOauthUrlForOpenid即拼接获取openidurl的方法

其实都很简单 拼接参数 拼成你要的url 然后curl到微信需要的url 设置参数 发送请求 获取后获得code

private function __CreateOauthUrlForOpenid($code)
    {
        $urlObj["appid"] = WxPayConfig::APPID;
        $urlObj["secret"] = WxPayConfig::APPSECRET;
        $urlObj["code"] = $code;
        $urlObj["grant_type"] = "authorization_code";
        $bizString = $this->ToUrlParams($urlObj);
        return "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString;
    }

public function GetOpenidFromMp($code)
    {
        $url = $this->__CreateOauthUrlForOpenid($code);
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->curl_timeout);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,FALSE);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        if(WxPayConfig::CURL_PROXY_HOST != "0.0.0.0" 
            && WxPayConfig::CURL_PROXY_PORT != 0){
            curl_setopt($ch,CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
            curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
        }
        //运行curl,结果以jason形式返回
        $res = curl_exec($ch);
        curl_close($ch);
        //取出openid
        $data = json_decode($res,true);
        $this->data = $data;
        $openid = $data['openid'];
        return $openid;
    }

需要注意一个问题 微信给出的demo只是一次性使用的 所以我一开始看忽略了一个问题
你们需要记住: 认证后获得的code只可使用一次 用完就失效 所以获取openid后应该保存起来 建议是放到session里

3,设置统一下单参数

$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);

这里我觉得需要说下的就是Attach和notifyurl
attach就是放你这个订单里,你要的自定义信息,例如商品id等等
notifyurl 就是支付成功后 微信服务器会向你的这个url放出请求,并带上attach等等参数 然你对用户购买成功后进行一系列逻辑操作

4,生成给前端的订单信息

就是调两个方法 一个根据你刚才设置的参数生成订单
另一个就是根据这个订单生成给前端的js参数
如果你用的是php 可以直接把这个两个方法当sdk用 里面包含了很多签名和加密的操作,看起来有点恶心,不过你看下文档就能知道 别人封装好了 你当然用就是
如果你用的是Python或者Java 那么恭喜你 你还得去看它们的源码并自己实现一遍

$order = WxPayApi::unifiedOrder($input);
$jsApiParameters = $tools->GetJsApiParameters($order);

5,前端根据生成的js参数唤起支付页面

我没做地址的获取 所以我这里跳过地址获取

首先要注意一个问题 微信内置对象WeixinJSBridge 不是一点进页面就生成好的 网速不好的时候 经常会是undefined 所以需要如下代码

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();
        }
    }

jsApiCall就是唤起支付的核心

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);
            }
        );
    }

如果你不是用后端渲染的话 把前面的步骤做成api,让前端调一下就可以获得 $jsApiParameters 然后赋到函数里面去 而不用直接在js里echo 看着有点不优雅

剩下的事就暂时不用你管了,交给用户输密码然后剁手 最后你还要做的一件事就是
notifyurl

开发支付成功后微信异步回调的接口

notify.php中内置了一个内部类 并继承自WxPayNotify
class PayNotifyCallBack extends WxPayNotify
然后实例了这个内部类并且调用了他的handle方法

$notify = new PayNotifyCallBack();
$notify->Handle(false);

我们来看下父类WxPayNotify的handle方法
忽略掉签名等操作

/**
     * 
     * 回调入口
     * @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);
    }

来看看WxpayApi::notify这个方法
首先把微信支付成功后发给你的xml获取并解析 里面有你设置的订单信息 attach等等
最后使用call_user_func( callback, result);这个方法我觉得有点像反射
他会执行NotifyCallBack方法 并把xml解析后的数据作为参数传入

/**
     * 
     * 支付结果通用通知
     * @param function $callback
     * 直接回调函数使用方法: notify(you_function);
     * 回调类成员函数方法:notify(array($this, you_function));
     * $callback  原型为:function function_name($data){}
     */
    public static function notify($callback, &$msg)
    {
        //获取通知的数据
        $xml = $GLOBALS['HTTP_RAW_POST_DATA'];
        //如果返回成功则验证签名
        try {
            $result = WxPayResults::Init($xml);
        } catch (WxPayException $e){
            $msg = $e->errorMessage();
            return false;
        }

        return call_user_func($callback, $result);
    }

再来看看NotifyCallBack

/**
     * 
     * 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;
    }

他会执行NotifyProcess根据执行后的结果 设置返回参数为成功或者失败 返回参数等下是要返回给微信服务器的
而NotifyProcess 就是处理里自己核心购买业务的地方 需要你自己重写

/**
     * 
     * 回调方法入口,子类可重写该方法
     * 注意:
     * 1、微信回调超时时间为2s,建议用户使用异步处理流程,确认成功之后立刻回复微信服务器
     * 2、微信服务器在调用失败或者接到回包为非确认包的时候,会发起重试,需确保你的回调是可以重入
     * @param array $data 回调解释出的参数
     * @param string $msg 如果回调处理失败,可以将错误信息输出到该方法
     * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调
     */
    public function NotifyProcess($data, &$msg)
    {
        //TODO 用户基础该类之后需要重写该方法,成功的时候返回true,失败返回false
        return true;
    }

返回false时微信还会继续回调,我理解一般是微信传的的参数有误什么的才回造成你处理失败,一直返回false给他,他也应该不会无限次回调

重新回到handle方法 你会看到
根据你自己处理回调参数,处理业务后 还需要返回信息给微信服务器 最长返回时间是2秒 也就是2秒内你必须告诉微信yes or no

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);

最后是ReplyNotify方法

/**
     * 
     * 回复通知
     * @param bool $needSign 是否需要签名输出
     */
    final private function ReplyNotify($needSign = true)
    {
        //如果需要签名
        if($needSign == true && 
            $this->GetReturn_code($return_code) == "SUCCESS")
        {
            $this->SetSign();
        }
        WxpayApi::replyNotify($this->ToXml());
    }

而replyNotify 就是一句echo 告诉微信服务器

/**
     * 直接输出xml
     * @param string $xml
     */
    public static function replyNotify($xml)
    {
        echo $xml;
    }

微信支付到这里就告一段落了

我之前做项目的时候用的是php ,所以我没深究签名等等 只需掌握上述的基本流程,借助demo作为sdk即可完成微信支付开发 这是最快的方式

如果你用的是Java或者其他没提供签名方法的语言 就还需要再继续阅读签名等方法 然后再啃啃文档 以后有空我写一个springmvc的sdk出来玩哈哈

猜你喜欢

转载自blog.csdn.net/we_phone/article/details/69824924