微信/微信小程序统一下单(PHP版)

  工作中经常用到微信支付或小程序支付,特此整理一下基础代码,方便以后重复造轮子的时候用得上……本文代码不可直接放入生产环境使用,需要根须自身产品需求进行代码封装。

事先准备工作

1.开通公众号/小程序支付
2.商户号后台设置支付目录、回调目录、白名单等设置。
3.记录商户号ID(mchid)和生成支付key

需要准备辅助函数

    //-----------------支付相关-----------------

    //随机32位字符串
    public function nonce_str(){
        $result = '';
        $str = 'QWERTYUIOPASDFGHJKLZXVBNMqwertyuioplkjhgfdsamnbvcxz';
        for ($i=0;$i<32;$i++){
            $result .= $str[rand(0,48)];
        }
        return $result;
    }

    //生成订单号
    public function order_number(){
        return date('Ymd',time()).time().rand(10,99);//18位
    }

    //签名 $data要先排好顺序
    public function sign($data, $mch_key){
        $stringA = '';
        ksort($data);
        foreach ($data as $key=>$value){
            if(!$value) continue;
            if($stringA) $stringA .= '&'.$key."=".$value;
            else $stringA = $key."=".$value;
        }
        $wx_key = $mch_key;
        $stringSignTemp = $stringA.'&key='.$wx_key;//申请支付后有给予一个商户账号和密码,登陆后自己设置key 
        return strtoupper(md5($stringSignTemp));
    }

    /**
     * [http_request CURL请求函数版本1 发送数组或字符串]
     * @param  [type] $url     [description]
     * @param  [type] $data    [description]
     * @param  array  $headers [description]
     * @return [type]          [description]
     */
    public function http_request($url,$data = null,$headers=array()){
        $curl = curl_init();
        if( count($headers) >= 1 ){
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        }
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        if (!empty($data)){
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($curl);
        curl_close($curl);
        return $output;
    }

    /**
     * [curl_post CURL请求函数 版本2 发送json]
     * @param  [String] $url       [请求URL]
     * @param  [Array]  $post_data [请求数组]
     * @return [Array]             [返回请求结果数组]
     */
    public function curl_post($url, $post_data = ''){
        $requestUrl = $url;
        if ($post_data == '') {
            $data_string = '{}';
        }else{
            $data = $post_data;
            $data_string = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        $ch = curl_init($requestUrl);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
            'Content-Length: ' . strlen($data_string)
        ));

        $result = curl_exec($ch);
        if (curl_errno($ch)) {
            curl_close($ch);
            print curl_error($ch);
        }else{
            $result = json_decode($result, true);
            curl_close($ch);
            return $result;
        }
    }    

    /**
     * [xml xml转数组]
     * @param  [type] $xml [description]
     * @return [type]      [description]
     */
    public function xml($xml){
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);        
        return $values;
    }

