PHP使用Laravel-Pay组件快速接入微信JSAPI支付(微信公众号支付)

PHP使用Laravel-Pay组件快速接入微信JSAPI支付(微信公众号支付)

本文为系列文章,接下来分别会讲解如何接入微信 与 支付宝 支付并完成支付的保姆式教程,各支付代码示例细节详见页尾链接。


文章目录


前言

本篇介绍微信 H5、JSAPI(公众号)、APP、小程序支付、Native支付(手机扫网站二维码进行支付)、需要的基础资料


一、前期准备与花费明细(仅供参考)

项目名 花费 功能
公众号认证 300 JSAPI支付
微信开放平台认证 300 APP支付、网站扫码支付(Native支付)
微信小程序认证 300 小程序支付
微信商户平台认证 300 H5基础支付

注意:
这里必须要开通的是微信商户平台。剩下的更你的实际业务需求进行开通。

使用composer下载laravel-pay组件

composer require yansongda/pay -vvv

laravel-pay官网地址:https://pay.yansongda.cn/docs/v2/installation.html
laravel-pay Git项目地址:https://github.com/yansongda/pay/tree/v2

二、支付类型

1.微信公众号支付(JSAPI支付)

支付原理:

1.前端获取微信code
2.前端发送code到我方后台换openid
3.带着openid请求我方后台拉起支付
4.前端更具后台返回值判断是否拉起 微信付款页面
5.用户在手机上进行付款操作
6.用户付款结束后,微信服务器向我方服务器发送请求,告诉我们用户已经支付完了
7.我方后台通过支付完成异步回调修改数据库中的订单支付状态值。
8.End

代码如下(示例):

前端html支付示例

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>

<button onclick="wechatPay()">点我拉起微信支付</button>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script>
    var code = getQueryVariable('code');//微信支付code
    var openid;//微信openid

    //1.先看地址栏中是否存在微信给的code
    //微信支付
    if(code){
        //有则直接用code后端换openid
        $.ajax({
            type : "POST", //提交方式
            url : "http://请求后端获取openid路由地址",//路径
            data : {
                '_token':"{{ csrf_token() }}",//laravel防止419错误的
                'code':code//微信的code
            },//数据,这里使用的是Json格式进行传输
            success : function(result) {//返回数据根据结果进行相应的处理
                openid = result.data.openid;//设置openid
                //console.log(result.data.openid);
            }
        });
    }else{
        //没有则先获取公众号 code
        var appid = "公众号appid";//这里填公众号appid
        var redirect_uri = "填获取code后重定向的地址";//授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理,例如:使用php urlencode(https://www.xxx.com/test?order_id=1&user_id=18)
        var state = '';//重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节;
        var url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+ appid +"&redirect_uri="+ redirect_uri +"&response_type=code&scope=snsapi_base&state="+ state +"#wechat_redirect";
        window.location.href = url;//重定向跳转,跳转完成后你会得到code
    }

    /**
     * 截取网址参数方法
     * 用于获取微信code
     * */
    function getQueryVariable(variable)
    {
        var query = window.location.search.substring(1);
        var vars = query.split("&");
        for (var i=0;i<vars.length;i++) {
            var pair = vars[i].split("=");
            if(pair[0] == variable){return pair[1];}
        }
        return(false);
    }

    //请求后台微信支付接口获取支付必要参数
    function wechatPay() {
        if(!open_id){
            alert('500:获取openid失败');
            return false;
        }
        $.ajax({
            type : "POST", //提交方式
            url : "请求我方后台接口,向微信服务器发起支付请求wechatPay.php",//路径
            data : {
                '_token':"{{ csrf_token() }}",//解决laravel 419错误
                "open_id" : open_id,//用户openid
            },//数据,这里使用的是Json格式进行传输
            success : function(res) {//返回数据根据结果进行相应的处理
                //检测请求是否处理成功
                if (res.success) {
                    //拉起微信APP支付(93-99行官方原代码,无需改动)
                    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(res.data);//拉起微信客户端支付,官方代码,请勿随意修改
                    }
                }

                //判断是否显示提示信息
                if(res.show_msg){
                    alert(res.msg)
                }
            }
        });
    }

    //拉起微信客户端支付,官方代码,请勿随意修改
    function onBridgeReady(data){
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest', {
                "appId":data.appId,     //公众号名称,由商户传入
                "timeStamp":data.timeStamp,         //时间戳,自1970年以来的秒数
                "nonceStr":data.nonceStr, //随机串
                "package":data.package,
                "signType":data.signType,         //微信签名方式:
                "paySign":data.paySign //微信签名
            },
            function(res){
                if(res.err_msg == "get_brand_wcpay_request:ok" ){
                    // 使用以上方式判断前端返回,微信团队郑重提示:
                    //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
                    //这里处理支付成功后的逻辑,通常为页面跳转
                    window.location = "访问我方自主查询支付结果页面";//因为微信支付的同步回调结果不一定准确,所以我们需要自己主动再去查询一下用户是否已经支付成功,当然你也可以跳转到你希望跳转的页面
                }
            });
    }
