zoukankan      html  css  js  c++  java
  • 微信发起支付步骤

    /**
     * 发起充值,获取充值参数
     */
    public function pay() {
        // step1 验证参数
        if (!$config_id = $_POST['config_id']) {
            $this->json->E('缺少参数');
        }
    
        $recharge_config = M('recharge_config');
        $recharge_config_info = $recharge_config->where(['id'=>$config_id,'deleted'=>0,'is_show'=>1])->find();
        if (!$recharge_config_info) {
            $this->json->E('充值项不存在');
        }
    
        // step2 创建订单
        $order_num = Func::createOrderNum();
        $add_data = [
            'order_num' => $order_num,
            'amount' => $recharge_config_info['recharge'],
            'year' => $recharge_config_info['year'],
            'company_uid' => $this->uid,
            'status' => 1, // 未支付
            'create_time' => time(),
        ];
    
        $recharge_order = M('recharge_order');
        $add_order_flag = $recharge_order->add($add_data);
        if (!$add_order_flag) {
            $this->json->E('创建支付订单失败,请重试');
        }
    
        // step3 生成支付参数
        $company_user = M('company_user');
        $company_user_info = $company_user->where(['id'=>$this->uid])->find();
        $openid = $company_user_info['openid'];
        $products_name = '会员充值';
        $total_fee = $recharge_config_info['recharge'] * 100;
        $unifiedorder = WxPayService::unifiedOrder($openid,$order_num,$total_fee,$products_name,C('COMPANY_RECHARGE_NOTIFY_URL'));
    
        // step4 等待支付成功后,处理微信回调
        $data                = $unifiedorder;
        // 其他信息
    
        $this->json->setAttr('data', $data);
        $this->json->Send();
        $this->json->S();
    }
    

    支付基类

    <?php
    
    /**
     * User: Eden
     * Date: 2019/3/21
     * 共有内容
     */
    
    namespace CommonService;
    
    use ThinkException;
    use VendorFuncHttp;
    
    class WxPayService extends CommonService
    {
        protected static $SSL_CERT_PATH = './apiclient_cert.pem'; //证书路径
        protected static $SSL_KEY_PATH =  './apiclient_key.pem'; //证书路径
    
        public static function unifiedOrder($openid, $order_num, $total_fee, $products_name, $notify_url = '')
        {
            $trade_no = $order_num;
            $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
            $data = [
                'appid'             => C('APP_ID'),
                'mch_id'            => C('MCHID'),
                'nonce_str'         => self::createNonceStr(),
                'sign_type'         => 'MD5',
                'body'              => $products_name,  //商品名称组合
                'attach'            => C('APP_NAME') . '-附加信息',
                'out_trade_no'      => $trade_no,       //订单号
                'fee_type'          => 'CNY',
                'total_fee'         => $total_fee, // 单位分
                'spbill_create_ip'  => $_SERVER['REMOTE_ADDR'],
                'goods_tag'         => C('APP_NAME') . '-商品标记',
                'notify_url'        => $notify_url ?: C('NOTIFY_URL'),
                'trade_type'        => 'JSAPI',
                'openid'            => $openid
            ];
            setlog($data,[],'','pay.log');
    
            $sign = self::MakeSign($data);
            $data['sign'] = $sign;
            $xml = self::ToXml($data);
            $result = self::FromXml(Http::postXmlCurl($url, $xml));
            
            /**
             * array (
             * 'return_code' => 'FAIL',
             * 'return_msg' => '签名错误',
             * )
             */
    
             /**
              *array (
              *'return_code' => 'SUCCESS',
              *'return_msg' => 'OK',
              *'appid' => 'wx4f00a0a86b52c297',
              *'mch_id' => '1574476801',
              *'nonce_str' => '7w1tka0oQmzzUtl9',
              *'sign' => 'E6470B7A55841CC77E905BE3BDFF2B92',
              *'result_code' => 'SUCCESS',
              *'prepay_id' => 'wx19111924446300f7f5d9f7fb1295195400',
              *'trade_type' => 'JSAPI',
              *)
              */
    
            setlog($result,[],'','pay.log');
    
            // 加工数据
            $data = [
                'appId' => $result['appid'] ?: C('APP_ID'),
                'timeStamp' => time(),
                'nonceStr' => self::createNonceStr(),
                'package' => 'prepay_id=' . $result['prepay_id'],
                'signType' => 'MD5'
            ];
            $sign = self::MakeSign($data);
            $data['sign'] = $sign;
            return $data;
        }
    
        /**
         * 处理退款
         * @param $out_trade_no
         * @param $total_fee
         * @param $refund_fee
         * @param $from 1余额 2未结算
         * @return array
         * @throws Exception
         * 策略一:当天支付的钱,从未结算中退;非当天支付的钱,从余额中退(结算的钱到余额中有个缓冲期1-3天,结算到余额要收千分之一的手续费)。确保退款正常,需要在余额中留有备用金。
         * 策略二:优先从未结算中退,未结算中余额不足,再从余额中退。(需要查询两次,比较消耗网络。好处就是可以节省被腾讯收取的千分之一的费用。)
         */
        public static function refundOrder($out_trade_no, $total_fee, $refund_fee, $from = 1)
        {
            $refund_no = $out_trade_no . $total_fee;
            if ((int) $from === 1) {
                $refund_account = 'REFUND_SOURCE_RECHARGE_FUNDS';
            } else {
                $refund_account = 'REFUND_SOURCE_UNSETTLED_FUNDS';
            }
            // $refund_account = 'REFUND_SOURCE_UNSETTLED_FUNDS';
            $param = array(
                'appid'         => C('APP_ID'),
                'mch_id'        => C('MCHID'),
                'nonce_str'     => self::createNonceStr(),
                'out_refund_no' => $refund_no, //由后端生成的退款单号,需要保证唯一,因为多个同样的退款单号只会退款一次。
                'out_trade_no'  => $out_trade_no,                   //退款订单在支付时生成的订单号
                'total_fee'     => $total_fee,
                'refund_fee'    => $refund_fee,
                'refund_account' => $refund_account,  // REFUND_SOURCE_RECHARGE_FUNDS 从余额退,REFUND_SOURCE_UNSETTLED_FUNDS 从未结算退
                'op_user_id'    => C('MCHID'),          //操作员 op_user_id .与商户号相同即可
            );
    
            $param['sign'] = self::MakeSign($param);
            $xml_data = self::ToXml($param);
            $xml_result = self::postXmlSSLCurl($xml_data, 'https://api.mch.weixin.qq.com/secapi/pay/refund');
            $result = self::FromXml($xml_result);
            if (!$result) {
                $result_arr = [
                    'num'     =>      '0',
                    'desc'   =>      '接口错误',
                ];
                return $result_arr;
            }
    
            if ($result['result_code'] != 'SUCCESS') {
                $result_arr = [
                    'num'     =>      '-1',
                    'desc'    =>      $result['err_code_des'],
                    'err_code' =>     $result['err_code'], // NOTENOUGH 余额不足
                ];
            } else {
                $result_arr = [
                    'num'     =>      '1',
                    'desc'    =>      '退款成功',
                    'refund_id'    =>      $result['refund_id'],
                    'refund_no'    =>      $refund_no,
                ];
            }
    
            return $result_arr;
        }
    
        /**
         * xml2array
         * @param $xml
         * @return mixed
         * @throws Exception
         */
        public static function FromXml($xml)
        {
            if (!$xml) {
                throw new Exception("xml数据异常!");
            }
            //将XML转为array
            //禁止引用外部xml实体
            libxml_disable_entity_loader(true);
            $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
            return $values;
        }
    
        /**
         * array2xml
         * @param $array
         * @return string|void
         */
        public static function ToXml($array)
        {
            if (!is_array($array) || count($array) <= 0) {
                return;
            }
            $xml = '<xml version="1.0">';
            foreach ($array as $key => $val) {
                if (is_numeric($val)) {
                    $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
                } else {
                    $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
                }
            }
            $xml .= "</xml>";
            return $xml;
        }
    
        /**
         * 创建随机字符串
         * @param int $length
         * @return string
         */
        public static function createNonceStr($length = 16) {
            $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
            $str = '';
            for ( $i = 0; $i < $length; $i++ )  {
                $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
            }
            return $str;
        }
    
        /**
         * 签名
         * @param $data
         * @return string
         */
        public static function MakeSign($data)
        {
            //签名步骤一:按字典序排序参数
            ksort($data);
            $string = self::ToUrlParams($data);
            //签名步骤二:在string后加入KEY
            $string = $string . "&key=".C('WEIXIN_PAY_KEY');
            //签名步骤三:MD5加密
            $string = md5($string);
            //签名步骤四:所有字符转为大写
            $result = strtoupper($string);
            return $result;
        }
    
    
        
        /**
         * url
         * @param $array
         * @return string
         */
        public static function ToUrlParams($array)
        {
            $buff = '';
            foreach ($array as $k => $v)
            {
                if($k != 'sign' && $v != '' && !is_array($v)){
                    $buff .= $k . '=' . $v . '&';
                }
            }
            $buff = trim($buff, '&');
            return $buff;
        }
    
    
        /**
         * 需要使用证书的请求
         * @param $xml
         * @param $url
         * @param int $second
         * @return bool|string
         */
        public static 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');
            curl_setopt($ch, CURLOPT_SSLCERT, self::$SSL_CERT_PATH);
            //默认格式为PEM,可以注释
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLKEY, self::$SSL_KEY_PATH);
            //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);
                echo "curl出错,错误码:$error" . "<br>";
                curl_close($ch);
                return false;
            }
        }
    }
    
    

    支付回调

    //微信支付回调
    public function order_notice()
    {
        $xml = $GLOBALS['HTTP_RAW_POST_DATA'];
        $data = WxPayService::FromXml($xml);
        $data_sign = $data['sign'];
        unset($data['sign']);
        $sign = WxPayService::MakeSign($data);
        if (($sign === $data_sign) && ($data['return_code'] == 'SUCCESS') && ($data['result_code'] == 'SUCCESS')) {
            $order_num  = $data['out_trade_no'];         //订单单号
            $openid     = $data['openid'];                  //付款人openID
            $total_fee  = $data['total_fee'];            //付款金额
            $transaction_id = $data['transaction_id'];  //微信支付流水号
            $result = $this->order_notice_datadeal($order_num, $openid, $total_fee, $transaction_id);
        } 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>';
        }
        echo $str;
        return $result;
    }
    
    // 支付成功后回调数据处理;
    // @param $order_num 订单单号
    // @param $openid   付款人openID
    // @param $total_fee ,实际支付的付款金额,单位分
    // @param string $transaction_id ,微信支付流水号
    private function order_notice_datadeal($order_num, $openid, $total_fee, $transaction_id = '')
    {
        $recharge_order = M('recharge_order');
        $order_info = $recharge_order->where(['order_num' => $order_num])->find();
        if ((int) $order_info['status'] === 2) {
            return true;
        }
    
        // 订单状态处理
        $save_data = [
            'total_payed_price' =>  MathUtil::div($total_fee, 100),
            'transaction_id'    =>  $transaction_id,
            'pay_time'          =>  time(),
            'status'            =>  2       //1.未支付;2.已支付;
        ];
    
        M()->startTrans();
        $order_save_flag = $recharge_order->where(array('order_num' => $order_num))->save($save_data);
        if ($order_save_flag === false) {
            M()->rollback();
            return false;
        }
    
    
        $company_user           = M('company_user');
        $user_info     = $company_user->where(array('id' => $order_info['company_uid']))->find();
        if ((int) $user_info['vip_end_time'] < time()) { // 已过期,新开会员
            $save_data = [
                'is_vip' => 1,
                'vip_end_time' => time() + (int)$order_info['year'] * 31536000,
            ];
        } else { // 续费
            $save_data = [
                'is_vip' => 1,
                'vip_end_time' => (int) $user_info['vip_end_time'] + (int)$order_info['year'] * 31536000,
            ];
        }
    
        // 处理vip
        $user_save_flag = $company_user->where(array('id' => $order_info['company_uid']))->save($save_data);
        if ($user_save_flag === false) {
            M()->rollback();
            return false;
        }
    
        M()->commit();
        return true;
    }
    

    tips:务必开通商户平台,并配置好商户号和支付秘钥

  • 相关阅读:
    什么是“泛在电力物联网”?要建一个什么样的泛在电力物联网?
    基于混合云雾计算的物联网架构
    探索 | “中医+AI”会诊电力设备故障
    泛在电力物联网有项核心技术 你听过没有?
    国网做泛在电力物联网的初衷是什么?如何参与?
    泛在电力物联网技术及战略解读:一个战略 两个领域 三个阶段
    构建“泛在电力物联网”成为国网当前最紧迫、最重要的任务
    如何解决分布式日志exceptionless的写入瓶颈
    SQL 查找是否"存在",别再 count 了,很耗费时间的
    abp vnext 微服务-基于Exceptionless实现分布式日志记录
  • 原文地址:https://www.cnblogs.com/jiqing9006/p/12523164.html
Copyright © 2011-2022 走看看