美文网首页
ThinkPHP接入微信支付 - JSAPI支付

ThinkPHP接入微信支付 - JSAPI支付

作者: 十万个魏什么啊 | 来源:发表于2020-05-16 23:44 被阅读0次

一、支付准备

二、获取用户openid

  • 首先,到微信公众平台后台 - 设置 - 网页授权域名(别忘了添加开发者)
// 在头部引入WechatPubService.php文件,见附录一
use app\api\service\WechatPubService;

 /**
   * 获取code
   */
    public function getInfo(){
        //前端传code过来
        $code = $this->request->param('code');
        // dump($code);die;
        if (isset($code)) {
            $wechat_public = config('wechat_public');
            try {
                $wechatService = new  WechatPubService($wechat_public);
                $token         = file_get_contents($wechatService->getToken($code));
                $token         = json_decode($token, true);
                // dump($token);
            } catch (\Exception $e) {
                return $this->json_error('系统错误');
            }
            if (!isset($token['errcode'])) {
                $useInfo = $wechatService->getUserInfo($token['access_token'], $token['openid']);
                $useInfo = json_decode($useInfo, true);
                // dump($useInfo);
                return $this->json_success('获取用户信息成功', $useInfo);
            } else {
                return $this->json_error('获取用户token失败');
            }
        } else {
            return $this->json_error('code能不能为空');
        }
    }

三、项目安装Payment

  • 安装Payment的前提条件为PHP版本 大于7.0,项目下命令行运行:composer require "riverslei/payment:*",即可安装成功。
    Payment为第三方支付SDK,项目地址:https://github.com/helei112g/payment,可以去了解更多的支付接入。

四、接入支付

  1. 服务端,创建支付订单,获取支付参数并传给客户端
// 头部引入
use Payment\Common\PayException;
use Payment\Client\Charge;
use Payment\Config;
use Payment\Client\Notify;
use Payment\Notify\PayNotifyInterface;

/**
     * 创建订单,并调起支付
     */
    public function create_order()
    {
        if (IS_POST) {
            //
            //---------- 获取订单参数 ----------
            //$openid = $this->request->param('openid');
            //
            //---------- 获取订单参数结束 ----------
            
            //---------- 验证订单参数 ----------
            //if (!$openid) {
            //    return $this->json_error('openid不能为空');
            //}
            //
            //---------- 验证订单参数结束 ----------

            // 生成订单号
            $orderNo = date('ymdHi') . rand(1000, 9999);

            Db::startTrans();
            try {
                //
                //---------- 以下为处理具体业务,并进行数据库操作 ----------
                //
                //
                //---------- 处理具体业务结束 ----------
                
                //---------- 以下为发起微信支付 ----------
                // 支付订单信息,即传递给微信的参数
                $payData = [
                    'body'            => '校友捐赠', //商品描述 用户支付后看不到此项
                    'subject'         => '校友捐赠', //商品详情
                    'order_no'        => $orderNo,
                    'timeout_express' => time() + 600,// 表示必须 600s 内付款
                    'amount'          => 0.01,// 微信沙箱模式,需要金额固定为3.01 单位:元
                    'return_param'    => 'JSAPI',  // 支付回调值
                    'client_ip'       => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1',// 客户地址
                    'openid'          => $openid,
                    'product_id'      => '123',
                ];
                // 获取微信配置
                $channel = 'wx_pub';// wx_app    wx_pub   wx_qr   wx_bar  wx_lite   wx_wap
                $wxConfig = config('wechat.wx_pay'); // 这里的配置文件见附录二

                $ret = Charge::run($channel, $wxConfig, $payData);

                Db::commit();
            } catch (Exception $e) {
                Db::rollback();
                return $this->json_error('调取支付失败'.$e->getMessage() );
            }
            if (!empty($ret)) {
                return $this->json_success('调取支付成功', $ret);
            } else {
                return $this->json_error('调取支付失败');
            }

        }
    }
  1. 客户端,调起支付,若成功支付则进入下一步
  2. 服务端,到支付回调方法下查看回调结果 (这里需要注意后台拿到回调结果后,需要进行签名验证提高安全性)
