微信发起支付步骤

/**
 * 发起充值,获取充值参数
 */
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;
        }
    }
}

支付回调

//微信支付回调
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:务必开通商户平台,并配置好商户号和支付秘钥

posted @ 2020-03-19 11:49  TBHacker  阅读(745)  评论(0编辑  收藏  举报