jsapi/小程序/h5/微信支付及订阅消息发送

微信支付类

<?php
namespace extend;

class WechatService
{
    private $appid = '';
    private $mch_id = '';
    private $key = '';
    private $secret = '';
    private $notify_url = '';

    private $sslCert_path = '/data/apiclient_cert.pem';
    private $sslKey_path = '/data/apiclient_key.pem';
    
    static $access_token_cache = './access_token_cache.txt';

    public function __construct()
    {
    }
    /**
     * 微信支付统一下单
     * @param string $out_trade_no  商户订单号
     * @param array $params  [out_trade_no,body,price]必填 [openid,notify_url]选填,例如
     *                       $params['out_trade_no'] = createOrderSn();//商户交易号
     *                       $params['body']='订单支付xxxxxx';//商品描述
     *                       $params['price']=0.01;//价格
     *                       $params['openid']='oycwZ4zZYZEaOj1F4HVf08dJQs68';//openid
     * @param string $trade_type  JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付
     */
    public function wechat_pay($params, $trade_type = 'JSAPI')
    {
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $now = time();
        if(isset($params['notify_url'])){
            $this->notify_url=$params['notify_url'];
        }
        $data = [
            'appid' => $this->appid,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->getNonceStr(),
            'body' => $params['body'],
            'out_trade_no' => $params['out_trade_no'],
            'total_fee' => $params['price']*100,
            'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
            'notify_url' => $this->notify_url,
            'sign_type' => 'MD5',
            'trade_type' => $trade_type,
        ];
        if ($trade_type == 'JSAPI') {
            $data['openid'] = $params['openid'];
        }elseif ($trade_type == 'MWEB') {
            $data['scene_info'] = '{"h5_info": {"type":"Wap","wap_url": "'.$_SERVER['HTTP_HOST'].'","wap_name": "'.$params['body'].'"}}';
        }
        $this->sign($data);
        $xml = $this->ToXml($data);
        if (!$xml) {
            return false;
        }
        $result = $this->postXmlCurl($xml, $url);
        if (!$result) {
            return false;
        }
        $return = $this->FromXml($result);
        if (!$return) {
            return false;
        }
        if ($trade_type == 'JSAPI') {
            $res = [
                'appId' => $return['appid'],
                'timeStamp' => strval($now),
                'nonceStr' => $return['nonce_str'],
                'package' => "prepay_id=".$return['prepay_id'],
                'signType' => 'MD5',
            ];
            $this->sign($res);
            $res['paySign']=$res['sign'];
            unset($res['sign']);
            return array_merge($res,[
                'result_code' => $return['result_code'],
                'return_code' => $return['return_code'],
            ]);
        }
        return $return;
    }

    /**
     * 签名
     * @param array $data  需要签名的数据
     */
    private function sign(&$data)
    {
        ksort($data);
        $text = '';
        foreach ($data as $key => $val) {
            $text == '' ? $text .= $key.'='.$val : $text .= '&'.$key.'='.$val;
        }
        $data['sign'] = md5($text.'&key='.$this->key);
    }
    