</script>
</html>

后端PHP拉起支付以及用户支付成功后异步回调示例

<?php

namespace App\Http\Controllers\H5;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Yansongda\Pay\Log;
use Yansongda\Pay\Pay;

/**
 * 微信支付控制器
 * Class wechatPayController
 * @package App\Http\Controllers\H5
 * @todo 这里使用的laravel-pay 为2.x版本
 */
class WechatPayController extends Controller
{

    private $wechatPay = [
        'appid' => '', // APP APPID(微信开发平台上的APP应用的appid,这个是接APP支付用的)
        'app_id' => '这里填公众号 APPID', // 公众号APPID
        'app_secret' => '这里填公众号开发者密码AppSecret', //公众号开发者密码AppSecret(这个支付用不到,是获取微信openid用的)
        'miniapp_id' => '', // 小程序 APPID
        'mch_id' => '微信商户平台的商户号',//商户号(不管是何种类型的支付,这个都是必填的)
        'key' => '商户平台的商户号秘钥',//商户号秘钥(不管是何种类型的支付,这个都是必填的)
        'notify_url' => 'https://www.xxx.com/wechatPay/wechatPayNotify',//微信小程序支付异步回调 @todo 上线的时候吧测试域名改为正式服务器的域名 (https://) 这里就写当前“WechatPayController”控制器的访问链接,注意定义路由动作时使用any,不用使用get或post,因为你不知道微信会用什么方式请求异步回调的控制器
        'cert_client' => './cert/apiclient_cert.pem', // optional,退款等情况时用到
        'cert_key' => './cert/apiclient_key.pem',// optional,退款等情况时用到
        'log' => [ // optional
            'file' => './login/wechat.log',//微信支付日志的log文件路径(文件会在 项目根目录/public/login/wechat.log)
            'level' => 'debug', // 建议生产环境等级调整为 info,开发环境为 debug(这个改不改都不会影响用户付钱)
            'type' => 'single', // optional, 可选 daily.
            'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
        ],
        'http' => [ // optional
            'timeout' => 5.0,
            'connect_timeout' => 5.0,
            // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
        ],
        // 'mode' => 'dev', // optional, dev/hk;当为 `hk` 时,为香港 gateway。
    ];


    /**
     * 公共返回数据方法
     */
    protected function send($code = 200,$success = false,$show_msg = false,$msg = '',$data=[]){
        return [
            'code' => $code,//状态码
            'success' => $success,//请求是否成功 true:成功,false:失败
            'show_msg' => $show_msg,//是否显示提示信息 true:是,false:否
            'msg' => $msg,//提示信息
            'data' => $data//返回数据数组
        ];//返回数据
    }

    /**
     * 微信公众号支付(JSAPI支付)
     * @param Request $request
     */

    public function wechatMpPay(Request $request)
    {
        $open_id = $request->open_id;//微信openid
        $order_pay = [
            'openid' => $open_id,
            'out_trade_no' => time(),//我方订单号
            'total_fee' => 1, // **单位:分**测试专用
            'body' => '支付测试Demo',
        ];

        $pay = Pay::wechat($this->wechatPay)->mp($order_pay);//拉起H5支付返回重定向地址

        if($pay){
            return $this->send(200,true,false,'成功',$pay);
        }
        return $this->send(500,false,false,'支付接口异常');
    }


