ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

微信支付和退款

2021-01-05 16:01:49  阅读:146  来源: 互联网

标签:ch return 微信 data result str 支付 curl 退款


1.流程图

在这里插入图片描述
apiclient_cert.pem 和 apiclient_key.pem 证书是商家在使用微信支付功能的时候,进行身份验证用到的,起到一种安全的作用,但是,目前微信支付仅仅只在使用退款接口或者撤销订单的时候需要可能会用到证书。

2.代码

付款和退款封装成一个类

class WxpayService
{
    public function __construct()
    {
        $this->conf =[
        "appid"  => config('wxpay.app_id'),  // 开放平台或商户平台APPID
        "mch_id" => config('wxpay.mch_id'),  // 商户平台 商户号
        "key"    => config('wxpay.key'),  // 商户平台 秘钥KEY
        "spbill_create_ip"  =>config('wxpay.spbill_create_ip'),
        "TOKEN"  => "",  // 此参数非必传 有的前端在jsapi 支付时会要求返回signature 参数  此参数即为此准备
        ];
    }

    /**
     * notes:支付
     * @param $param
     * @param string $openid
     * @return array
     */
    public function pay($param,$openid="")
    {
        $order = [
            'out_trade_no'  => $param['out_trade_no'],// 订单号
            'total_fee'     => intval($param['total_fee']*100),// 订单金额  以(分)为单位
            'body'          => $param['body'],// 商品描述
            'notify_url'    => $param['notify_url'], //回调地址
            'spbill_create_ip' => $this->conf['spbill_create_ip'], //对应服务器IP
            'trade_type'    => $param['trade_type']  //对应支付类型
        ];

        #当支付类型为JAPI时  openid  必传
        if($param['trade_type'] == "JSAPI")
        {
            $order['openid'] = $openid;
        }

        #统一下单 获取prepay_id
        $unified_order=$this->unifiedOrder($order);
        //error_log(var_export($unified_order,true),3,__FILE__.'.sfxf');

        #获取当前时间戳
        $time = time();

        #JSAPI
        if($param['trade_type'] == 'JSAPI')
        {
            #组合jssdk需要用到的数据
            $data = [
                'appId'     => $this->conf['appid'], //appid
                'timeStamp' => strval($time), //时间戳
                'nonceStr'  =>$unified_order['nonce_str'],// 随机字符串
                'package'   => 'prepay_id='.$unified_order['prepay_id'],// 预支付交易会话标识
                'signType'  => 'MD5'      //加密方式
            ];
            // 生成签名
            $data['paySign']=$this->makeSign($data);
            // #有的可能会有需求signature 此参数的在此加密一下即可
            // $token = $this->config['token'];
            // $tmpArr = array($token, strval($time),$unified_order['nonce_str']);
            // sort($tmpArr, SORT_STRING);
            // $tmpStr = implode( $tmpArr );
            // $tmpStr = sha1($tmpStr);
            // $data['signature'] = $tmpStr;
        }
        #APP
        elseif($param['trade_type'] == 'APP')
        {
            $data  = [
                'appid'     => $this->conf['appid'],
                'partnerid' => $this->conf['mch_id'],
                'prepayid'  => $unified_order['prepay_id'],
                'package'   => 'Sign=WXPay',
                'noncestr'  => $unified_order['nonce_str'],// 随机字符串
                'timestamp' => strval($time), //时间戳
            ];
            //生成签名
            $data['sign'] =  $this->makeSign($data);
        }
        return $data;
    }
    /**
     * 统一下单
     * @param  array $order 订单 必须包含支付所需要的参数
     * body(产品描述)、total_fee(订单金额)、out_trade_no(订单号)、
     * product_id(产品id)、trade_type(类型:JSAPI,NATIVE,APP)
     */
    public function unifiedOrder($order)
    {
        $config =[
            'appid'     => $this->conf['appid'], //appid
            'mch_id'    => $this->conf['mch_id'], //商户号ID
            'nonce_str' => $this->getNonceStr()
        ];

        # 合并配置数据和订单数据
        $data=array_merge($order,$config);

        # 生成签名
        $sign=$this->makeSign($data);
        $data['sign']=$sign;
        #转换成xml
        $xml=$this->toXml($data);
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';  //接收xml数据的文件
        $header[] = "Content-type: text/xml";      //定义content-type为xml,注意是数组
        $ch = curl_init ($url);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 兼容本地没有指定curl.cainfo路径的错误
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        $response = curl_exec($ch);
        if(curl_errno($ch)){
            # 显示报错信息;终止继续执行
            die(curl_error($ch));
        }
        curl_close($ch);

        #转换成数组
        $result=$this->toArray($response);

        #显示错误信息
        if ($result['return_code']=='FAIL')
        {
            die($result['return_msg']);
        }

        $result['sign']=$sign;
        $result['nonce_str']=$this->getNonceStr();
        return $result;
    }

