美文网首页PHP
微信支付 php

微信支付 php

作者: 7c3b293d3b97 | 来源:发表于2019-10-11 16:17 被阅读0次
<?php

namespace service\weChat;


class WeChatPaySvc
{

    //微信统一下单接口
    private $unifiedOrder = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
    //订单查询
    private $orderQuery = 'https://api.mch.weixin.qq.com/pay/orderquery';
    //退款
    private $refund = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
    const METHOD_PUT  = 'PUT';
    const METHOD_POST = 'POST';
    const METHOD_GET  = 'GET';

    const secretKey = '';//微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置


    //微信统一下单参数
    private $weChatUnifiedOrderParams = [
        'appid'             => '',      //微信开放平台审核通过的应用APPID(请登录open.weixin.qq.com查看,注意与公众号的APPID不同)
        'mch_id'            => '',      //微信支付分配的商户号
        'device_info'       => 'WEB',   //终端设备号(门店号或收银设备ID),默认请传"WEB"
        'nonce_str'         => '',      //随机字符串,不长于32位。 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
        'sign_type'         => '',      //签名类型,目前支持HMAC-SHA256和MD5,默认为MD5
        'body'              => '',      //商品描述交易字段格式根据不同的应用场景按照以下格式:
        'detail'            => '',      //APP——需传入应用市场上的APP名字-实际商品名称,天天爱消除-游戏充值。
        'attach'            => '',      //商品详细描述,对于使用单品优惠的商户,该字段必须按照规范上传,详见  https://pay.weixin.qq.com/wiki/doc/api/danpin.php?chapter=9_102&index=2
        'out_trade_no'      => '',   //附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
        'fee_type'          => 'CNY',      //商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。详见商户订单号  https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_2
        'total_fee'         => '',       //订单总金额,单位为分,详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_2
        'spbill_create_ip'  => '',      //支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
        'time_start'        => '',      //订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则  //https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_2
        'time_expire'       => '',      //订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 建议:最短失效时间间隔大于1分钟  https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_2
        'goods_tag'         => '',   //订单优惠标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠 https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_1
        'notify_url'        => '',   //接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
        'trade_type'        => 'APP',   //支付类型
        'limit_type'        => '',      //no_credit--指定不能使用信用卡支付
        'receipt'           => 'N',     //Y,传入Y时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效
    ];

    //微信查询订单接口
    private $weChatOrderQueryParams = [
        'appid'             => '',      //微信开放平台审核通过的应用APPID(请登录open.weixin.qq.com查看,注意与公众号的APPID不同)
        'mch_id'            => '',      //微信支付分配的商户号
        'nonce_str'         => '',      //随机字符串,不长于32位。 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
        'transaction_id'    => '',
        'out_trade_no'    => '',
    ];

    //app调起微信支付参数
    private $appPaymentParams = [
        'appid'             => '',      //微信开放平台审核通过的应用APPID(请登录open.weixin.qq.com查看,注意与公众号的APPID不同)
        'partnerid'         => '',      //微信支付分配的商户号
        'prepayid'          => '',      //微信返回的支付交易会话ID
        'package'           => 'Sign=WXPay',
        'noncestr'          => '',
        'timestamp'         => '',
    ];

    //微信退款
    //注意refund_account参数
         //策略一:当天支付的钱,从未结算中退;非当天支付的钱,从余额中退(结算的钱到余额中有个缓冲期1-3天,结算到余额要收千分之一的手续费)。确保退款正常,需要在余额中留有备用金。
         //策略二:优先从未结算中退,未结算中余额不足,再从余额中退。(需要查询两次,比较消耗网络。好处就是可以节省被腾讯收取的千分之一的费用。)

