PHP7 微信预支付+红包+企业转账到零钱+退款+退款通知内容解密

每次写微信支付新的内容都要找一些文档,有点烦了,就干脆发一波代码好了,懒得用微信的sdk,就是这么懒,看都没看过

注意,代码使用laravel框架写的,不影响使用,加入你的namespace,替换成你的Exception,配置好你的微信支付参数,相关的信息改写成你需要的,基本就能直接上手使用了

class  WeChatPayService

function

1 qrCodePay 生成支付二维码

2 transferIntoWallet 向个人微信钱包转账(企业转账到钱包)

3 sendBasicRedPack 公众号发送微信红包

4 generateSign 参数签名

5 convertArrayToXml 数组参数转为xml参数

6 preOrder 微信预支付(统一下单)

7 refund 订单退款

8 xmlStructureToArray 微信返回的xml结构化为key->value的数组

9 decodeRefundNotifyInfo 解析微信通知的加密内容,返回数组(解密AES的应该用openssl方法)


仅供参考,请勿用于业务开发,出了问题我不负责的昂==



class WeChatPayService
{
    private static $instance;
    private $conf;
    //统一下单API
    const PreOrder = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
    //普通红包
    const BasicRedPack = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack';
    //企业转账到零钱
    const Transfer = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
    //订单退款
    const Refund = 'https://api.mch.weixin.qq.com/secapi/pay/refund';

    private function __construct(){
        $this->conf = Config::get('eqy.WeChatPay');
    }

    private function __clone(){}

    public static function getInstance()
    {
        if(!(self::$instance instanceof  self))
            self::$instance = new self();

        return self::$instance;
    }



    /**生成二维码支付 二维码30分钟有效
     * @param $price int 订单价格
     * @param $order_no string 商家订单编号
     * @return array  [prepay_id,qr_code_url]
     * @throws SystemInternalError
     */
    public function qrCodePay($price,$order_no){
        $result = $this->preOrder($price,$order_no,'NATIVE');
        $xml_paser = xml_parser_create();
        xml_parse_into_struct($xml_paser,$result,$re);
        $prepay_id = $re[17]['value'];
        $url = $re[21]['value'];
        return [$prepay_id,$url];
    }


    /**企业直接转账到微信零钱
     * @param $order_no
     * @param int $amount
     * @param string $dest_open_id
     * @param bool $check_name
     * @param null $name
     * @param string $desc
     * @throws SystemInternalError
     */
    public function transferIntoWallet($order_no,$amount=500,$dest_open_id='obuxnxAIYyzLDwZ8_7yJo4kXRO-I',$check_name=false,$name=null,$desc='e企印首单返现')
    {
        $data = [
            'mch_appid'=>$this->conf['AppId'],
            'mchid'=>$this->conf['MchId'],
            'nonce_str'=>str_random(24),
            'partner_trade_no'=>$order_no,
            'openid'=>$dest_open_id,
            'amount'=>$amount,
            'desc'=>$desc,
            'spbill_create_ip'=>$this->conf['ServerIp']
        ];

        if($check_name)
        {
            $data['check_name'] = 'FORCE_CHECK';
            $data['re_user_name'] = $name;
        }else
            $data['check_name'] = 'NO_CHECK';

        $this->generateSign($data);
        $data = $this->convertArrayToXml($data);

        $re = CurlService::getInstance()->post(self::Transfer,$data,null,false,
            $this->conf['CertPath'],$this->conf['PKeyPath']);

        if($re)
        {

            $arr = $this->xmlStructureToArray($re);
            if($arr['return_code']=='SUCCESS') {
                if ($arr['result_code'] != 'SUCCESS')
                    throw new SystemInternalError('basic red pack send failed! open_id=' . $dest_open_id);
            }
            else
                throw new SystemInternalError("basic red pack api failed!" . json_encode($arr));



        }

    }