    /**
     * notes:退款处理
     * @param $param
     * @return array
     */
    public function trade_refund($param) {
        $config =[
            'appid'     => $this->conf['appid'], //appid
            'mch_id'    => $this->conf['mch_id'], //商户号ID
            'key' => $this->conf['key'],
            'nonce_str' => $this->getNonceStr(),
        ];
        $order = array(
            'appid' => $config['appid'],
            'mch_id' => $config['mch_id'],
            'nonce_str' => $config['nonce_str'],
            'total_fee' => intval($param['total_fee'] * 100), //订单金额  单位 转为分
            'refund_fee' => intval($param['refund_fee'] * 100), //退款金额 单位 转为分
            'sign_type' => 'MD5', //签名类型 支持HMAC-SHA256和MD5,默认为MD5
            //'transaction_id' => $param['transaction_id'], //微信订单号  和下面的out_trade_no 二选一
            'out_trade_no' => $param['out_trade_no'], //商户订单号
            'out_refund_no' => $param['out_refund_no'], //商户退款单号
            'refund_desc' => '用户申请退款', //退款原因(选填)
            'notify_url'=>$param['notify_url'],//退款成功,异步通知
        );
        $order['sign'] = $this->makeSign($order, $config['key']);
        $xmlresult = $this->postXmlSSLCurl($this->ToXml($order),'https://api.mch.weixin.qq.com/secapi/pay/refund');
        $result = $this->toArray($xmlresult);
        return $result;
    }


    /**
     * 生成签名
     * @return 签名,本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值
     */
    public function makeSign($data)
    {
        # 去空
        $data=array_filter($data);
        #签名步骤一:按字典序排序参数
        ksort($data);
        #将数组转成url形式
        $string_a=http_build_query($data);
        $string_a=urldecode($string_a);
        #签名步骤二:在string后加入KEY
        $string_sign_temp=$string_a."&key=".$this->conf['key'];
        #签名步骤三:MD5加密
        $sign = md5($string_sign_temp);
        # 签名步骤四:所有字符转为大写
        $result=strtoupper($sign);
        return $result;
    }

    /**
     * 将xml转为array
     * @param  string $xml xml字符串
     * @return array       转换得到的数组
     */
    public function toArray($xml){
        #禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $result= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $result;
    }

    /**
     *
     * 产生随机字符串,不长于32位
     * @param int $length
     * @return 产生的随机字符串
     */
    public function getNonceStr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str ="";
        for ( $i = 0; $i < $length; $i++ )
        {
            $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }

    /**
     * 输出xml字符
     * @throws WxPayException
     **/
    public function toXml($data)
    {
        if(!is_array($data) || count($data) <= 0)
        {
            throw new WxPayException("数组数据异常!");
        }
        $xml = "<xml>";
        foreach ($data as $key=>$val){
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    //解密退款成功时,微信回调返回的信息
    public function decipheringReqInfo($str){
        //微信商户key
        $key = $this->conf['key'];
        $str = base64_decode($str);
        $xml = openssl_decrypt($str,'aes-256-ecb',md5($key),OPENSSL_RAW_DATA);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }



    //需要使用证书的请求
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
        //设置header
        curl_setopt($ch,CURLOPT_HEADER,FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        //dirname(getcwd())."/certs/apiclient_cert.pem"
        //dirname(getcwd());  /data
        //getcwd(); /data/doctorpc.test
        curl_setopt($ch,CURLOPT_SSLCERT, realpath(getcwd().'/application/certs/wxpay/apiclient_cert.pem'));
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLKEY, realpath(getcwd().'/application/certs/wxpay/apiclient_key.pem'));
        //post提交方式
        curl_setopt($ch,CURLOPT_POST, true);
        curl_setopt($ch,CURLOPT_POSTFIELDS,$xml);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            $errpr = curl_getinfo($ch);
            return $errpr;
            // echo json($error);
            echo "curl出错,错误码:$error"."<br>";
            curl_close($ch);
            return false;
        }
    }