支付核心

    /**
     * [Pay 支付]
     * @return [type] [description]
     */
    public function Pay() {
        //支付金额
        $fee              = 0.01;
        //公众号或小程序appid
        $appid            = '';
        //商品描述
        $body             = 'XX服务购买';
        //商户ID
        $mch_id           = '';
        //商户key
        $mch_key          = '';
        //随机字符串
        $nonce_str        = $this->nonce_str();
        //异步回调URL(需在商户平台添加白名单)
        $notify_url       = '';
        //用户openid
        $openid           = '';
        //商户订单号
        $out_trade_no     = $this->order_number();
        //用户openid
        $openid           = '';
        //终端IP(服务器IP)
        $spbill_create_ip = '';
        //单位为分
        $total_fee        = $fee*100;
        //交易类型 默认
        $trade_type       = 'JSAPI';
        //这里是按照顺序的 因为下面的签名是按照顺序 排序错误 肯定出错
        $post['appid']            = $appid;
        $post['body']             = $body;
        $post['mch_id']           = $mch_id;
        $post['nonce_str']        = $nonce_str;
        $post['notify_url']       = $notify_url;
        $post['openid']           = $openid;
        $post['out_trade_no']     = $out_trade_no;
        $post['spbill_create_ip'] = $spbill_create_ip;
        $post['total_fee']        = $total_fee;
        $post['trade_type']       = $trade_type;
        //生成签名
        $sign = $this->sign($post, $mch_key);
        //统一下单XML数据包生成
        $post_xml = '<xml>
               <appid>'.$appid.'</appid>
               <body>'.$body.'</body>
               <mch_id>'.$mch_id.'</mch_id>
               <nonce_str>'.$nonce_str.'</nonce_str>
               <notify_url>'.$notify_url.'</notify_url>
               <openid>'.$openid.'</openid>
               <out_trade_no>'.$out_trade_no.'</out_trade_no>
               <spbill_create_ip>'.$spbill_create_ip.'</spbill_create_ip>
               <total_fee>'.$total_fee.'</total_fee>
               <trade_type>'.$trade_type.'</trade_type>
               <sign>'.$sign.'</sign>
            </xml> ';
        //统一接口接口
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        //下单
        $xml = $this->http_request($url,$post_xml);
        //对下单结果解包(XML)
        $array = $this->xml($xml);
        if($array['return_code'] == 'SUCCESS' && $array['result_code'] == 'SUCCESS'){
            $time = time();
            $tmp = array();//临时数组用于签名
            $tmp['appId'] = $appid;
            $tmp['nonceStr'] = $nonce_str;
            $tmp['package'] = 'prepay_id='.$array['prepay_id'];
            $tmp['signType'] = 'MD5';
            $tmp['timeStamp'] = "$time";

            $data['state'] = 1;
            $data['timeStamp'] = "$time";  //时间戳
            $data['nonceStr'] = $nonce_str;//随机字符串
            $data['signType'] = 'MD5';     //签名算法,暂支持 MD5
            $data['package'] = 'prepay_id='.$array['prepay_id'];//统一下单接口返回的 prepay_id 参数值
            $data['paySign'] = $this->sign($tmp);//签名,具体签名方案参见微信公众号支付帮助文档;
            $data['out_trade_no'] = $out_trade_no;

        }else{
            $data['state'] = 0;
            $data['text'] = "错误";
            $data['RETURN_CODE'] = $array['RETURN_CODE'];
            $data['RETURN_MSG'] = $array['RETURN_MSG'];
        }
        //订单储存到本地
        $pay_data = array(
          'order_id' => $out_trade_no,
          'total_pay'   => $fee,
          'pay_status'  => '未支付',
          'pay_time'    => time()
        );
        //输出订单数据到前端,调用支付SDK
        echo json_encode($data, true);
    }

异步回调

xml转数组函数

function xmlToArray($xml)
{    
    //禁止引用外部xml实体
    libxml_disable_entity_loader(true);
    $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);        
    return $values;
}

异步回调验证

$pay_data = file_get_contents('php://input');
$pay_data = xmlToArray($pay_data);
if ($pay_data['result_code'] == 'SUCCESS' && $pay_data['return_code'] == 'SUCCESS') {
    //此处为订单成功回调后的业务逻辑
    //注:要验证订单真实性
}else{
    //支付失败的业务逻辑,记录异常
}

备注

1.订单数据返回到前端之前,将订单信息插入到订单表,用来异步回调时核对账单。
2.异步回调要做好订单验证,相关验证SDK可以去微信支付开发文档查看。
3.本代码支持微信公众号和小程序的JSAPI支付。
4.本代码需要根据自身业务需求进行封装使用。

最后修改:2019 年 03 月 11 日 04 : 18 PM
如果觉得我的文章对你有用,请随意赞赏

发表评论

5 条评论

  1. 一只小物块

    要认证号才行对吗

    1. 心语难诉
      @一只小物块

      认证过的服务号或小程序,并且开通微信支付。如果有认证的服务号并且开通了支付,可以使用服务号快速开通小程序,这样小程序也同样认证+开通微信支付了(需要微信商户授权)。

  2. 锋言锋语

    学习了,技术博客啊,收藏了!

  3. 微信选房

    程序倒是好搞,认证麻烦得要死

  4. Fate

    路过赞一个|´・ω・)ノ