    /**
     * 输出随机字符串
     * @param int $length  字符串长度
     */
	private 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字符
     * @param array $data  需要post的xml数据
     */
	private function ToXml($data)
	{
		if(!is_array($data) 
			|| count($data) <= 0)
		{
    		return false;
    	}
    	
    	$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; 
    }
    
    
    /**
     * 将xml转为array
     * @param string $xml
     */
	private function FromXml($xml)
	{	
		if(!$xml){
			return false;
		}
        //将XML转为array
        //禁止引用外部xml实体
        $disableLibxmlEntityLoader = libxml_disable_entity_loader(true); //添加这句 2018.07.05
        $return = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        libxml_disable_entity_loader($disableLibxmlEntityLoader); //添加这句 2018.07.05		
		return $return;
	}
    
    
	/**
	 * 以post方式提交xml到对应的接口url
	 * 
	 * @param string $xml  需要post的xml数据
	 * @param string $url  url
	 * @param bool $useCert 是否需要证书,默认不需要
	 * @param int $second   url执行超时时间,默认30s
	 */
	private function postXmlCurl($xml, $url, $method = 'POST', $useCert = false, $second = 30)
	{		
		$ch = curl_init();
		//设置超时
		curl_setopt($ch, CURLOPT_TIMEOUT, $second);
		
		curl_setopt($ch,CURLOPT_URL, $url);
		// curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
		// curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
		if(stripos($url,"https://")!==FALSE){
        	//curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
        	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        }else{
		    curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
		    curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
		}

		//设置header
		curl_setopt($ch, CURLOPT_HEADER, FALSE);
		//要求结果为字符串且输出到屏幕上
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
	
		if($useCert == true){
			//设置证书
			//使用证书:cert 与 key 分别属于两个.pem文件
			curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLCERT, $this->sslCert_path);
			curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLKEY, $this->sslKey_path);
		}
        //post提交方式
        if ($method == 'POST') {
            curl_setopt($ch, CURLOPT_POST, TRUE);
        }
		if (is_array($xml)) {
            $xml = json_encode($xml);
		}
		curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
		//运行curl
		$data = curl_exec($ch);
		//返回结果
		if($data){
            curl_close($ch);
            return $data;
		} else {
			$error = curl_errno($ch);
			curl_close($ch);
			return false;
		}
    }
    
    public function orderquery($out_trade_no)
    {
        // 查询订单状态
        $url = 'https://api.mch.weixin.qq.com/pay/orderquery';
        $data = [
            'appid' => $this->appid,
            'mch_id' => $this->mch_id,
            'out_trade_no' => $out_trade_no,
            'nonce_str' => $this->getNonceStr(),
        ];
        $this->sign($data);
        $xml = $this->ToXml($data);
        if (!$xml) {
            return false;
        }
        $result = $this->postXmlCurl($xml, $url);
        if (!$result) {
            return false;
        }
        $return = $this->FromXml($result);
        if (!$return) {
            return false;
        }
        return $return;
    }

    public function getAccessToken($code) {
        if (empty($code)) {
            return false;
        }
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token';
        $data = [
            'grant_type' => 'authorization_code',
            'appid' => $this->appid,
            'secret' => $this->secret,
            'code' => $code,
        ];
        $data = json_decode($this->postXmlCurl($data,$url,"GET"),true);
        if (!empty($data['access_token']) && !empty($data['openid'])) {
            return $data;
        } else {
            return false;
        }
    }

    public function getUserInfo($access_token,$openid) {
        $url = 'https://api.weixin.qq.com/sns/userinfo';
        $data = [
            'access_token' => $access_token,
            'openid' => $openid,
            'lang' => 'zh_CN',
        ];
        $data = json_decode($this->postXmlCurl($data,$url,"GET"),true);
        if (!empty($data['openid'])) {
            return $data;
        } else {
            return false;
        }
    }


    /**
     * 退款
     */
    public function refund($out_trade_no,$out_refund_no,$total_fee,$refund_fee) {
        $url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
        $data = [
            'appid' => $this->appid,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->getNonceStr(),
            'out_trade_no' => $out_trade_no,
            'out_refund_no' => $out_refund_no,
            'total_fee' => $total_fee,
            'refund_fee' => $refund_fee,
        ];
        $this->sign($data);

        $xml = $this->ToXml($data);
        if (!$xml) {
            return false;
        }
        $result = $this->postXmlCurl($xml, $url, 'POST', true);
        if (!$result) {
            return false;
        }
        $return = $this->FromXml($result);
        if (!$return) {
            return false;
        }
        return $return;
    }
    /**
     * 服务器端获取AccessToken
     */
    public function getAccessTokenServic() {
        $data = json_decode(file_get_contents(self::$access_token_cache),true);
        if ($data['expire_time']+$data['expires_in'] > time()) {
            return $data['access_token'];
        }else{
            $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$this->appid.'&secret='.$this->secret.'';
            $res = json_decode(file_get_contents($url), true);
            if (empty($res['access_token'])) {
                return false;
            }
            $res['expire_time']=time();
            file_put_contents(self::$access_token_cache,json_encode($res));
            return $res['access_token'];
        }
    }
    // 发送订阅消息
    public function sendMessage(){
        // 小程序前端需要wx.requestSubscribeMessage,弹框用户授权订阅
        $access_token=$this->getAccessTokenServic();
        if(!$access_token){
            return false;
        }
        //发送模板消息url
        $urls = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=' . $access_token;
        //发送内容
        $data=[];
        $data['touser']='xxx';
        $data['template_id']='xxxx';
        // $data['page']='';//跳转页面不填则模板无跳转。
        // $data['lang']='zh_CN';
        $data['data']=[
            "phrase1" => [
                'value' => '已完成'
            ],
            "time2" => [
                'value' => '2023-11-12'
            ],
            "thing3" => [
                'value' => '李理2'
            ],
        ];
        $data['miniprogram_state'] = 'trial';//跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版

        $result = $this->postXmlCurl($data,$urls);
        if ($result)
		{
			$json = json_decode($result,true);
			if (!$json || !empty($json['errcode'])) {
                return $json;
			}
			return true;
		}
		return false;
    }
}

