微信公众号支付

主流程

  1. 微信公众号支付课程介绍
  2. 开发微信支付的前期准备
  3. 微信公众号支付流程分析
  4. 微信签名算法分析与实战
  5. 如何获取用户Openid
  6. 统一下单API原理分析与实现
  7. 构建JSAPI支付请求
  8. 支付通知的处理

2. 微信支付前期的准备

公众平台  https://mp.weixin.qq.com/

商户平台  https://pay.weixin.qq.com/

1. 开通微信支付的流程

2.配置授权目录和域名

3. 获取开发相关的配置信息

 

开发者配置信息

开发者ID APPID公众平台—>开发->基本配置 可查看(无法修改)
开发者密码 SECRET公众平台 -> 开发 -> 基本配置 可设置或者重置
商户号 MCHID商户平台 -> 产品中心 ->开发配置中可查看
商户支付秘钥 KEY商户平台 -> 账户中心 -> API安全中设置
证书文件商户平台 -> 账户中心 -> API安全 下载证书

3. 微信公众号支付流程分析

开发文档: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1

微信支付流程: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4


4. 微信签名算法分析与实战

统一下单 api: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

源码放在 tp5中

Wxbase.php

<?php
// +----------------------------------------------------------------------
// | User: zq
// +----------------------------------------------------------------------
// | Time: 2020/3/22 2:01 下午
// +----------------------------------------------------------------------
namespace app\api\controller;

use think\Controller;
use think\Db;
use think\Request;

class Wxbase extends Controller
{
    # 微信签名校验类
    private $appid  = 'xxx';
    private $key    = "xxx";
    private $mch_id = 'xxx';

    /*
     * $arr = ['appid'=>'dfggg', 'mch_id'=>'sdgfgd', 'body'=>'88333']
     */
    //解决签名中有中文的情况
    public function arrToUrl($arr)
    {
        return urldecode(http_build_query($arr));
    }

    //将一个不带签名的数组设置上签名
    public function setSign($arr)
    {
        //1. 获取签名
        $sign = $this->getSign($arr);
        //2. 加上签名
        $arr['sign'] = $sign;
        //3. 返回数组
        return $arr;
    }

    //生成签名
    public function getSign($arr)
    {
        //1. 对参数按照key=value的格式,并按照参数名ASCII字典序排序生成字符串
        //1.1 去除空值
        $arr = array_filter($arr);
        //1.2 如果其中含有签名,则要去掉签名
        if (isset($arr['sign'])) {
            unset($arr['sign']);
        }
        //1.3 按照参数名ASCII字典序排序生成字符串
        ksort($arr);
        //1.4 生成url格式的字符串,防止里面有中文,所以单独写了一个方法
        $str = $this->arrToUrl($arr);
        //2. 连接商户key
        $str = $str . '&key=' . $this->key;
        //3. 生成sign并转成大写,并返回签名
        return strtoupper(md5($str));

    }



    //验证签名, 验证签名接受的数组,比之前生成签名的数组多了一项(会在数组最后,这一项就是签名)
    /*
     * $arr = [
     *        ...
     *        'sign'   => 'C9B6CE4BD1307D74B5D98A247DA0F27B'
     *   ];
     */
    //我们在得到签名的函数中,已经做了处理,如果有签名,把签名去掉,所以可以直接获取新的签名和数组中的最后一项签名做对比

    public function checkSign($arr)
    {
        //1. 生成签名
        $sign = $this->getSign($arr);
        //2. 验证签名
        if ($sign == $arr['sign']) {
            return true;
        } else {
            return false;
        }
    }

}

Wxpay.php

<?php
// +----------------------------------------------------------------------
// | User: zq
// +----------------------------------------------------------------------
// | Time: 2020/3/22 2:12 下午
// +----------------------------------------------------------------------
namespace app\api\controller;

use app\api\controller\Wxbase;

use think\Db;
use think\Request;

class Wxpay extends Wxbase
{
    public function __construct(Request $request = null)
    {
        parent::__construct($request);
    }

    public function testpay()
    {
        $arr = [
            'appid'  => 'dfggg',
            'mch_id' => 'sdgfgd',
            'body'   => '88333'
        ];
        //设置签名
        $arr = $this->setSign($arr);
        //验证签名
        dump($this->checkSign($arr));
    }


}

5. 如何获取用户Openid

  1. 获取用户openid
  2. 构建原始数据
  3. 加入签名
  4. 调用统一下单API
  5. 获取到prepay_id

文档: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#0

这里只需要openid,所以用 静默(snsapi_base)的方式即可。

如下代码,这里用了一个很巧妙的地方,就是用 code做判断,如果有则获取到openid,如果没有,先去获取code,然后跳转到自身地址,又会做 code判断,这时候有了,就直接获取openid了。