    /**公众号发送微信红包
     * @param $order_no
     * @param $dest_open_id
     * @param int $red_pack_amount
     * @param string $name
     * @param string $wishing
     * @param string $act_name
     * @param string $remark
     * @return mixed
     * @throws \Exception
     */
    public function sendBasicRedPack($order_no,$dest_open_id='obuxnxAIYyzLDwZ8_7yJo4kXRO-I',
 $red_pack_amount=300,$name='e企印',$wishing='图文服务,使命必达!',$act_name='e企印首单返现',$remark='首单返现')
    {

        $data = [
            'mch_billno'=>$order_no,
            'nonce_str'=>str_random(24),
            'mch_id'=>$this->conf['MchId'],
            'wxappid'=>$this->conf['AppId'],
            'send_name'=>$name,
            're_openid'=>$dest_open_id,
            'total_amount'=>$red_pack_amount,
            'total_num'=>1,
            'wishing'=>$wishing,
            'client_ip'=>$this->conf['ServerIp'],
            'act_name'=>$act_name,
            'remark'=>$remark
        ];
        $this->generateSign($data);
        $data = $this->convertArrayToXml($data);

        $re = CurlService::getInstance()->post(self::BasicRedPack,$data,null,false,
            $this->conf['CertPath'],$this->conf['PKeyPath']);


        if($re) {
            //
            //
            //
            //
            //
            //
            //
            //
//            $xml_parser = xml_parser_create();
//            xml_parse_into_struct($xml_parser, $re, $arr);


            $arr = $this->xmlStructureToArray($re);
            if($arr['return_code']=='SUCCESS') {
                if ($arr['result_code'] != 'SUCCESS')
                    throw new SystemInternalError('basic red pack send failed! open_id=' . $dest_open_id);
            }else
                throw new SystemInternalError("basic red pack api failed!" . json_encode($arr));


        }


    }

    /**参数签名
     * @param $data
     */
    private function generateSign(&$data)
    {
        ksort($data);//参数按照ASCII有小到大排序
        $stringA = '';
        //生成stringA
        $length = count($data);
        $i = 0;
        foreach($data as $k=>$v)
        {
            $i++;
            $stringA.=$k.'='.$v;
            if($i != $length)
            {
                $stringA.='&';
            }
        }

        $stringSignTemp = $stringA.'&key='.$this->conf['Key'];
        $data['sign'] = strtoupper(md5($stringSignTemp));
    }

    /**将array转变为xml
     * @param $data
     * @return string
     */
    private function convertArrayToXml($data)
    {
        if(empty($data)) return 'error';
        $xml = ''."\n";
        foreach($data as $k=>$v)
        {
            $xml.='<'.$k.'>'.$v.'.$k.'>'."\n";
        }
        $xml.='';
        return $xml;
    }


    /**生成微信的预支付订单
     * @param $price int 订单金额,单位分
     * @param $order_no  string 商家自定义的订单编号
     * @param string $trade_type string JSAPI 公众号支付 NATIVE 扫码支付 APP APP支付
     * @return mixed
     * @throws SystemInternalError
     */
    private function preOrder($price,$order_no,$trade_type='NATIVE')
    {
        $time = time();
        $data = [
            'appid'=>$this->conf['AppId'],
            'mch_id'=>$this->conf['MchId'],
            'nonce_str'=>str_random(18),
            'detail'=>'e企印用户支付('.($price/100).'元)',
            'attach'=>'杭州牛印科技有限公司',
            'device_info'=>'WEB',
            'body'=>'e企印',
            'total_fee'=>$price,
            'fee_type'=>'CNY',
            'time_start'=>date('YmdHis',$time),
            'time_expire'=>date('YmdHis',$time+600),
            'trade_type'=>$trade_type,
            'out_trade_no'=>$order_no,
            'notify_url'=>HOST.$this->conf['NotifyUrl']
        ];
        $this->generateSign($data);
        $data = $this->convertArrayToXml($data);

        $result = CurlService::getInstance()->post(self::PreOrder,$data);

        if(empty($result)) throw new SystemInternalError('wechat preorder failed!');

        return $result;

    }


    /**微信订单退款
     * @param $order_no
     * @param $refund_fee
     * @param $total_fee
     * @return array
     * @throws SystemInternalError
     * https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_4
     */
    public function refund($order_no,$refund_fee,$total_fee)
    {
        $data = [
            'appid'=>$this->conf['AppId'],
            'mch_id'=>$this->conf['MchId'],
            'nonce_str'=>str_random(18),
            'sign_type'=>'MD5',
            'out_trade_no'=>$order_no,
            'out_refund_no'=>str_random(18),
            'total_fee'=>$total_fee,
            'refund_fee'=>$refund_fee,
            'refund_fee_type'=>'CNY',
            'refund_desc'=>'订单退款,标号:'.$order_no,
            'notify_url'=>HOST.$this->conf['RefundNotifyUrl']
        ];

        $this->generateSign($data);
        $data = $this->convertArrayToXml($data);

        $re = CurlService::getInstance()->post(self::Refund,$data,null,false,$this->conf['CertPath'],$this->conf['PKeyPath']);

        if(empty($re)) throw new SystemInternalError('wechat refund failed!');

        $arr = $this->xmlStructureToArray($re);
        if($arr['return_code']=='SUCCESS' || $arr['result_code'] == 'SUCCESS')
            return [true,'success'];
        else {
            return [false,$arr['err_code']];
        }

    }


