2

序言

随着微信被越来越多的人使用,微信商城成为如今的热门。每一个商城都需要有自己的支付方式,微信商城也不例外。微信公众号支付就是微信商城的一种支付方式,微信支付随着微信的推广使用也被广泛应用。今天我主要讲的是yii2嵌入微信公众号支付,微信官网的微信支付文档比较简洁,接下来跟着我来看一下yii2如何嵌入微信公众号支付,以及需要注意的事项吧!

前期准备工作

首先必须有一个微信公众号,并且需要在微信公众平台申请微信支付。微信公众平台的地址是:https://mp.weixin.qq.com/cgi-bin/loginpage。申请的过程我这边就不写了,录公众平台以后就可以看到。
其次需要先配置一个域名,必须是80端口,然后在申请完微信支付以后配置上支付授权目录。
准备工作完成以后我们可以开始开发了。

代码分析

1、我们需要获取到关注微信公众号的人的openid。获取openid,我这边是通过网页授权获取用户基本信息接口获取的。其实在github上已经有封装好的关于微信的开发的接口,我们可以直接从上面下载,以后基于yii2的开发微信的其他的功能可以使用。网址是:https://github.com/callmez/yii2-wechat-sdk 大家可以下载然后安装。我的openid的获取也是在此基础上更改的。安装完成以后的目录结构如下图:
图片描述

我这边使用的是MpWechat.php,在MpWechat.php中我增加了一个方法获取openid。

public function getOpenid($turl)
    {
        if (!isset($_GET['code'])){
            //触发微信返回code码
            
             $url=$this->getOauth2AuthorizeUrl($turl, $this->state, 'snsapi_userinfo');
            Header("Location: $url");
            exit();
        } else {
            //获取code码,以获取openid
            $code = $_GET['code'];
            $access_info = $this->getOauth2AccessToken($code);
            return $access_info;
        }
        
    }

$turl是获取到code以后需要跳转的地址。
在控制其中获取openid我是这样写的:

 public function actionView($id)
    {
        
        $openid=isset($_COOKIE['openid'])?$_COOKIE['openid']:'';
            
            if(empty($openid))
            {
                $t_url=Url::to(['@web/clue/view?id='.$id],true);//这个是获取到code以后需要跳转的url。Url这个是在头部需要增加:use yii\helpers\Url
                $info=$this->wechat->getOpenid($t_url);//注:$this->wechat安装完成yii2-wechat-sdk以后配置以后可以使用
                if($info){
            setcookie('access_token',$info['access_token'],time()+86400*365);
                  setcookie('openid',$info['openid'],time()+86400*365);    
                  
                }
                
            }
        
    }
将获取到的openid放入的cookie中储存。

2、openid获取以后可以进行开发微信支付,我这边将微信支付封装成了一个类,可以配置访问。

第一步、我们可以在common/config/main.php中配置