    /**
     * 验证支付成功回调
     * @return array 返回数组格式的notify数据
     */
    public function notify()
    {
        // 获取xml
        $xml=file_get_contents('php://input', 'r');
        # 转成php数组
        $data=$this->toArray($xml);
        # 保存原sign
        $data_sign=$data['sign'];
        # sign不参与签名
        unset($data['sign']);
        $sign=$this->makeSign($data);
        # 判断签名是否正确  判断支付状态
        if ($sign===$data_sign && $data['return_code']=='SUCCESS' && $data['result_code']=='SUCCESS')
        {
            $result=$data;
        }else{
            $result=false;
        }

        # 返回状态给微信服务器
        if ($result)
        {
            $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
        }else{
            $str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
        }
        $res['result']=$result;
        $res['str']=$str;
        return $res;
    }

    public function refund_notify()
    {
        // 获取xml
        $xml=file_get_contents('php://input', 'r');
        # 转成php数组
        $data=$this->toArray($xml);
        /**
         * array (
        'return_code' => 'SUCCESS',
        'appid' => 'wx457125l3a8cd5b54',
        'mch_id' => '124342369',
        'nonce_str' => 'b5bdf257c21c4548eb221cbd871197a3',
        'req_info' => 'csFFQu3LCbIc22YL0YSifbQJ/iWl6il/Uctok3Tm6DWAeeIJuPPQF6ux6TUHiokzjebEgqt3YNaB74Lzf/iGEf1p8uwG3hAFwWWAWNXum5bRXh8KjA/4y62NuwLPQpaY8K2efVICuvgWhz9P67Qt9GL005qKXWYS9nclnkyMrrlZ/QNcub2dk+vereZAmxRSakhHheFQkJjdADOHN3EbMFEB7JkL0HfE+ApvQcYH1CW8Ngqc072jKy5SqT4/jIVexl6wROZqOqbiE4Eu5JHb7dHSrHuHzQ02eXtIIBGhrJfzp5U4anfeZJosL76R66YEV2oKr4+Iud6ucM6B+PEVGkf7WgM5wwURvlinA7nDSSoF9DkVhAfrBKgn5FXqDe9KWX25rHAQtNzfo6mey/BJS7eIEtlx8b/5zc6DMkND9I60A5C6n2/0BOgOOUHmSDhgPTxQBgMb+6WII9LFkgOiD4JOrnPJezwcq+aBDl0VSBKHtsyDImqBRPIvsIRGP87oEX3XJWeaaBNb439Xi81fZU2RBGf7mfSVJ7VfvNcVcHIYhmybpQHLD3W+wshAZNCXCnVxt8L/3Mp4it/rFyBGBPt0ujYBlsFEq7whHB4qlEbaHKTegMEZ3nE4E+WdyYHISn1DcMRJeCtv5m8RI/FcFldJj0/K6rNpWZEjNKNJ8WLPP6Wegq4M9ljoe+jZcxHmRPeAL1elVvnPTVM6904m9AOgPiOPVXWLtBSNZkAQR/pRGj7gzEqOb9hrIkT23+HcQ/hZ8KHvd+qwpUWNP1cNIDvU2JIu1TDqgjBlWm1EX8IxNmfBC5oIuJku1PPwir3TQ7zfWNhW7TcS7S7QIyb7G8Og5fGpeajotCcbOoFSo1fjB3HgBvZcRbrXEq9snPBr7p8AqvnzJXnbDHQyiFWqAW3/37SBsvNZzrlcLNwuXyfK1KaBZyXWYQOQizkwbu7H4DHDH0076QoZXGnS/zQouG2hG5PjcI8Rk4QhWOKcoSDOqgPgSOW7hvnBxu1Oka1cHm9SkC0tos4dMX6/x3nhVtJiFRiF7/qsBe9qhVMtqHguj46LOl75wTVgnKCcFCKj',
        )
         */
        $req_info=$this->decipheringReqInfo($data['req_info']);
        //解密信息
        if ($data['return_code']=='SUCCESS')
        {
            $result=$req_info;
        }else{
            $result=false;
        }
        # 返回状态给微信服务器
        if ($result)
        {
            $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
        }else{
            $str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
        }
        $res['result']=$result;
        $res['str']=$str;
        return $res;


    }
}