    /**针对微信返回的xml参数,结构化为操作便利的数组
     * @param $xml
     * @return array
     */
    public function xmlStructureToArray($xml)
    {
        $parser = xml_parser_create();
        xml_parse_into_struct($parser,$xml,$arr);

        $data = [];
        foreach ($arr as $item)
        {
            if($item['tag']!='XML')
                $data[strtolower($item['tag'])] = $item['value'];
        }

        unset($arr,$xml,$parser);
        return $data;
    }


    /**解析微信退款内容  AES-256-ECB  PKCS7Padding
     * @param $info
     * @return array
     * @throws SystemInternalError
     */
    public function decodeRefundNotifyInfo($info)
    {
        $data = base64_decode($info);
        $key = strtolower(md5($this->conf['Key']));
        $re = openssl_decrypt($data,'AES-256-ECB',$key,OPENSSL_NO_PADDING);
        if($re == false)
            throw new SystemInternalError('info decryption failed!');

        $len = strlen($re);
        $pad = ord($re[$len - 1]);
        if ($pad < 1 || $pad > 32)
            $pad = 0;
        $xml = substr($re, 0, $len - $pad);

        return $this->xmlStructureToArray($xml);
    }




}



附CurlService(网络请求)


class CurlService{

    private static $instance;

    private function __construct(){}

    private function __clone(){}

    public static function getInstance()
    {
        if(!(self::$instance instanceof  self))
            self::$instance = new self();
        return self::$instance;
    }


    /** post方法
     * @param $url string 目标api地址
     * @param $data  array|json string 参数
     * @param null $header array http请求头
     * @param bool $is_debug bool 调试模式
     * @param null $cert_path string 证书路径
     * @param null $pkey_path string 私钥路径
     * @param null $ca_path string 根证书路径
     * @return mixed
     * @throws SystemInternalError
     */
    public function post($url,$data,$header=null,$is_debug=false,$cert_path=null,$pkey_path=null,$ca_path=null)
    {
        $ch = curl_init();
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
        if($header)
            curl_setopt($ch,CURLOPT_HTTPHEADER,$header);
        curl_setopt($ch,CURLOPT_POST,true);
        curl_setopt($ch,CURLOPT_POSTFIELDS,$data);
        curl_setopt($ch,CURLOPT_URL,$url);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
        if($cert_path && $pkey_path)
        {
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT,$cert_path);
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY,$pkey_path);
        }
        if($ca_path) {
            curl_setopt($ch, CURLOPT_CAINFO, 'PEM');
            curl_setopt($ch, CURLOPT_CAPATH, $ca_path);
        }

        $re = curl_exec($ch);
        if($is_debug)
            return $re;

        $code = curl_getinfo($ch,CURLINFO_HTTP_CODE);
        if($code != 200)
            throw new SystemInternalError('curl function failed! http code:'.$code);

        return $re;
    }


    /** get方法
     * @param $url string 目标api地址
     * @param null $data array 数组参数
     * @param null $header array 请求头
     * @param bool $is_debug bool  调试模式
     * @return mixed
     * @throws SystemInternalError
     */
    public function get($url,$data=null,$header=null,$is_debug=false)
    {

        if(is_array($data))
        {
            $url .= '?';
            $size = count($data);

            $index = 1;
            foreach ($data as $k=>$v)
            {
                $url .= $k.'='.$v;
                if($index != $size)
                    $url .= '&';
                $index ++;
            }
        }

        $ch = curl_init();
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
        if($header)
            curl_setopt($ch,CURLOPT_HTTPHEADER,$header);
        curl_setopt($ch,CURLOPT_URL,$url);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
        $re = curl_exec($ch);
        if($is_debug)
            return $re;

        $code = curl_getinfo($ch,CURLINFO_HTTP_CODE);
        if($code != 200)
            throw new SystemInternalError('curl function failed! http code:'.$code);

        return $re;
    }


配置文件截图如下(laravel框架下)

PHP7 微信预支付+红包+企业转账到零钱+退款+退款通知内容解密_第1张图片


你可能感兴趣的:(程序员)