return [
    'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
    'language' => 'zh-CN', // 启用国际化支持
    'sourceLanguage' => 'zh-CN', // 源代码采用中文
    'timeZone' => 'Asia/Shanghai', // 设置时区
    'components' => [
        'cache' => [
            'class' => 'yii\caching\FileCache',
        ],
        'user' => [            
            'identityClass' => 'login\models\User',
            'enableAutoLogin' => false,  //开启authTimeout失效
            'identityCookie' => ['name' => '_identity', 'httpOnly' => true,'domain' => '.' . DOMAIN],
            // 'returnUrl'=>'//' . DOMAIN_HOME,
            'authTimeout' => 24*3600*30, // 30 days 
        ],        

        'session' => [
            'timeout' => 24*3600*30, // 30 days
            'useCookies' => true,
            'cookieParams' => [
                'domain' => '.' . DOMAIN,
                'lifetime' => 24*3600*30,
            ],
        ],
        //这个是支付的
        'payment' => [
            'class'=>'common\widgets\payment\Instance',
                
                'weixinjspi_config' => [
                    'code'      => 2,
                    'appid'     => 'wx88ee3ca8c06be5c6',//微信的appid
                    'secret'    => '48180f87c2693d50b29d822d019999',//appsecret,在申请完公众号以后可以看到
                    'mch_id'    => '13260614455',//商户号
                    'key'       => '16ceshi',//key需要设置
                    'cert_path' => '',//可以不用填写
                    'key_path'  => '',//可以不用填写
                ],
               
        ],
    ],

我上面的appid、key等都是错误信息需要你们根据自己申请的微信支付进行填写
第二步、在commonwidgets中加入payment文件夹,在文件夹中增加Instance.php

<?php

namespace common\widgets\payment;

use yii\base\Component;

class Instance extends Component{
    private $weixin_config;
    private $weixins_config;
    private $weixinjspi_config;
    private $alipay_config;
    private $balance_config;
    private $_weixin = null;
    private $_weixins = null;
    private $_alipay = null;
    private $_balance = null;
    private $_weixinjspi = null;

    public function setWeixin_config($config)
    {
        $this->weixin_config = $config;
    }
    public function setWeixins_config($config)
    {
        $this->weixins_config = $config;
    }
    public function setWeixinjspi_config($config)
    {
        $this->weixinjspi_config = $config;
    }
    public function setAlipay_config($config)
    {
        $this->alipay_config = $config;
    }
    public function setBalance_config($config)
    {
        $this->balance_config = $config;
    }

    /**
     * 组合使用余额支付和其他支付
     * @param $order
     * @param $payment
     * @param $balance
     */
    public function unionPay($order,$payment,$balance,$notify)
    {
        $res = [];
        $total_fee = $order['total_fee'];
        if($balance > 0)
        {
            $pay = $this->getBalance();
            if($balance >= $total_fee)
            {
                $res['balance'] = $pay->pay($order);
                return $res;
            }
            else
            {
                $order['total_fee'] = $balance;
                $res['balance'] = $pay->pay($order);
                $total_fee -= $balance;
            }
        }
        $order['total_fee'] = $total_fee;
        $pay = $this->$payment;
        $pay->setNotifyUrl($notify);
        $res[$payment] = $pay->prepay($order);
        //$res[$payment]['balance'] = $balance;
        return $res;
    }

    /**
     * 获得支付宝支付
     * @param null $notify_url
     * @return mixed|object
     */
    public function getAlipay($notify_url = null)
    {
        $this->alipay_config = array_merge(['class'=>Alipay::className()],$this->alipay_config);
        //'var_dump($this->alipay_config);exit;
        return $this->_getPayment('_alipay',$notify_url);
    }

    /**
     * 获得微信app c端支付
     * @param null $notify_url
     * @return mixed|object
     */
    public function getWeixin($notify_url = null)
    {
        $this->weixin_config = array_merge(['class'=>Weixin::className()],$this->weixin_config);
        return $this->_getPayment('_weixin',$notify_url);
    }
    /**
     * 获得手机微信端支付
     * @param null $notify_url
     * @return mixed|object
     */
    public function getWeixinjspi($notify_url = null)
    {
        $this->weixinjspi_config = array_merge(['class'=>Weixinjspi::className()],$this->weixinjspi_config);
        return $this->_getPayment('_weixinjspi',$notify_url);
    }

    /**
     * 获得微信app s端支付
     * @param null $notify_url
     * @return mixed|object
     */
    public function getWeixins($notify_url = null)
    {
        $this->weixins_config = array_merge(['class'=>Weixin::className()],$this->weixins_config);
        return $this->_getPayment('_weixins',$notify_url);
    }

    /**
     * 获得余额支付
     * @return mixed|object
     */
    public function getBalance()
    {
        $this->balance_config = array_merge(['class'=>Balance::className()],$this->balance_config);
        return $this->_getPayment('_balance',null);
    }

    /**
     * @param $name
     * @param $notify_url
     * @return mixed|object
     * @throws \yii\base\InvalidConfigException
     */
    private function _getPayment($name, $notify_url)
    {
        $config = substr($name.'_config',1);
        if(is_null($this->{$name}))
        {
            $this->{$name} = \Yii::createObject($this->{$config});
        }
        if(!is_null($notify_url))
        {
            $this->{$name}->setNotifyUrl($notify_url);
        }
        return $this->{$name};
    }
}

这个类其实是结合了其他的支付方式,其中现在最主要的是getWeixinjspi($notify_url = null)这个方法。
除了这个还需要增加Weixinjspi.php

<?php
namespace common\widgets\payment;

use Yii;
use yii\base\InvalidConfigException;
use yii\base\InvalidValueException;

class Weixinjspi extends Payment
{
    public $order_url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
    public $search_order_url = 'https://api.mch.weixin.qq.com/pay/orderquery';
    public $close_order_url = 'https://api.mch.weixin.qq.com/pay/closeorder';
    public $refund_url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
    public $notify_url;
    public $search_refund_url = 'https://api.mch.weixin.qq.com/pay/refundquery';
    public $order_pre = 'Weixin';

    private $appid;
    private $mch_id;
    private $key;
    private $key_path;
    private $secret;
    private $cert_path;
    private $notify_data;
    private $curl_proxy_host;
    private $curl_proxy_port;
    
    public function setAppid($appid)
    {
        $this->appid = $appid;
    }

    public function setKey($key)
    {
        $this->key = $key;
    }

    public function setSecret($secret)
    {
        $this->secret = $secret;
    }

    public function setKey_path($key_path)
    {
        $this->key_path = Yii::getAlias($key_path);
    }
    public function setCert_path($cert_path)
    {
        $this->cert_path = Yii::getAlias($cert_path);
    }

    public function setMch_id($mch_id)
    {
        $this->mch_id = $mch_id;
    }

    public function init()
    {
        parent::init();
        $needs = array('appid','mch_id','key','secret');
        foreach($needs as $need)
        {
            if(empty($this->{$need}))
            {
                throw new InvalidConfigException(get_class($this) . " must define weixin's params {$need}.");
            }
        }
    }

    private function _checkRefund()
    {
        $needs = array('key_path','cert_path');
        foreach($needs as $need)
        {
            if(empty($this->{$need}))
            {
                throw new InvalidConfigException(get_class($this) . " must define weixin's params {$need}.");
            }
        }
    }

    /**
     * jsapi支付接口
     * @param $order
     * @return mixed
     */
    public function pay($order)
    {
        $paras = [
            'body'              =>$order['goods_desc'],//设置商品或支付单简要描述
            'attach'            =>'PC',//设置附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
            'out_trade_no'      =>$this->order_pre.$order['order_sn'],//订单号
            'total_fee'         =>(int)($order['total_fee'] * 100),//订单总金额
            'detail'            =>$order['body'],
            'time_start'        =>empty($order['time_start'])? '':$order['time_start'],
            'time_expire'       =>empty($order['time_expire'])? '':$order['time_expire'],
            'goods_tag'         =>empty($order['goods_tag'])? '':$order['goods_tag'],//设置商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠
            'notify_url'        =>empty($order['notify_url'])? '':$this->setNotifyUrl($order['notify_url']),//回调地址
            'trade_type'        =>'JSAPI',
            'product_id'        =>empty($order['order_sn'])? '':$order['order_sn'],
            'openid'            =>empty($order['openid'])? '':$order['openid'],
            'nonce_str'         =>$this->randomStr(),
           
        ];
        ///var_dump($paras);exit;
        $timeout = empty($order['timeout'])? 6 :$order['timeout'];
        if($order['total_fee'] <= 0)
        {
            throw new InvalidValueException(get_class($this) . " 支付金额必须大于0");
        }
        $trade = $this->createOrder($paras,$timeout);
        
         if(isset($trade['return_code']) && $trade['return_code'] == 'SUCCESS')
        {
            if(isset($trade['result_code']) && $trade['result_code'] == 'SUCCESS')
            {
                
                $data=[
                    'appId'    =>$trade['appid'],
                    'timeStamp'=>"".time()."",
                    'nonceStr' =>$this->randomStr(),
                    'signType'=>'MD5',
                    'package'  =>'prepay_id='.$trade['prepay_id'],    
                ];
                $data['paySign']=$this->sign($data);
                //$data['signature']=$order['signature'];
                $parameters = json_encode($data);
               return $parameters;//二维码的信息
            }
            else
            {
                
                return false;
            }
        }
        else
        {
            return false;
        }
    }
 
    public function getJs($parameters,$order_sn)
    {
        
        $str="<script type=\"text/javascript\">
    //调用微信JS api 支付
    function jsApiCall()
    {
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest',
            ".$parameters.",
            function(res){
                
                 if(res.err_msg == \"get_brand_wcpay_request:ok\")
                 {
                     $.ajax({
                            type:\"get\",
                            url:\"/clue/pay_status?order_sn=".$order_sn."\",
                            datatype:\"json\",
                            data:{},
                            success: function(data_)
                            {
                               window.location.reload();
                   
                            }
                        })
                     alert('支付成功!');
                    
                 }else if(res.err_msg == \"get_brand_wcpay_request:cancel\")
                 {
                     
                     alert('支付过程中用户取消!');
                     
                 }else{
                     
                    alert('支付失败!'); 
                 }    
      

            }
        );
    }

    function callpay()
    {
        if (typeof WeixinJSBridge ==\"undefined\"){
            if( document.addEventListener ){
                document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
            }else if (document.attachEvent){
                document.attachEvent('WeixinJSBridgeReady', jsApiCall); 
                document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
            }
        }else{
            jsApiCall();
        }
    }
    </script>
        ";
        return $str;
    }
    /**
     * 预支付接口,在APP上发起支付
     * @param $order
     * @return mixed
     */
    public function prepay($order)
    {
        $needs = array('title','order_sn','body','total_fee');
        foreach($needs as $need)
        {
            if(!isset($order[$need]))
            {
                throw new InvalidConfigException(get_class($this) . " \$order 中必须包含键 {$need}.");
            }
        }
        $paras = [
            'trade_type'        =>'APP',
            'time_start'        =>empty($order['time_start'])? '':$order['time_start'],
            'time_expire'       =>empty($order['time_expire'])? '':$order['time_expire'],
            'goods_tag'         =>empty($order['goods_tag'])? '':$order['goods_tag'],
            'device_info'       =>empty($order['device_info'])? '':$order['device_info'],
            'out_trade_no'      =>$this->order_pre.$order['order_sn'],
            'detail'            =>$order['body'],
            'total_fee'         =>(int)($order['total_fee'] * 100),
            'body'              =>$order['title'],
            'fee_type'          =>empty($order['fee_type'])? '':$order['fee_type'],
            'product_id'        =>empty($order['product_id'])? '':$order['product_id'],
            'openid'            =>empty($order['openid'])? '':$order['openid'],
            'attach'            =>empty($order['attach'])? '':$order['attach'],
            'notify_url'        =>empty($order['notify_url'])? '':$this->setNotifyUrl($order['notify_url']),//回调地址
        ];
        $timeout = empty($order['timeout'])? 6 :$order['timeout'];
        if($order['total_fee'] <= 0)
        {
            throw new InvalidValueException(get_class($this) . " 支付金额必须大于0");
        }
        $trade = $this->createOrder($paras,$timeout);
        if(isset($trade['return_code']) && $trade['return_code'] == 'SUCCESS')
        {
            if(isset($trade['result_code']) && $trade['result_code'] == 'SUCCESS')
            {
                $trade['total_fee'] = $order['total_fee'];
                return $this->getSign($trade);
            }
            else
            {
                throw new InvalidValueException(get_class($this) . $trade['err_code_des']);
            }
        }
        else
        {
            throw new InvalidValueException(get_class($this) . $trade['return_msg']);
        }
    }

    public function getSign($order)
    {
        $total_fee = $order['total_fee'];
        $keys = ['appid','partnerid','prepayid','package','noncestr','timestamp','sign'];
        $order['partnerid'] = $order['mch_id'];
        $order['prepayid'] = $order['prepay_id'];
        $order = array_intersect_key($order,array_fill_keys($keys,''));
        $order['package'] = 'Sign=WXPay';
        $order['timestamp'] = time();
        $order['noncestr'] = $this->randomStr(30);
        $order['sign'] = $this->sign($order);
        $order['total_fee'] = $total_fee;
        return $order;
    }

    public function notify()
    {
        //获取通知的数据
        $xml = $GLOBALS['HTTP_RAW_POST_DATA'];
        //如果返回成功则验证签名
        //try {
            $result = $this->fromXml($xml);
            if($result['return_code'] == 'SUCCESS')
            {
                $sign = $this->sign($result);
                if($sign == $result['sign'])
                {
                    $result['trade_no']=$result['transaction_id'];
                    $result['out_trade_no']=substr($result['out_trade_no'],strlen($this->order_pre));
                    $result['trade_status']='TRADE_SUCCESS';
                    return $result;
                }
                else
                {
                    return false;
                }
            }
            else
            {
               return false;
            }
       // } catch (\Exception $e){
       //     throw new InvalidValueException(get_class($this) . $e->errorMessage());
       // }
    }

    /**
     * 退款接口
     * @param $order
     * @return mixed
     */
    public function refund($order)
    {
        $this->_checkRefund();
        $needs = array('order_sn','total_fee');
        foreach($needs as $need)
        {
            if(!isset($order[$need]))
            {
                throw new InvalidConfigException(get_class($this) . " \$order 中必须包含键 {$need}.");
            }
        }
        $order['out_trade_no'] = $this->order_pre.$order['order_sn'];
        $order['total_fee'] = round($order['total_fee'],2) * 100;
        $order['refund_fee'] = $order['total_fee'];
        $order['op_user_id'] = $this->mch_id;

        $need = array('out_trade_no','out_refund_no','total_fee','refund_fee','op_user_id');
        $keys = ['device_info','refund_fee_type','transaction_id'];
        foreach($need as $key)
        {
            if(empty($order[$key])) {
                throw new InvalidConfigException("缺少退款申请接口必填参数{$key}!");
            }
        }

        $order = array_intersect_key($order,array_fill_keys(array_merge($need, $keys),''));
        $order['appid'] = $this->appid;
        $order['mch_id'] = $this->mch_id;
        $order['nonce_str'] = $this->randomStr();
        $order['sign'] = $this->sign($order);
        $xml = $this->toXml($order);
        $response = $this->postXmlCurl($xml, $this->refund_url);
        $result = $this->convertResponse($response);

        return $result;
    }

    /**
     * Notify处理完成接口
     * @return mixed
     */
    public function finish()
    {
        $arr = ['return_code'=>'SUCCESS'];
        $xml = $this->toXml($arr);
        return $xml;
    }

    /**
     * 设置Notify回调接口
     * @return mixed
     */
    public function setNotifyUrl($url)
    {
        $this->notify_url = $url;
    }

    /**
     * 获得Notify返回的支付金额
     * @return mixed
     */
    public function getTotalFee($total_fee = null)
    {
        if($total_fee)
        {
            return round($total_fee/100,2,PHP_ROUND_HALF_DOWN);
        }
        if(isset($this->notify_data['total_fee']))
        {
            return round($this->notify_data['total_fee']/100,2,PHP_ROUND_HALF_DOWN);
        }
        return false;
    }

    /**
     * 获得Notify返回的交易号
     * @return mixed
     */
    public function getSerialNo($arr = null)
    {
        if(isset($arr['transaction_id']))
        {
            return $arr['transaction_id'];
        }
        if(isset($this->notify_data['transaction_id']))
        {
            return $this->notify_data['transaction_id'];
        }
        return false;
    }

    /**
     * 获得Notify返回的原始数据
     * @return mixed
     */
    public function getNotifyRaw()
    {
        return $GLOBALS['HTTP_RAW_POST_DATA'];
    }

    public function randomStr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }

    public function getIp()
    {
        if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
            $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $strIp = $arr[0];
        } elseif (isset($_SERVER["HTTP_CLIENT_IP"])) {
            $strIp = $_SERVER["HTTP_CLIENT_IP"];
        } elseif (isset($_SERVER["REMOTE_ADDR"])) {
            $strIp = $_SERVER["REMOTE_ADDR"];
        } else {
            $strIp = "0.0.0.0";
        }
        return $strIp;
    }

    private function toXml($values)
    {
        if(!is_array($values) || count($values) <= 0)
        {
            throw new InvalidValueException("数组数据异常!");
        }
        //var_dump($values);exit;
        $xml = "<xml>";
        foreach ($values as $key=>$val)
        {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
                
            }
        }
        $xml.="</xml>";
        //echo $xml;exit;
        return $xml;
    }

    private function fromXml($xml)
    {
        if(!$xml){
            throw new InvalidValueException("xml数据异常!");
        }
        try
        {
            $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        }
        catch(\Exception $e)
        {
            throw new InvalidValueException("xml数据异常!");
        }
        return $values;
    }

    public function sign($values)
    {
        ksort($values);
        $string = "";
        foreach ($values as $k => $v)
        {
            if($k != "sign" && $v != "" && !is_array($v)){
                $string .= $k . "=" . $v . "&";
            }
        }

        $string = trim($string, "&");
        $string = $string . "&key=".$this->key;
        $string = md5($string);
        return strtoupper($string);
    }

    public function checkSign($values)
    {
        if($this->sign($values) == $values['sign']){
            return true;
        }
        throw new InvalidValueException("验证签名错误!");
    }

    private function convertResponse($xml)
    {
        $result = $this->fromXml($xml);
        if($result['return_code'] != 'SUCCESS')
        {
            throw new InvalidValueException($result['return_msg']);
        }
        if($this->checkSign($result) === true){
            return $result;
        }else{
            return false;
        }
    }

    public function searchOrder($out_trade_no, $transaction_id = '',$timeOut = 6)
    {
        if(empty($out_trade_no) && empty($transaction_id)) {
            throw new InvalidValueException("缺少订单查询接口必填参数out_trade_no或transaction_id!");
        }
        $order = ['out_trade_no'=>$out_trade_no,'transaction_id'=>$transaction_id];
        $order['appid'] = $this->appid;
        $order['mch_id'] = $this->mch_id;
        $order['nonce_str'] = $this->randomStr();
        $order['sign'] = $this->sign($order);

        $xml = $this->toXml($order);
        $response = $this->postXmlCurl($xml, $this->search_order_url, false, $timeOut);
        $result = $this->convertResponse($response);

        return $result;
    }

    public function closeOrder($out_trade_no, $timeOut = 6)
    {

        if(empty($out_trade_no)) {
            throw new InvalidValueException("缺少订单查询接口必填参数out_trade_no!");
        }
        $order = ['out_trade_no'=>$out_trade_no];
        $order['appid'] = $this->appid;
        $order['mch_id'] = $this->mch_id;
        $order['nonce_str'] = $this->randomStr();
        $order['sign'] = $this->sign($order);

        $xml = $this->toXml($order);
        $response = $this->postXmlCurl($xml, $this->close_order_url, false, $timeOut);
        $result = $this->convertResponse($response);

        return $result;
    }

    public function createOrder(array $order, $timeOut = 6)
    {
        //检测必填参数
        $need = array('out_trade_no','body','total_fee','trade_type');
        $keys = array('appid','mch_id','device_info','nonce_str','sign','detail','attach','fee_type',
            'spbill_create_ip','time_start','time_expire','goods_tag','notify_url','product_id','openid');
        $keys = array_merge($need,$keys);
        foreach($need as $key)
        {
            if(empty($order[$key])) {
                throw new InvalidValueException("缺少统一下单接口必填参数{$key}!");
            }
        }

        //关联参数
        if($order['trade_type'] == "JSAPI" && empty($order['openid'])){
            throw new InvalidValueException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");
        }
        if($order['trade_type'] == "NATIVE" && empty($order['product_id'])){
            throw new InvalidValueException("统一支付接口中,缺少必填参数product_id!trade_type为JSAPI时,product_id为必填参数!");
        }

        $order = array_intersect_key($order,array_fill_keys($keys,''));
        $order['appid'] = $this->appid;
        $order['mch_id'] = $this->mch_id;
        $order['notify_url'] = $this->notify_url;
        $order['spbill_create_ip'] = $this->getIp();
        $order['nonce_str'] = $this->randomStr();
        $order['sign'] = $this->sign($order);
        $xml = $this->toXml($order);
    //    var_dump( $order['notify_url']  );exit;
        $response = $this->postXmlCurl($xml, $this->order_url, false, $timeOut);
        //var_dump($response);exit;
        $result = $this->convertResponse($response);
        return $result;
    }

    public function searchRefund($order, $timeOut = 6)
    {
        $keys = ['out_refund_no','out_trade_no','transaction_id','refund_id'];
        if(empty($order['out_trade_no']) && empty($order['transaction_id']) && empty($order['out_refund_no']) && empty($order['refund_id'])) {
            throw new InvalidValueException("退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个!");
        }
        $order = array_intersect_key($order,array_fill_keys($keys,''));
        $order['appid'] = $this->appid;
        $order['mch_id'] = $this->mch_id;
        $order['nonce_str'] = $this->randomStr();
        $order['sign'] = $this->sign($order);
        $xml = $this->toXml($order);
        $response = $this->postXmlCurl($xml, $this->search_refund_url, true, $timeOut);
        $result = $this->convertResponse($response);

        return $result;
    }

    private function postXmlCurl($xml, $url, $useCert = false, $second = 30)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);

        //如果有配置代理这里就设置代理
        if($this->curl_proxy_host != "0.0.0.0" && $this->curl_proxy_port != 0){
            curl_setopt($ch,CURLOPT_PROXY, $this->curl_proxy_host);
            curl_setopt($ch,CURLOPT_PROXYPORT, $this->curl_proxy_port);
        }
        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);

        if($useCert == true && !empty($this->cert_path) && !empty($this->key_path)){
            //设置证书
            //使用证书:cert 与 key 分别属于两个.pem文件
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT, $this->cert_path);
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY, $this->key_path);
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        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);
            throw new InvalidValueException("curl出错,错误码:{$error}");
        }
    }
}