    private $weChatRefundParams = [
        'appid'             => '',      //微信开放平台审核通过的应用APPID(请登录open.weixin.qq.com查看,注意与公众号的APPID不同)
        'mch_id'            => '',      //微信支付分配的商户号
        'nonce_str'         => '',      //随机字符串,不长于32位。推荐随机数生成算法
        'sign'              => '',
        'sign_type'         => 'MD5',//签名类型,目前支持HMAC-SHA256和MD5,默认为MD5
        'transaction_id'    => '',//微信生成的订单号,在支付通知中有返回  transaction_id和out_trade_no二选一
        'out_trade_no'      => '',//商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。  transaction_id和out_trade_no二选一
        'out_refund_no'     => '',//商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
        'total_fee'         => '',//订单总金额,单位为分,只能为整数,详见支付金额
        'refund_fee'        => '',//退款总金额,订单总金额,单位为分,只能为整数,详见支付金额
        'refund_fee_type'   => 'CNY',//退款货币类型,需与支付一致,或者不填。符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
        'refund_desc'       => '',//若商户传入,会在下发给用户的退款消息中体现退款原因  注意:若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因
        'refund_account'    => '',//仅针对老资金流商户使用 REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款) REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款    
        'notify_url'        => '',//异步接收微信支付退款结果通知的回调地址,通知URL必须为外网可访问的url,不允许带参数  如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效。
    ];
    /*
     * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
     * 微信统一下单
     * */
    public function weChatUnifiedOrder($orderNumber, $totalFee, $goodsName, $notifyUrl)
    {

        $conf = \Helper::getConfigPhp('system');
        if(isset($conf['wxApp']['appid']) && !empty($conf['wxApp']['appid']))
        {
            $this->weChatUnifiedOrderParams['appid'] = $conf['wxApp']['appid'];
        }
        if(isset($conf['wxApp']['mch_id']) && !empty($conf['wxApp']['mch_id']))
        {
            $this->weChatUnifiedOrderParams['mch_id'] = $conf['wxApp']['mch_id'];
        }
        $this->weChatUnifiedOrderParams['appid'] = '';
        $this->weChatUnifiedOrderParams['time_start'] = date('YmdHis');
        $this->weChatUnifiedOrderParams['notify_url'] = $notifyUrl;
        $this->weChatUnifiedOrderParams['total_fee'] = $totalFee;
        $this->weChatUnifiedOrderParams['body'] = $goodsName;
        $this->weChatUnifiedOrderParams['out_trade_no'] = $orderNumber;
        $this->weChatUnifiedOrderParams['nonce_str'] = md5(uniqid().max(1000,9999));

        $sign = self::sign($this->weChatUnifiedOrderParams);
        $this->weChatUnifiedOrderParams['sign'] = $sign;
        $xml = self::toXml($this->weChatUnifiedOrderParams);

        $res = self::send($this->unifiedOrder, self::METHOD_POST, $xml);

        if(empty($res) || empty($res['response'])) return false;

        $res_xml = simplexml_load_string($res['response'], null, LIBXML_NOCDATA);
        if(empty($res_xml)) return false;

        $result = @json_decode(json_encode($res_xml), true);
        return $result;
    }


    /*
     * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_2
    * 微信查询订单
    * */
    public function weChatOrderQuery($orderNumber, $transactionId='')
    {
        $conf = \Helper::getConfigPhp('system');
        if(isset($conf['wxApp']['appid']) && !empty($conf['wxApp']['appid']))
        {
            $this->weChatOrderQueryParams['appid'] = $conf['wxApp']['appid'];
        }
        if(isset($conf['wxApp']['mch_id']) && !empty($conf['wxApp']['mch_id']))
        {
            $this->weChatOrderQueryParams['mch_id'] = $conf['wxApp']['mch_id'];
        }
        $this->weChatOrderQueryParams['appid'] = '';
        $this->weChatOrderQueryParams['transaction_id'] = $transactionId;
        $this->weChatOrderQueryParams['out_trade_no'] = $orderNumber;
        $this->weChatOrderQueryParams['nonce_str'] = md5(uniqid().max(1000,9999));

        $sign = self::sign($this->weChatOrderQueryParams);
        $this->weChatOrderQueryParams['sign'] = $sign;
        $xml = self::toXml($this->weChatOrderQueryParams);

        $res = self::send($this->orderQuery, self::METHOD_POST, $xml);

        if(empty($res) || empty($res['response'])) return false;

        $res_xml = simplexml_load_string($res['response'], null, LIBXML_NOCDATA);

        if(empty($res_xml)) return false;

        $result = @json_decode(json_encode($res_xml), true);

        return $result;
    }