订阅消息发送

                $wechatService = new WechatService();
                $res = $wechatService->sendMessage();
                dd($res);

微信支付

                $wechatService = new WechatService();
                $data['out_trade_no'] = createOrderSn();//商户交易号
                $data['body']='订单支付'.$equipmentInfo['order_sn'];//商品描述
                $data['price']=$equipmentInfo['total_price'];//价格
                $open_id = model('thirdLogin')->findValue(['user_id' => $this->userId], 'openid');
                if(!$open_id){
                    $this->error('请先授权获取openid');
                }
                $data['openid']=$open_id;
                $data['notify_url']=domain().'/common/public/wechatNotify';//回调
                    
                $res = $wechatService->wechat_pay($data);
                if ($res['result_code'] == "SUCCESS" && $res['return_code'] == "SUCCESS") {
                    //更新订单商户交易号
                    $equipmentModel->updateData(['out_trade_no'=>$data['out_trade_no']],['id'=>$equipmentInfo['id']]);
                    $this->success($res);
                }else{
                    $this->error('获取支付信息失败');
                }

微信回调方法


    public function wechatNotify() {

        $returnFAIL='<xml><return_code><![CDATA[FAIL]]></return_code></xml>';
        $returnSUCCESS='<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>';
        
        $xml = file_get_contents('php://input');
        if(!$xml){
            echo $returnFAIL;exit;
        }
        libxml_disable_entity_loader(true);
        $msg = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        
        $wechatService = new WechatService();
        $result = $wechatService->orderquery($msg['out_trade_no']);//查询订单
        // $result['return_code']='SUCCESS'; //测试数据
        // $result['result_code']='SUCCESS';
        // $result['trade_state']='SUCCESS';
        // $result['total_fee']='7400';
        // $result['out_trade_no']='2304058623';
        // $result['transaction_id']='1004400740201409030005092168';
        // error_log(print_r($result, 1), 3, TMP_PATH . 'log/payWx_' . date('Ymd', time()) . '.log');
        if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS' && $result['trade_state'] == 'SUCCESS') {
            // 更改订单状态
            $update=$this->paySuccess($result);

            if($update){
                echo $returnSUCCESS;exit;
            }else{
                echo $returnFAIL;exit;
            }
        } else {
            echo $returnFAIL;exit;
        }
    }

    private function paySuccess($params) {

        $equipmentModel = model('equipment');
        $equipmentInfo = $equipmentModel->findOne(['out_trade_no'=>$params['out_trade_no']],['id,is_pay,total_price']);
        if (!$equipmentInfo) {
            return false;
        }
        if ($equipmentInfo['is_pay'] == 1) {
            return true;
        }
        // 金额是否相同
        if($params['total_fee']!=$equipmentInfo['total_price']*100){
            return false;
        }
        $now = time();
        try {
            Db::pdo()->beginTransaction();
            $ordersModel = model('equipment');
            $updateData['status']=11;//已支付状态
            $updateData['pay_id']=1;//在线支付
            $updateData['is_pay']=1;//已支付
            $updateData['pay_time']=$now;//支付时间
            $updateData['transaction_id']=$params['transaction_id'];//第三方平台交易流水号
            $orderRes    = $ordersModel->updateData($updateData, ['out_trade_no'=>$params['out_trade_no']]);
            if($orderRes){
                Db::pdo()->commit();
                return true;
            }else{
                Db::pdo()->rollBack();
                return false;
            }
        } catch (\Exception $e) {
            return false;
        }
    }