/**
     * 支付回调方法
     */
    public function notify(){
        // file_put_contents('upload/1.txt' , '进入了回调');
        // 获取回调值
        $xmlData = file_get_contents('php://input');
        // 解析回调值
        //XML格式转换
        $xmlObj = simplexml_load_string($xmlData, 'SimpleXMLElement', LIBXML_NOCDATA);
        $xmlObj = json_decode(json_encode($xmlObj),true);

        // 当支付通知返回支付成功时
        if ($xmlObj['return_code'] == "SUCCESS" && $xmlObj['result_code'] == "SUCCESS") {
 
            //回调签名验证;
            foreach( $xmlObj as $k=>$v) {
                if($k == 'sign') {
                    $xmlSign = $xmlObj[$k];
                    unset($xmlObj[$k]);
                };
            }
            $sign = http_build_query($xmlObj);
            // file_put_contents('upload/1.txt' , $sign);
            //md5处理
            $sign = md5($sign.'&key='.'fb84170d5baeaf8a9a96c286821ad781');
            //转大写
            $sign = strtoupper($sign);
 
            //验签名。默认支持MD5
            //验证加密后的32位值和 微信返回的sign 是否一致!!!
            if ( $sign === $xmlSign) {
                // 订单号
                $trade_no = $xmlObj['out_trade_no'];

                //---------- 以下为处理具体业务 ----------
                $edit['is_pay']   = 1;
                $edit['pay_time'] = date('Y-m-d H:i:s');
                $model     = new OrderModel();
                $order = $model->where(['order_no'=>$trade_no])->update($edit);
                //---------- 处理具体业务结束 ----------

                if($order){
                    return $this->json_success('支付成功,感谢您的捐赠');
                }else{
                    return $this->json_error('支付失败');
                }
            }
        }
    }

附录:

  • 附录一:(这里有多种微信相关方法,需要什么就可以调用什么)
<?php
namespace app\api\service;
use \Exception;
use think\facade\Cache;

class WechatPubService
{
    //获取微信授权cod地址
    const CODE_URL_BASE = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect';
    const CODE_URL_USER = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect';

    const TOKEN_URL = 'https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code';
    const TOKEN = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s';
    const APPQRCODE = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s';

    //获取token
    const ACCESS_TOKEN = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code';

    //获取用户信息
    const USER_INFO = 'https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN';

    //获取jsapi_tocket
    const JSAPI_TICKET = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi';

    //获取公众号的全局唯一接口调用凭据
    const PUBLIC_ACCESS_TOKEN = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s';

    //判断用户是否关注了公众号
    const ATTEND_PUBLIC = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN';

    private static $instance;
    //配置
    private $_config = [
        'AppID'     => 'wx**********************99',
        'AppSecret' => '21**********************7c',
    ];

    //access_token和ticket的有效时间,最好小于7200秒
    const TOKEN_EXPIRE = 7000;

    /**
     * WechatPubService 初始化方法.
     * @param array $conf_option
     * @throws Exception
     */
    public function __construct($conf_option = [])
    {
        $this->_config = array_merge($this->_config, $conf_option);
        if (!isset($this->_config['AppID']) || empty($this->_config['AppID'])) {
            throw new Exception('微信AppID不能为空');
        } else if (!isset($this->_config['AppSecret']) || empty($this->_config['AppSecret'])) {
            throw new Exception('微信AppSecret不能为空');
        }
    }

    /**
     * @param array $conf_option
     * @return WechatPubService
     * @throws Exception
     */
    public static function instance($conf_option = [])
    {
        if (is_null(self::$instance)) {
            self::$instance = new self($conf_option);
        }
        return self::$instance;
    }

    /**
     * 生成获取用户code的请求链接
     * @param $uri string 用户同意授权以后的跳转链接(当前项目的链接,不要有任何登录检查和权限检查)
     * @return string 获得授权地址,使用header进行跳转
     */
    public function getCode($uri = '')
    {
        $uri = urlencode($uri);
        $url = sprintf(self::CODE_URL_USER, $this->_config['AppID'], $uri);
        return $url;
    }

    /**
     * 根据用户的授权code获取用户的access_token
     * @param string $code
     * @return string 获得请求地址,使用curl或者file_get_contents获取结果
     */
    public function getToken($code = '')
    {
        $url = sprintf(self::ACCESS_TOKEN, $this->_config['AppID'], $this->_config['AppSecret'], $code);
        return $url;
    }

    /**
     * 拉取用户信息  scope为 snsapi_userinfo才可以调用
     * @param string $access_token
     * @param string $openid
     * @return false|string
     */
    public function getUserInfo($access_token = '', $openid = '')
    {
        $url    = sprintf(self::USER_INFO, $access_token, $openid);
        $result = file_get_contents($url);
        return $result;
    }