    /*
     * 生成app调起微信支付的参数
     * */
    public function appPaymentParams($prepayid)
    {
        $conf = \Helper::getConfigPhp('system');
        if(isset($conf['wxApp']['appid']) && !empty($conf['wxApp']['appid']))
        {
            $this->appPaymentParams['appid'] = $conf['wxApp']['appid'];
        }
        if(isset($conf['wxApp']['mch_id']) && !empty($conf['wxApp']['mch_id']))
        {
            $this->appPaymentParams['partnerid'] = $conf['wxApp']['mch_id'];
        }

        $this->appPaymentParams['appid'] = '';
        $this->appPaymentParams['prepayid'] = $prepayid;
        $this->appPaymentParams['noncestr'] = md5(uniqid().max(1000,9999));
        $this->appPaymentParams['timestamp'] = time();
        $sign = self::sign($this->appPaymentParams);
        $this->appPaymentParams['sign'] = $sign;
        return $this->appPaymentParams;
    }


    /*
     * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_4&index=6
     * 退款
     * $outTradeNo 商户订单号
     * $transactionId 微信订单号
     * $outRefundNo  商户退款单号
     * $totalFee  订单金额
     * $refundFee  退款金额
     * $refundDesc  退款原因   若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因
     *
     * */
    public function weChatRefund($transactionId='', $outTradeNo, $outRefundNo, $totalFee, $refundFee, $refundDesc='', $notifyUrl='')
    {
        $conf = \Helper::getConfigPhp('system');
        if(isset($conf['wxApp']['appid']) && !empty($conf['wxApp']['appid']))
        {
            $this->weChatRefundParams['appid'] = $conf['wxApp']['appid'];
        }
        if(isset($conf['wxApp']['mch_id']) && !empty($conf['wxApp']['mch_id']))
        {
            $this->weChatRefundParams['mch_id'] = $conf['wxApp']['mch_id'];
        }
        $this->weChatRefundParams['appid'] = '';
        $this->weChatRefundParams['transaction_id'] = $transactionId;
        $this->weChatRefundParams['out_trade_no'] = $outTradeNo;
        $this->weChatRefundParams['nonce_str'] = md5(uniqid().max(1000,9999));
        $this->weChatRefundParams['out_refund_no'] = $outRefundNo;
        $this->weChatRefundParams['total_fee'] = $totalFee;
        $this->weChatRefundParams['refund_fee'] = $refundFee;
        $this->weChatRefundParams['refund_desc'] = $refundDesc;
        $this->weChatRefundParams['notify_url'] = $notifyUrl;

        $sign = self::sign($this->weChatRefundParams);
        $this->weChatRefundParams['sign'] = $sign;
        $xml = self::toXml($this->weChatRefundParams);

        $res = self::send($this->refund, self::METHOD_POST, $xml);

        if(empty($res) || empty($res['response'])) return false;

        $res_xml = simplexml_load_string($res['response'], null, LIBXML_NOCDATA);

        if(empty($res_xml)) return false;

        $result = @json_decode(json_encode($res_xml), true);

        return $result;
    }


    static function sign($params)
    {
        ksort($params);
        $string = '';
        foreach($params as $key=>$value){
            if(empty($value))
            {
                continue;
            }
            $string .= "$key=$value&";
        }
        $string = $string . "key=".self::secretKey;
        $string = md5($string);
        return strtoupper($string);
    }

    static function verify($params)
    {
        $sign = $params['sign'];
        unset($params['sign']);
        if(self::sign($params) != $sign)
        {
            return false;
        }
        return true;
    }

    static function toXml($data)
    {
        $xml = '<xml>';

        foreach($data as $key=>$val){
            $xml .= "<$key>$val</$key>";
        }
        $xml .= '</xml>';
        return $xml;
    }

    /**
     * zll 将信息提交到微信服务器,发起企业付款
     */
    static public function send($url, $method='POST', $data=null,$admin = 0, $headers=[], $timeout=5){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

        switch($method){
            case self::METHOD_POST:
                curl_setopt($ch, CURLOPT_POST, 1);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
                break;
            case self::METHOD_PUT:
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
                break;
        }
        $header[] = 'X-HTTP-Method-Override: '.$method;
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);

        curl_setopt($ch, CURLOPT_HEADER, 1);
        curl_setopt($ch, CURLINFO_HEADER_OUT, true);

        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, APP_PATH.'/conf/cert/apiclient_cert.pem');
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, APP_PATH.'/conf/cert/apiclient_key.pem');

        $response = curl_exec($ch);
        $headers  = curl_getinfo($ch);
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $res_header = substr($response,0, $headerSize);
        $res_body   = substr($response, $headerSize);

        $headers['res_header'] = $res_header;
        curl_close($ch);
        return ['response'=>$res_body, 'headers'=>$headers];
    }

}