还需要增加一个Notifyurl.php

<?php

namespace common\widgets\payment;
use Yii;
use common\widgets\payment\Weixinjspi;

class Notifyurl
{
   
    public function notify($code)
   {
       
       $funame='get'.$code;
     
        $respmodel=Yii::$app->payment->$funame();
        $result=$respmodel->notify();
        if($result){
            //商户订单号

            $out_trade_no = $result['out_trade_no'];

            //交易号

            $trade_no = $result['trade_no'];

            //交易状态
            $trade_status = $result['trade_status'];


            if ($trade_status == 'TRADE_SUCCESS')
            {
                $this->update_status($out_trade_no,$trade_no,1);
                echo "success";
            }else{
                
                 echo "fail";
            }
            
        }else{
            echo "fail";
         
        }
       
       
       
   }
   //支付状态更改
   public  function update_status($out_trade_no,$trade_no,$pay_status)
   {
      
   }
   
}

?>

第三步、我们如何使用呢,
在我们需支付的controller中,
//微信支付

  public function actionWeixinpay()
    {
        
            $openid=isset($_COOKIE['openid'])?$_COOKIE['openid']:'';
        
           
            if($openid)
            {
                
                  $order_=[
                              'goods_desc'=>'测试',
                              'order_sn' =>'2222',
                              'total_fee' =>12,
                              'body'=>'测试',
                              'time_start' =>date("YmdHis"),
                              'time_expire'=>date("YmdHis", time() + 86400*300),
                              'goods_tag' =>'',
                              'notify_url'=>Url::to(['@web/respond/updatepay'],true),//这是回调地址,微信通知的地址
                              'openid'=>$openid,
                           
                  
                           ];
                  $paymodel=Yii::$app->payment->getWeixinjspi();
        
                   $result=$paymodel->pay($order_);//生成预付单
                 if($result)
                 {
                     $jsstr=$paymodel->getJs($result,$order_['order_sn']);
                    //根据预付单信息生成js,详细的可以看上面的类的方法。
                
                 }
                  
                
           }
              
                
            
           
        echo $jsstr;
        
        
    }

