php 微信支付分 教程

微信支付分是微信比较新的一种支付方式,针对特殊场景,对接过程还是有点小坑,相关教程比较少。

记录一下源码,以作备忘。微信接口版本为最新版本 。

<?php
/**
 * 微信支付分
 */

class WxPayscore
{
    private $wxConfig = [
        'appid' => '',//公众号appid
        'appSecret' => '',//公众号秘钥
        'mch_id' => '',//商户号
        'service_id' => '',//商户号服务id
        'key' => '',//v3秘钥
        'serial_no' => "",//这个是证书号
        'notify_url' => '',// 异步回调地址
        'pay_sign_page' => '',// 签约后跳转页面
    ];

    private $mch_private_key = '';// 私钥秘钥

    public function __construct()
    {

    }


    /**
     * 创建支付订单
     * @param $order_no
     * @param number $total_fee
     * @param $openid
     * @return int|void
     */
    public function createOrder($order_no, $total_fee, $openid)
    {
        $website_name = '支付名称';

        $total_fee = (int)bcmul($total_fee, 100, 0);// 传给微信的单位是分
        $para = [
            'out_order_no' => $order_no,
            'appid' => $this->wxConfig['appid'],
            'service_id' => $this->wxConfig['service_id'],
            'service_introduction' => $website_name,
            'risk_fund' => ['name' => 'ESTIMATE_ORDER_COST', 'amount' => $total_fee],
            'time_range' => ['start_time' => 'OnAccept'],
            'notify_url' => $this->wxConfig['notify_url'],
            'openid' => $openid,
            'need_user_confirm' => false,
        ];
        $reData = $this->wxV3Post('https://api.mch.weixin.qq.com/v3/payscore/serviceorder', $para);

        if ($reData['code'] == 200) {
            $result = $this->queryOrder($order_no);
            if ($this->isMyError($result)) {
                return $this->myError($result['msg']);
            }
            if ($result != 'ing') {
                return $this->myError('创建支付单失败');
            }
            return true;
        } else {
            return $this->myError($reData['data']['message']);
        }
    }


    public function queryOrder($out_order_no)
    {
        $para = [
            'appid' => $this->wxConfig['appid'],
            'service_id' => $this->wxConfig['service_id'],
            'out_order_no' => $out_order_no
        ];

        $reData = $this->wxV3Get("https://api.mch.weixin.qq.com/v3/payscore/serviceorder", $para);
        /* state 表示当前单据状态
 枚举值:
 CREATED:商户已创建服务订单;
 DOING:服务订单进行中;
 DONE:服务订单完成;
 REVOKED:商户取消服务订单;
 EXPIRED:服务订单已失效,"商户已创建服务订单"状态超过30天未变动,则订单失效
 示例值:CREATED*/

        if ($reData['code'] != 200) {
            return $this->myError($reData['message']);
        }
        $state = $reData['data']['state'];
        if ($state == 'DONE') {
            // 已完成
            return 'done';
        } else if ($state == 'REVOKED' || 'EXPIRED' == $state) {
            // 已结束
            return 'closed';
        } else if ('DOING' == $state) {
            // 进行中
            return 'ing';
        } else {
            return $this->myError('状态异常');
        }

    }


    /**
     * 查询授权记录
     * @param $openid
     */
    public function permissions($openid)
    {
        $para = [ // JSON请求体
//            'out_order_no' => $order_no,//订单号
            'appid' => $this->wxConfig['appid'],
            'service_id' => $this->wxConfig['service_id'],
        ];

        $reData = $this->wxV3Get("https://api.mch.weixin.qq.com/v3/payscore/permissions/openid/{$openid}", $para);
        if ($reData['code'] == 200) {
            if ($reData['data']['authorization_state'] == 'AVAILABLE') {
                return true;
            }
        }
        return false;
    }

    public function getOpenid($redirect_uri)
    {
        $appid = $this->wxConfig['appid'];
        $secret = $this->wxConfig['appSecret'];

        if (isset($_GET['code']) && !empty($_GET['code'])) {
            $code = $_GET['code'];
            // 获取openid
            $weixin = file_get_contents("https://api.weixin.qq.com/sns/oauth2/access_token?appid=$appid&secret=$secret&code=$code&grant_type=authorization_code");
            //通过code换取网页授权access_token
            $jsondecode = json_decode($weixin); //对JSON格式的字符串进行编码
            $array = get_object_vars($jsondecode);//转换成数组
            if (!isset($array['openid'])) {
                die('get openid fail!');
            }
            $openid = $array['openid'];//输出openid

            //第二步:根据全局access_token和openid查询用户信息
            $access_token = $array["access_token"];
            $openid = $array['openid'];
            $get_user_info_url = "https://api.weixin.qq.com/sns/userinfo?access_token=$access_token&openid=$openid&lang=zh_CN";
            $userInfo = $this->getJson($get_user_info_url);

            return $openid;
        } else {
            // 获取code
            $redirect_uri = urlencode($redirect_uri);
            $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' . $appid . '&redirect_uri=' . $redirect_uri . '&response_type=code&scope=snsapi_base&state=1#wechat_redirect';
            header('location:' . $url);
            die();
        }

    }