支付回调

<?php

use service\weChat\WeChatPaySvc;
use service\shop\ShopOrderSvc;
use shop\ShopOrderModel;

class WechatController extends BaseController
{
    //初始化函数
    public function init()
    {
        parent::init();
    }

    /*
     * 商城支付微信回调
     * */
    public function wechatNotifyShopAction()
    {
        $xml = file_get_contents("php://input");

        \MyLog::logAntiCheating('wechatNotifyShopAction:xml'.base64_encode($xml));

        $jsonXml = json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA));
        $data = json_decode($jsonXml, true);
        \MyLog::logAntiCheating('wechatNotifyShopAction:data'.json_encode($data));
        $returnData = [
            'return_code' => 'SUCCESS',
            'return_msg' => 'OK'
        ];


        if($data['result_code'] != 'SUCCESS' || $data['return_code'] != 'SUCCESS')
        {
            //todo 微信错误记录
        }

        //验签
        if(!WeChatPaySvc::verify($data))
        {
            $returnData['return_code'] = 'FAIL';
            $returnData['return_msg'] = '签名验证失败';
            $xml = WeChatPaySvc::toXml($returnData);
            exit($xml);
        }


        //微信支付订单号
        $transactionId = $data['transaction_id'];
        $orderNumber = $data['out_trade_no'];

        //查询订单是否已经支付
        $shopOrderSvc = new ShopOrderSvc();
        $orderDetail = $shopOrderSvc->orderDetail($orderNumber);
        if($orderDetail->payStatus == \shop\ShopOrderModel::PAY_STATUS1)
        {
            $returnData['return_msg'] = '订单已支付';
            $xml = WeChatPaySvc::toXml($returnData);
            exit($xml);
        }

        //检查订单金额
        if($orderDetail->payAmount*100 != $data['total_fee'])
        {
            $returnData['return_code'] = 'FAIL';
            $returnData['return_msg'] = '订单金额错误';
            $xml = WeChatPaySvc::toXml($returnData);
            exit($xml);
        }


        //同步订单状态
        $shopOrderModel = new ShopOrderModel();
        $query = [
            '_id' => $orderDetail->_id,
        ];
        $update = [
            '$set' => [
                'payTime' => time(),
                'payStatus' => ShopOrderModel::PAY_STATUS1,
                'transactionId' => $data['transaction_id'],
            ]
        ];
        $shopOrderModel->update($query, $update);
        $xml = WeChatPaySvc::toXml($returnData);
        exit($xml);
    }

    /*
     * 退款
     * */
    public function wechatNotifyRefundAction()
    {
        $xml = file_get_contents("php://input");

        \MyLog::logAntiCheating('wechatNotifyRefundAction:xml'.base64_encode($xml));

        $jsonXml = json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA));
        $data = json_decode($jsonXml, true);

        \MyLog::logAntiCheating('wechatNotifyRefundAction:data'.json_encode($data));
        $returnData = [
            'return_code' => 'SUCCESS',
            'return_msg' => 'OK'
        ];

        //解密req_info
        $reqInfo = $data['req_info'];
        $decrypt = base64_decode($reqInfo, true);
        $xml = openssl_decrypt($decrypt, 'aes-256-ecb', md5(WeChatPaySvc::secretKey), OPENSSL_RAW_DATA);
        $jsonXml = json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA));
        $data = json_decode($jsonXml, true);
        //记录退款结果
        $shopRefundModel = new \shop\ShopRefundModel();
        $query = [
            'refundNumber' => $data['out_refund_no'],
            'orderNumber' => $data['out_trade_no'],
        ];
        $param = [
            '$set' =>[
                'wxRefundReqInfo' => $data,
            ]
        ];
        $shopRefundModel->update($query, $param);
        //同步订单状态
        $shopOrderModel = new ShopOrderModel();
        $shopOrderModel->update(['orderNumber'=>$data['out_trade_no']], ['$set'=>['payStatus'=>ShopOrderModel::PAY_STATUS3]]);

        $xml = WeChatPaySvc::toXml($returnData);
        exit($xml);
    }

}

相关文章

网友评论

    本文标题:微信支付 php

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