前端代码

<!DOCTYPE html>
<html>

<head>
    <include file="public@head" />
</head>
<php>
    $head_no_search = true;

    $is_weixin = strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false ? true : false;
</php>
<body>
    <include file="public@nav" />
    <main>
        <div class="pay_msg_box">
            <div class="pay_msg_item1">
                <div class="icon gou"></div>
                <h4>订单提交成功</h4>
                <p class="date">您的订单在<span class="m_color1">24小时</span>内未支付将被取消,
                    请尽快完成支付!
                </p>
                <div class="box">
                    <div class="item">
                        订单号:
                        <span class="num">{$order.order_number}</span>
                    </div>
                    <div class="item">
                        订单总额:
                        <span class="price">¥ {$order.pay_price*1}</span>
                    </div>
                </div>
            </div>
            <div class="zf_title">选择支付方式</div>
            <div class="zf_list">
                <div class="zf_item" <if condition="$is_weixin">style="display:none;"</if>> 
                    <div class="name zfb_pay">支付宝支付</div>
                    <label class="zf_radio">
                        <input type="radio" name="pay">
                        <span></span>
                    </label>
                </div>
                <div class="zf_item"> 
                    <div class="name wx_pay">微信支付</div>
                    <label class="zf_radio">
                        <input type="radio" name="pay">
                        <span></span>
                    </label>
                </div>
                <div class="zf_item"> 
                    <div class="name yhk_pay">对公付款支付</div>
                    <label class="zf_radio">
                        <input type="radio" name="pay">
                        <span></span>
                    </label>
                </div>
            </div>

            <a class="pay_btn btn_icon active" href="javascript:;" onclick="submitPay()">立即支付</a>
            <!-- 公司信息 -->
            <div class="order_gs_msg" style="display: none;">
                <p>单位名称:{$site_info.corporate_payment_company}</p>
                <p>开户行:{$site_info.corporate_payment_bank}</p>
                <p>账号:{$site_info.corporate_payment_account}</p>
            </div>

            <div class="pay_msg_item2">
                <div class="title">温馨提示</div>
                <div class="box">
                    <p>{:htmlspecialchars_decode($site_info.site_pay_tip)}</p>
                </div>
            </div>
        </div>
    </main>
    <div class="add_cover" style="display: none;">
	</div>
    <div class="foot_zw"></div>
    <include file="public@footer" />
</body>