第四步、在view层我们可以这样写:

<button type="button" class="btn red-sunglo" onclick="pay_(2)" >
<div id="js_">
        

        </div>
<script>
//支付
        function pay_(id)
        {
            
             $.ajax({
                    type:"get",
                    url:"/ceshi/weixinpay?id="+id,//这是访问的地址
                    datatype:"json",
                    data:{},
                    success: function(data)
                    {
                       if(data!='')
                       {
                           
                           $("#js_").html(data);
                           callpay();
                          
                           
                       }
            //alert(data);
                    }
             })
        }
</script>

我这边是采用的ajax的形式进行支付的,可以根据自己的情况进行调整
第五步、支付最重要的是需要回调,yii2增加了Csrf验证,禁止了外部的访问,我们在控制其中写回调方法的时候必须增加:

public function beforeAction($action) {
        $this->enableCsrfValidation = false;
       return parent::beforeAction($action);
   }

不然微信将访问不到。
回调的方法使用:

 use common\widgets\payment\Weixinjspi;
use common\widgets\payment\Notifyurl;
 public function actionUpdatepay()
    {
        $model=new Notifyurl();
        $model->notify('Weixinjspi');
        
    }

以上就是我的微信支付。
第一次写文章,有不完善的地方请多包涵,有不对的地方请指出我这边再完善


yanbingyun1990
236 声望23 粉丝