    function getJson($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        curl_close($ch);
        return json_decode($output, true);
    }


    /**
     * 完结订单-扣款
     * @param $order_no
     * @param $price
     * @return array
     */
    public function endOrder($order_no, $price)
    {
        $out_order_no = $order_no;
//        $price = 0;
//        $price = 0.01;
        $price = (int)bcmul($price, 100, 0);

        if ($price == 0) {
            $para = [
//                'out_order_no' => $order_no,
                'appid' => $this->wxConfig['appid'],
                'service_id' => $this->wxConfig['service_id'],
                'finish_type' => 1,//1取消,2完结
                'cancel_reason' => '购物取消',
                'total_amount' => 0,
                'post_payments' => [['name' => '支付类目名称', 'amount' => 0]],
                'time_range' => ['end_time' => date('YmdHis', time() - 1)],
            ];
        } else {
            $para = [
//                'out_order_no' => $order_no,
                'appid' => $this->wxConfig['appid'],
                'service_id' => $this->wxConfig['service_id'],
                'finish_type' => 2,//1取消,2完结
                'cancel_reason' => '称重鲜售货柜',
                'real_service_end_time' => date('YmdHis', time()),
                'post_payments' => [['name' => "支付类目名称", 'amount' => $price]],
                'total_amount' => $price,//总金额
                'time_range' => ['end_time' => date('YmdHis', time() - 1)],
            ];
        }
        $reData = $this->wxV3Post("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{$out_order_no}/complete", $para);
        if ($reData['code'] == 200) {
            return true;
        } else {
            return false;
        }
    }

    public function isMyError($result)
    {
        if (isset($result['code']) && $result['code'] != 0) {
            return true;
        }
        return false;
    }

    public function myError($msg, $code = 1)
    {
        return ['msg' => $msg, 'code' => $code, 'data' => []];
    }