    /**
     * 获取公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token
     * @param string $app_id
     * @param string $app_secret
     * @return mixed
     */
    public function getAccessToken($app_id = '', $app_secret = '')
    {

        $url    = sprintf(self::PUBLIC_ACCESS_TOKEN, $app_id, $app_secret);
        $result = file_get_contents($url);
        return json_decode($result, true);

    }

    /**
     * 获取公众号accessTOken
     * @param string $app_id
     * @param string $app_secret
     * @return mixed
     * @throws Exception
     */
    public function getPublicAccessToken($app_id = '', $app_secret = '')
    {
        $cache_key = 'wechat_public_access_token_cache_' . $app_id;
//        Cache::rm($cache_key);
        if (Cache::has($cache_key)) {
            return Cache::get($cache_key);
        } else {
            $result = $this->getAccessToken($app_id, $app_secret);
            if (isset($result['errcode'])) {
                throw new Exception($result['errmsg']);
            } else {
                Cache::set($cache_key, $result['access_token'], self::TOKEN_EXPIRE);
                return $result['access_token'];
            }
        }
    }

    /**
     * 获取JSAPI_TICKET接口
     * @throws Exception
     */
    public function getJsApiTicket()
    {
        $cache_key = 'wechat_public_js_api_ticket_prefix_' . $this->_config['AppID'];
//        Cache::rm($cache_key);
        if (Cache::has($cache_key) && 1 ==2) {
            return Cache::get($cache_key);
        } else {
            //抓取错误,不然缓存会缓存错误的结果
            try {
                $access_token = $this->getPublicAccessToken($this->_config['AppID'], $this->_config['AppSecret']);
            } catch (Exception $e) {
                throw new Exception($e->getMessage());
            }
            $url    = sprintf(self::JSAPI_TICKET, $access_token);
            $result = file_get_contents($url);
            $result = json_decode($result, true);
            Cache::set($cache_key, $result, self::TOKEN_EXPIRE);
            return $result;
        }

    }

    //获取随机字符串
    public static function getRandCode($num = 16)
    {
        $array   = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
            't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
            'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
        );
        $tmp_str = '';
        $max     = count($array);
        for ($i = 1; $i <= $num; $i++) {
            $tmp_str .= $array[rand(0, $max - 1)];
        }
        return $tmp_str;
    }

    /**
     * 判断用户是否关注了公众号
     * @param $access_token
     * @param $open_id
     */
    public function hasAttendPub($open_id)
    {
        $access_token = $this->getPublicAccessToken($this->_config['AppID'], $this->_config['AppSecret']);
        $url          = sprintf(self::ATTEND_PUBLIC, $access_token, $open_id);
        $result       = file_get_contents($url);
        return json_decode($result, true);
    }
}
  • 附录二:
<?php
return [
    'appid'  => 'wx*****************99', // 公众号或小程序的appid
    'secret' => '21*****************7c', // 公众号或小程序的secret
    'wx_pay' => [
        'use_sandbox'       => false,// 是否使用 微信支付仿真测试系统 开启使用沙箱模式。金额固定

        'app_id'            => 'wx***************99',  // 公众账号ID
        'mch_id'            => '14******02',// 商户id
        'md5_key'           => 'fb*******************81',// md5 秘钥(商户的秘钥)
        // (前面第一步下载的证书文件放到指定位置,在这里设置路径)
        'app_cert_pem'      =>  env('CONFIG_PATH').'cert/' . 'apiclient_cert.pem',
        'app_key_pem'       =>  env('CONFIG_PATH').'cert/' . 'apiclient_key.pem',
        'sign_type'         => 'MD5',// MD5  HMAC-SHA256
        'limit_pay'         => [
            //'no_credit',
        ],// 指定不能使用信用卡支付   不传入,则均可使用
        'fee_type'          => 'CNY',// 货币类型  当前仅支持该字段

        'notify_url'        => 'http://***.com/api/v1/Payment/notify', // 回调返回的方法,到此方法下接收支付回调

//        'redirect_url'      => 'https://helei112g.github.io/',// 如果是h5支付,可以设置该值,返回到指定页面

        'return_raw'        => false,// 在处理回调时,是否直接返回原始数据,默认为true
    ],
];

结束,这是我目前总结的最简单的实现微信JSAPI支付的流程,其中还包含了获取用户信息。
如果仅仅是需要获取用户微信信息可以直接使用第二步,简单便捷。
不足之处,望不吝指出~

相关文章

网友评论

      本文标题:ThinkPHP接入微信支付 - JSAPI支付

      本文链接:https://www.haomeiwen.com/subject/smudohtx.html