统一下单使用方法

public function diagnosis_wxpay(Request $request)
    {

        $data=[
            'out_trade_no'=>$request->param('orderid'),
            'body'=>$request->param('subject'),
            'total_fee'=>$request->param('total_amount'),
            'notify_url'=>config('wxpay.notify_url'),
            'trade_type' => 'APP'
        ];
        $wxpay=new WxpayService();
        #根据 trade_type  类型不同,是否传递openid
        #APP   类型支付调用
        $res = $wxpay->pay($data);
        #JSAPI 类型支付调用
        //$res = $wxpay->index($date,$openid);
        #获得后将对应内容返回前端即可
        //return $res;
        //return json_encode($res);
        return Send::successMsg($res,'微信下单成功');
    }

退款使用方法

public function wxpay_trade_refund(Request $request){
        $data['refund_reason']='用户订单取消-退款';
        $data=[
            'total_fee'=>'0.01',
            'refund_fee'=>'0.01',
            //'transaction_id'=>$order['trade_no'],//和下面的out_trade_no二选一
            'out_trade_no'=>'DN202101051054280HwV',
            'out_refund_no' => 'DNTK'.date('Ymdhis',time()).str_random(6),
            'notify_url'=>config('wxpay.refund_notify_url'),
        ];
        $wxpay=new WxpayService();
        $res = $wxpay->trade_refund($data);
        if($res['return_code']=='SUCCESS' && $res['result_code']=='SUCCESS'){
            return Send::successMsg('订单退款成功');
        }else{
            return Send::errorMsg(500,'订单退款失败');
        }
    }

回调类

class Pays
{
    public function wxpay_notify(Request $request){
        $wxpay  = new WxpayService();
        $res = $wxpay->notify();//notify方法中已经进行了验签
        //验签成功
        if($res['result']){
        	...................
        	...................
             查询自己系统订单表,获取订单信息
        	...................
        	...................
            if($order){
                //后验证金额是否一致
                if($order['amount']*100 == $res['result']['total_fee']){
                    //更新订单状态,判断该笔订单是否在商户网站中已经做过处理
                    if($order['status']=='0') {//订单状态为待支付
                        $update['id']=$order['id'];
                        $update['paytime']=microtime($res['result']['time_end']);//13位时间戳转10位
                        $update['status']='1';
                        $update['payment']='微信';
                        $update['trade_no']=$res['result']['transaction_id'];
                        ...................
                        ...................
                         更新自己系统的订单状态
                        ...................
                        ...................
                        echo $res['str'];
                    }
                }
            }

        }else{
            echo $res['str'];
        }

    }
    public function wxpay_refund_notify(Request $request){
        $wxpay=new WxpayService();
        $res = $wxpay->refund_notify();//notify方法中已经进行了验签
        //验签成功
        if($res['result']){
            ...................
        	...................
             查询自己系统订单表,获取订单信息
        	...................
        	...................
            if($order){
                if($res['result']['refund_status']=='SUCCESS'){
                    $update['id']=$order['id'];
                    $update['refundtime']=time();
                    $update['status']='5';
                    $update['refund_reason']='问诊订单取消-退款';
                     ...................
                     ...................
                         更新自己系统的订单状态
                     ...................
                     ...................
                    echo $res['str'];
                }
            }

        }else{
            echo $res['str'];
        }

    }

}

标签:ch,return,微信,data,result,str,支付,curl,退款
来源: https://blog.csdn.net/sang521jia/article/details/112238603

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有