    protected function wxV3Post($url, $para)
    {
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        //当前时间戳
        $timestamp = time();
        //随机字符串
        $nonce = $this->getRandomStr(32);
        //POST请求时
        $body = json_encode($para);

        $message = "POST\n" . $canonical_url . "\n" . $timestamp . "\n" . $nonce . "\n" . $body . "\n";
        //生成签名
        openssl_sign($message, $raw_sign, openssl_get_privatekey($this->mch_private_key), 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        //Authorization 类型
        $schema = 'WECHATPAY2-SHA256-RSA2048';
        //生成token
        $token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"', $this->wxConfig['mch_id'], $this->wxConfig['serial_no'], $nonce, $timestamp, $sign);
        $header = [
            'Content-Type:application/json',
            'Accept:application/json',
            'User-Agent:*/*',
            'Authorization: ' . $schema . ' ' . $token
        ];

//        print_r($para);
//        print_r($header);
//        exit;

        $curl = curl_init(); // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);  // 从证书中检查SSL加密算法是否存在
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        $tmpInfo = curl_exec($curl);
        $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        curl_close($curl);
        return ['code' => $httpCode, 'data' => json_decode($tmpInfo, true)];
    }

    private function wxV3Get($url, $para)
    {
        $para['service_id'] = $this->wxConfig['service_id'];
        $para['appid'] = $this->wxConfig['appid'];
        $url = "$url?" . http_build_query($para);
        $header = $this->createAuthorization($url);
        $curl = curl_init(); // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);  // 从证书中检查SSL加密算法是否存在
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        $tmpInfo = curl_exec($curl);
        $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        curl_close($curl);
        return ['code' => $httpCode, 'data' => json_decode($tmpInfo, true)];
    }

    /**
     * 验证是否开启支付分
     */
    public function verifySign($openid)
    {
        $para = [
            'appid' => $this->wxConfig['appid'],
            'service_id' => $this->wxConfig['service_id'],
            'openid' => $openid
        ];
        $gR = $this->wxV3Get("https://api.mch.weixin.qq.com/v3/payscore/user-service-state", $para);
        if ($gR['code'] == 200) {
            if ("AVAILABLE" == $gR['data']['use_service_state']) {
                return 1;
            } else {
                return 0;
            }
        } else {
            return 0;
        }
    }


    /**
     * 微信支付分签约页面
     * @return string
     */
    public function paySign()
    {
        //以下是jsapi签名动作
        $time = time();
        $noncestr = $this->getRandomStr(16);
        //获取access_token
        $access_token = '';
        if (!$access_token) {
            $appid = $this->wxConfig['appid'];
            $appSecret = $this->wxConfig['appSecret'];
            $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$appSecret";
            $reData = file_get_contents($url);
            $reData = json_decode($reData, true);
            if (!isset($reData['access_token'])) {
                echo $reData['errmsg'];
                exit;
            }
            $access_token = $reData['access_token'];
//            Cache::set('wx_access_token', $access_token, 7000);
        }
        //获取jsapi_ticket
        $jsapi_ticket = '';
        if (!$jsapi_ticket) {
            $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=$access_token&type=jsapi";
            $reData = file_get_contents($url);
            $reData = json_decode($reData, true);
            $jsapi_ticket = $reData['ticket'];
//            Cache::set('wx_jsapi_ticket', $jsapi_ticket, 7000);
        }
        //执行签约跳转
        $jsapi = [
            'noncestr' => $noncestr,
            'jsapi_ticket' => $jsapi_ticket,
            'timestamp' => $time,
            'url' => "https://xxx/api/payment/paySign"//此处注意,需完整的URL地址,例如伪静态生成的.html或带参数get地址
        ];
        $jsapi['signature'] = sha1($this->asc_sort($jsapi));
        $jsapi['appid'] = $this->wxConfig['appid'];
        //以下是跳转签约页面的动作
        $singData = [
            'mch_id' => $this->wxConfig['mch_id'],
            'nonce_str' => $noncestr,//使用签名的随机字符串
            'out_request_no' => $this->getRandomStr(16),
            'service_id' => $this->wxConfig['service_id'],
            'sign_type' => 'HMAC-SHA256',
            'timestamp' => $time,
        ];
        $stringSignTemp = $this->asc_sort($singData);
        $stringSignTemp .= "&key=" . $this->wxConfig['key'];
        $singData['sign'] = strtoupper(hash_hmac('sha256', $stringSignTemp, $this->wxConfig['key']));//签名值,mch_id、service_id、out_request_no、timestamp、nonce_str、sign_type

        $data['jsapi'] = $jsapi;
        $data['para'] = $singData;
        //签约成功后跳转的页面
//        $data['paySignPage'] = $this->wxConfig['pay_sign_page'];

        $data['paySignPage'] = session('pay_sign_page');

        // 模板输出
        return view('wx.paySign', $data);
    }

    //生成v3 Authorization
    protected function createAuthorization($url)
    {
        if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) {
            throw new \RuntimeException("当前PHP环境不支持SHA256withRSA");
        }
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        //当前时间戳
        $timestamp = time();
        //随机字符串
        $nonce = $this->getRandomStr(32);
        //POST请求时
        $body = "";
        $message = "GET\n" . $canonical_url . "\n" . $timestamp . "\n" . $nonce . "\n" . $body . "\n";
        //生成签名
        openssl_sign($message, $raw_sign, openssl_get_privatekey($this->mch_private_key), 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        //Authorization 类型
        $schema = 'WECHATPAY2-SHA256-RSA2048';
        //生成token
        $token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"', $this->wxConfig['mch_id'], $this->wxConfig['serial_no'], $nonce, $timestamp, $sign);
        $header = [
            'Content-Type:application/json',
            'Accept:application/json',
            'User-Agent:*/*',
            'Authorization: ' . $schema . ' ' . $token
        ];
        return $header;
    }

    /**
     * 获得随机字符串
     * @param $len             需要的长度
     * @param $special        是否需要特殊符号
     * @return string       返回随机字符串
     */
    function getRandomStr($len, $special = false)
    {
        $chars = 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"
        );

        if ($special) {
            $chars = array_merge($chars, array(
                "!", "@", "#", "$", "?", "|", "{", "/", ":", ";",
                "%", "^", "&", "*", "(", ")", "-", "_", "[", "]",
                "}", "<", ">", "~", "+", "=", ",", "."
            ));
        }

        $charsLen = count($chars) - 1;
        shuffle($chars);                            //打乱数组顺序
        $str = '';
        for ($i = 0; $i < $len; $i++) {
            $str .= $chars[mt_rand(0, $charsLen)];    //随机取出一位
        }
        return $str;
    }

    /**ascii码从小到大排序
     * @param array $params
     * @return bool|string
     */
    private function asc_sort($params = array())
    {
        if (!empty($params)) {
            $p = ksort($params);
            if ($p) {
                $str = '';
                foreach ($params as $k => $val) {
                    $str .= $k . '=' . $val . '&';
                }
                $strs = rtrim($str, '&');
                return $strs;
            }
        }
        return false;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值