微信发起支付步骤

时间:2020-03-19 12:03:36   收藏:0   阅读:90
/**
 * 发起充值,获取充值参数
 */
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 Common\Service;

use Think\Exception;
use Vendor\Func\Http;

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;
        }
    }
}

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

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!