    /**
     * 微信APP/小程序支付(支付后回调)
     * @return string
     */
    public function wechatPayNotify()
    {
        $pay = Pay::wechat($this->wechatPay);
        try {
            $data = $pay->verify(); // 是的,验签就这么简单!返回值为微信回调的订单数据
            Log::debug('Wechat notify', $data->all());//记录日志
            file_put_contents('wechat_log.txt',$data->all());
            //判断是否支付成功
            if ($data->return_code == "SUCCESS" && $data->result_code == "SUCCESS") {
                //支付成功
                file_put_contents('wechat_log.txt','支付成功啦!');
                //这里你可以写判断订单是否已经改为支付状态,防止微信服务器因为首次异步不成功导致微信异步回调重复修改数据库!
                //告知微信服务器停止异步回调
                $str = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
                return $str;// laravel 框架中请直接 `return $pay->success()`
            }
        } catch (\Exception $exception) {
            Log::debug('Wechat BUG', $exception->getMessage());
            file_put_contents('wechat_log.txt','支付回调异常:'.$exception->getMessage());
        }
        $str = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
        return $str;// laravel 框架中请直接 `return $pay->success()`
        //return $pay->success()->send();// laravel 框架中请直接 `return $pay->success()`
    }

    /**
     * 检测订单支付是否成功
     * @param $order_id 订单id(orders表id)
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
     */
    public function isPaySuccess($order_id)
    {
        //查询订单是否支付成功
        //这里写自定义查询订单表的业务逻辑
        $isPaySuccess = Order::where([
            ['id',$order_id],
            ['is_pay',1]
        ])->first();

        //检测订单查询结果
        if($isPaySuccess){
            //就是个静态展示页面。view内容就是支付成功/失败的<h1>支付成功</h1>的标签。
            return redirect()->route('h5.goods.goodsList',$isPaySuccess['students_id'])->with('success','支付成功');
//            return view('h5.wechatPay.pay_return');//进入支付成功页面
            //return $this->send(200,true,false,'支付成功');
        }else{
            //就是个静态展示页面。view内容就是支付成功/失败的<h1>支付失败</h1>的标签。
            return redirect()->route('h5.goods.goodsList',$isPaySuccess['students_id'])->with('error','error');
        }

    }


    //获取微信公众号openid
    public function getWechatMpOpenid(Request $request){
        $code = (string)$request->code;//在微信网页获取到的code,用来换openid
        $appid = $this->wechatPay['app_id'];//测试账号的appid
        $secret = $this->wechatPay['app_secret'];//测试账号的app_secret
        $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$appid}&secret={$secret}&code={$code}&grant_type=authorization_code";//这链接格式是微信官方给的不用改,把参数拼进去就行

        // 1. 初始化一个cURL会话
        $ch = curl_init();
        //设置选项,包括URL
        curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        //执行并获取HTML文档内容
        $response = curl_exec($ch);
        // 4. 释放cURL句柄,关闭一个cURL会话
        curl_close($ch);

        $response = json_decode($response,true);
        return [
            'code'=>200,
            'data'=>[
                'openid'=>$response['openid'],
                'array'=>$response
            ]
        ];
    }
}

该处使用的url网络请求的数据。


微信官方后台

1.微信公众平台配置我方项目域名,如下图,若与下图不符,请检查你的公众号是否没有进行认证处理

PHP使用Laravel-Pay组件快速接入微信JSAPI支付(微信公众号支付)

要是你不想花钱测试支付,可以在侧边栏 开发→开发者工具→公众平台测试账号 中申请测试账号,并进行支付测试,需要注意:你在微信官方给测试账号中支付的花销是退不回来的!!!退不回来的!!!退不回来的!!!
2.微信公众号在侧边栏 微信支付 与商户号进行绑定操作。
3.登录微信商户平台,开通JSAPI支付产品
4.在微信商户平台进行商户号与微信公众号的绑定操作与相关配置。

上一篇:Laravel学习笔记汇总——RESTful API举例和HTTP状态码


下一篇:laravel之laravel-s组件技术指南