</html>
<include file="public@script" />
<script>
    $(function () { 
         $('.zf_radio').click(function(){
            var index =  $(this).parents('.zf_item').index()
            var nub = $('.zf_item').length
            if(index == nub - 1){
                $('.btn_icon').hide()
                $('.order_gs_msg').show()
            }else{
                $('.btn_icon').show()
                $('.order_gs_msg').hide()
            }
         })

    })
    Wind.use('noty3');
    var paycon_loop = null;
    var ajax_post = true;
    var pay_post = true;
    function submitPay() {
        var pay_type = 0;
        $('.zf_list input').each(function(k,v) {
            if ($(v)[0].checked) {
                pay_type = k+1
            }
        })
        
        if (pay_type == 0) {
            new Noty({
                text: '请选择支付方式',
                type: 'error',
                layout: 'center',
                modal: true,
                animation: {
                    open: 'animated bounceInDown', // Animate.css class names
                    close: 'animated bounceOutUp', // Animate.css class names
                },
                timeout: 1.5,
            }).show();
            return;
        }

        if (pay_post) {
            pay_post = false
        } else {
            return;
        }

        var wap = 1;
        if (pay_type == 2 && is_weixin) {
            wap = 2;
        }
        $.ajax({
            url: "{:url('goods/details/payPost')}",
            method: "post",
            data: {
                id: "{$order.id}",
                type: pay_type,
                wap: wap,
            },
            success: function(res) {
                pay_post = true
                if (res.code == 1) {
                    if (pay_type == 1) {
                        $('.add_cover').html(res.data.html);
                        // document.forms['alipaysubmit'].submit();
                        document.forms['alipaysubmit'].submit();
                        if (paycon_loop) {
                            clearInterval(paycon_loop)
                        }
                        paycon_loop = setInterval(function(){
                            synConfirm(res.data.log_id)
                        }, 2000);
                    } else if (pay_type == 2) {
                        // $('.add_cover').html('<div style="padding-top: 25%;text-align: center;"></div><h1 style="text-align: center;font-size: .5rem;font-weight: 700;">请扫码支付</h1>')
                        // $('.add_cover').find('div').qrcode({
                        //     render: "canvas",
                        //     width: 250,
                        //     height: 250,
                        //     text: res.data.code_url,
                        // })
                        // $('.add_cover').show();
                        // $('.wx_pay_mask').find('.img_box').html('').qrcode({
                        //     render: "canvas",
                        //     width: 210,
                        //     height: 210,
                        //     text: res.data.code_url,
                        // })
                        // $('.wx_pay_mask').show();
                        if (wap == 1 && res.data.mweb_url) {
                            var url = res.data.mweb_url+'&redirect_url='+encodeURIComponent(window.location.href)
                            window.location.href = url
                            // window.open(res.data.mweb_url)
                            if (paycon_loop) {
                                clearInterval(paycon_loop)
                            }
                            paycon_loop = setInterval(function(){
                                synConfirm(res.data.log_id)
                            }, 2000);
                        } else if (wap == 2 && res.data) {
                            WeixinJSBridge.invoke('getBrandWCPayRequest', {
                                    "appId":res.data.appId,
                                    "timeStamp":res.data.timeStamp,
                                    "nonceStr":res.data.nonceStr,
                                    "package":res.data.package,
                                    "signType":res.data.signType,
                                    "paySign":res.data.sign,
                                },
                                function(res){
                                if(res.err_msg == "get_brand_wcpay_request:ok" ){
                                    synConfirm(res.data.log_id)
                                }
                            });
                            if (paycon_loop) {
                                clearInterval(paycon_loop)
                            }
                            paycon_loop = setInterval(function(){
                                synConfirm(res.data.log_id)
                            }, 2000);
                        } else {
                            new Noty({
                                text: '请求失败,请稍后重新尝试',
                                type: 'error',
                                layout: 'center',
                                modal: true,
                                animation: {
                                    open: 'animated bounceInDown', // Animate.css class names
                                    close: 'animated bounceOutUp', // Animate.css class names
                                },
                                timeout: 1.5
                            }).show();
                            return;
                        }
                    } else {
                        new Noty({
                            text: res.msg,
                            type: 'success',
                            layout: 'center',
                            modal: true,
                            animation: {
                                open: 'animated bounceInDown', // Animate.css class names
                                close: 'animated bounceOutUp', // Animate.css class names
                            },
                            timeout: 1.5,
                            callbacks: {
                                afterClose: function () {
                                    window.location.href = "{:url('user/user/orderList')}"
                                }
                            }
                        }).show();
                        return;
                    }
                } else {
                    new Noty({
                        text: res.msg,
                        type: 'error',
                        layout: 'center',
                        modal: true,
                        animation: {
                            open: 'animated bounceInDown', // Animate.css class names
                            close: 'animated bounceOutUp', // Animate.css class names
                        },
                        timeout: 1.5,
                    }).show();
                    return;
                }
            },
            error: function() {
                pay_post = true
                new Noty({
                    text: '服务器错误,请联系管理员',
                    type: 'error',
                    layout: 'center',
                    modal: true,
                    animation: {
                        open: 'animated bounceInDown', // Animate.css class names
                        close: 'animated bounceOutUp', // Animate.css class names
                    },
                    timeout: 1.5,
                }).show();
                return;
            }
        })
    }

    
    function synConfirm(log_id) {
        if (ajax_post) {
            ajax_post = false;
            $.ajax({
                url:"{:Url('goods/details/ajaxCheckPay')}", 
                method: "post",
                data:{
                    log_id:log_id,
                },
                success:function(res){
                    ajax_post = true
                    if (res.code == 1) {
                        clearInterval(paycon_loop)
                        location.href = "{:url('goods/details/paySuccess',['id'=>$order.id])}"
                    } else {

                    }
                }
            })
        }
    }

</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值