//获取opeind
//文档 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#0
public function getOpenId(Request $request = null)
{
    //1. 如果session中存在了openid,则直接返回, 如果不存在则去获取openid
    if (input('session.openid')) {
        return input('session.openid');
    } else {
        // 2. 静默获取openid
        // 2.1 用户访问一个地址先获取code (页面有跳转)
        // 2.2 根据code获取到openid
        if (!input('get.code')) {
            //构建跳转地址 跳转
            // 获取当前地址,下面的redirect_uri,就是访问下面的$url会跳转到当前地址来
            $request = Request::instance();
            $self_url = $request->url(True);
            // 拼接微信api文档中指定的地址
            $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' . $this->appid . '&redirect_uri=' . $self_url . '&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect';
            // 跳转
            header('location:' . $url);
        } else {
            //调用接口获取 openid
            $code_url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' . $this->appid . '&secret=' . $this->key . '&code=' . input('get.code') . '&grant_type=authorization_code';
            //请求这个地址,返回一个含有openid的数组
            $data = http_curl($code_url);
            //把openid 保存到session中
            input('session.openid', $data['openid']);
            //返回openid
            return $data['openid'];
        }

    }


}

 


6. 统一下单API原理分析与实现

文档: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

  1. 构建原始数据
  2. 加入签名
  3. 将数据转为 xml
  4. 发送xml格式的数据到接口地址
//调用统一下单api
public function unifiedOrder()
{
    /*
     * 构建原始数据
     * 加入签名
     * 将数据转为 xml
     * 发送xml格式的数据到接口地址
     */
    //1.构建原始数据
    $parms = [
        'appid'            => $this->appid,  //公众号id
        'mch_id'           => $this->mch_id, //商户id,
        'nonce_str'        => md5(time()),          //随机字符串
        'body'             => '公众号测试',               //商品描述
        'out_trade_no'     => '1119',       //商户订单号
        'total_fee'        => '2',           //标价金额
        'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],    //终端IP
        'notify_url'       => url('notify'),         //通知地址
        'trade_type'       => 'JSAPI',         //交易类型
        'product_id'       => '1119',          //商品ID
        'openid'           => $this->getOpenId()
    ];
    //2.加入签名
    $parms = $this->setSign($parms);
    //3.将数据转为 xml
    $xmldata = ArrToXml($parms);
    //4.发送xml格式的数据到接口地址
    $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; //统一下单api地址
    //5. 返回的结果如下,也是xml格式的数据
    $resdata = postXml($url, $xmldata);
    //6. 把xml转为数组,prepay_id就在数组中
    $resdata = XmlToArr($resdata);
    dump($resdata);
}

上面的 商品订单号,和商品id,都是自己定义的。特别是商品订单号,在自己的项目中,要建立数据表,先新建一个商品订单号,然后再进行下面操作。


7. 构建JSAPI支付请求

微信内H5调起支付: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6

//获取公众号支付所需要的json数据
public function getJsParms($prepay_id)
{
    $params = [
        'appId'     => $this->appid,
        'timeStamp' => '"'.time().'"',
        'nonceStr'  => md5(time()),
        'package'   => 'prepay_id=' . $prepay_id,
        'signType'  => 'MD5'
    ];
    //获取签名
    $params['paySign'] = $this->getSign($params);
    //返回一个json数据
    return json_encode($params);

}

构建JSAPI支付请求

<script>
    function onBridgeReady(){
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest', {$json},
            function(res){
                if(res.err_msg == "get_brand_wcpay_request:ok" ){
                    // 使用以上方式判断前端返回,微信团队郑重提示:
                    //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
                }
            });
    }
    if (typeof WeixinJSBridge == "undefined"){
        if( document.addEventListener ){
            document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
        }else if (document.attachEvent){
            document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
            document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
        }
    }else{
        onBridgeReady();
    }
</script>

8. 支付通知的处理

  1. 获取通知数据 -> 转换为数组
  2. 验证签名 (使用以前的方法)
  3. 验证业务结果(return_code 和 result_code)
  4. 验证订单号和金额 (out_trade_no total_fee)
  5. 记录日志 修改订单状态,给用户发货
#支付成功之后的通知,支付成功之后会返回到这个地址,得到的数据是xml格式的.
/*
 * 1.获取通知数据 -> 转换为数组
 * 2.验证签名 (使用以前的方法)
 * 3.验证业务结果(return_code 和 result_code)
 * 4.验证订单号和金额 (out_trade_no total_fee)
 * 5.记录日志 修改订单状态,给用户发货
 */
public function notify()
{
    //1.获取通知数据 -> 转换为数组
    $xmlData = $this->getPost();
    file_put_contents('info1.txt', $xmlData . "\n", FILE_APPEND);
    $arr = XmlToArr($xmlData);
    //2.验证签名
    if ($this->checkSign($arr)) {
        //3.验证业务结果(return_code 和 result_code)
        if ($arr['return_code'] == 'SUCCESS' && $arr['result_code'] == 'SUCCESS') {
            //成功
            //4. 验证订单号和金额 (out_trade_no total_fee)
            // 这里是通过数据库查询订单号,得到金额和返回的金额对比,再进行操作.
            // 这里有一个问题,我看文档的返回数据中,貌似没有上面2个值...

            //返回结果给微信服务器
            $returnparams = [
                'return_code' => 'SUCCESS',
                'return_msg'  => 'OK'
            ];
            echo ArrToXml($returnparams);
        } else {
            //失败
            return '支付失败';
        }
    } else {
        //签名不成功
        return '签名失败';
    }
}
//获取post过来的数据
public function getPost()
{
    return file_get_contents('php://input');
}

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值