ouyangfeixue

上一章节我们讲解了小程序在线支付的前期准备工作,这一章我们将讲解如何编写支付接口。之前我们也说了,我使用的是thinkphp5,因此希望大家在看我这篇文章的时候了解一下thinkphp5。

一、相关函数
/**
 * 密码字符集
 * @param int $length
 * @return string
 */
public function generateNonceStr($length=16){
    // 密码字符集,可任意添加你需要的字符
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    $str = "";
    for($i = 0; $i < $length; $i++)
    {
        $str .= $chars[mt_rand(0, strlen($chars) - 1)];
    }
    return $str;
}


//获取IP
public function GetIP()
    {
        static $ip = NULL;
        if($ip !== NULL) return $ip;
        if(isset($_SERVER[\'HTTP_X_FORWARDED_FOR\']))  {
            $arr = explode(\',\', $_SERVER[\'HTTP_X_FORWARDED_FOR\']);
            $pos = array_search(\'unknown\',$arr);
            if(false !== $pos) unset($arr[$pos]);
            $ip  = trim($arr[0]);
        }
        else if(isset($_SERVER[\'HTTP_CLIENT_IP\']))  {
            $ip = $_SERVER[\'HTTP_CLIENT_IP\'];
        }  else if(isset($_SERVER[\'REMOTE_ADDR\']))  {
            $ip = $_SERVER[\'REMOTE_ADDR\'];
        }
        //IP地址合法验证
        $ip = (false !== ip2long($ip)) ? $ip : \'0.0.0.0\';
        return $ip;
    }
/**
 * 获取订单序号
 * @return string
 */
public function getOrder() {
    //订单号码主体(YYYYMMDDHHIISSNNNNNNNN)
    $order_id_main = date(\'YmdHis\') . rand(10000000,99999999);
    //订单号码主体长度
    $order_id_len = strlen($order_id_main);
    $order_id_sum = 0;
    for($i=0; $i<$order_id_len; $i++){
        $order_id_sum += (int)(substr($order_id_main,$i,1));
    }
    //唯一订单号码(YYYYMMDDHHIISSNNNNNNNNCC)
    return $order_id_main . str_pad((100 - $order_id_sum % 100) % 100,2,\'0\',STR_PAD_LEFT);
}

 /**
     * 返回提示信息
     * @param $code string 错误码  4001 空值  4002 格式不正确  4003 长度  4004 提示  200正确放回 ,0失败
     * @param $msg string 错误描述
     * @param $data string 返回值
     * @return array
     */
    public function resMap($code, $msg, $data) {
        $map = array();
        $map[\'errMsg\'] = $msg;
        $map[\'data\'] = $data;
        $map[\'errCode\'] = $code;
        return json($map);
    }

 

二、签名函数
  /**
     * 生成签名, $KEY就是支付key
     * @return string 签名
     */
    public function MakeSign($params,$KEY){
        //签名步骤一:按字典序排序数组参数
        ksort($params);
        $string = $this->ToUrlParams($params);  //参数进行拼接key=value&k=v
        Log::write(\'参数进行拼接:\' .$string);
        //签名步骤二:在string后加入KEY
        $string2 = $string . "&key=".$KEY;
        //签名步骤三:MD5加密
        $string3 = md5($string2);
        //签名步骤四:所有字符转为大写
        $result = strtoupper($string3);
        return $result;
    }

    /**
     * 将参数拼接为url: key=value&key=value
     * @param $params
     * @return string
     */
    public function ToUrlParams($params){
        $string = \'\';
        if(!empty($params)){
            $array = array();
            foreach( $params as $key => $value ){
                $array[] = $key.\'=\'.$value;
            }
            $string = implode("&",$array);
        }
        return $string;
    }
三、HTTP请求
/**
     * 调用接口, $data是数组参数
     * @return string 签名
     */
    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;
    }

    //获取xml里面数据,转换成array
    private function xml2array($xml){
        $p = xml_parser_create();
        xml_parse_into_struct($p, $xml, $vals, $index);
        xml_parser_free($p);
        $data = "";
        foreach ($index as $key=>$value) {
            if($key == \'xml\' || $key == \'XML\') continue;
            $tag = $vals[$value[0]][\'tag\'];
            $value = $vals[$value[0]][\'value\'];
            $data[$tag] = $value;
        }
        return $data;
    }

    /**
     * 将xml转为array
     * @param $xml
     * @return bool|mixed
     */
    public function xml_to_array($xml){
        if(!$xml) {
            return false;
        }
        //将XML转为array
        libxml_disable_entity_loader(true);
        $data = json_decode(json_encode(simplexml_load_string($xml, \'SimpleXMLElement\', LIBXML_NOCDATA)), true);
        return $data;
    }

    public function post_data(){
        $receipt = $_REQUEST;
        if($receipt==null){
            $receipt = file_get_contents("php://input");
            if($receipt == null){
                $receipt = $GLOBALS[\'HTTP_RAW_POST_DATA\'];
            }
        }
        return $receipt;
    }

在这里,我要说明一下,在支付的时候我们需要商户号和商户密钥,这两样可以登录微信支付平台获取,相关截图如下:

 

 
商户密钥
 
商户号
四、支付函数
 /**
     * 付款
     * @param $total_fee int 金额
     * @param $openid string 用户OPENID
     * @param $order_id string 订单号
     * @return array
     */
    public  function Pay($total_fee,$openid,$order_id){
        if(empty($total_fee)){
            return $this->resMap(4002,\'金额有误\',\'金额有误\');
        }
        if(empty($openid)){
            return $this->resMap(4002,\'登录失效,请重新登录\',\'登录失效,请重新登录\');
        }
        if(empty($order_id)){
            return $this->resMap(4002,\'订单不存在\',\'订单不存在\');
        }
        $appid = \'xxx\';//如果是公众号 就是公众号的appid;小程序就是小程序的appid
        $body = \'订单支付\';
        $mch_id =\'xxx\'; //这是商户号,登录微信支付平台就能看到,参考教程一
        $KEY = \'xxxx\'; //这里是商户的支付密钥
        $nonce_str =    $this->generateNonceStr();//随机字符串
        $notify_url =   \'https://xxxxxxx/rest/xiao_notify_url\';  //支付完成回调地址url,不能带参数
        $out_trade_no = $order_id;//商户订单号
        $spbill_create_ip = $this->GetIP();
        $trade_type = \'JSAPI\';//交易类型 默认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;//服务器终端的ip
        $post[\'total_fee\'] = intval($total_fee) * 100;        //总金额 最低为一分钱 必须是整数
        $post[\'trade_type\'] = $trade_type;
        Log::write($post);
        $sign = $this->MakeSign($post,$KEY);              //签名
        Log::write($sign); //打印日志
        $post[\'sign\'] = $sign;
        ksort($post); //签名生成以后,还要进行一次排序,如果缺少这一步,你的签名永远都是“签名错误”的提示,这里我被坑过,PHP用的函数是ksort

        $post_xml = \'<xml>
<appid>\'.$post[\'appid\'].\'</appid>
<body>\'.$post[\'body\'].\'</body>
<mch_id>\'.$post[\'mch_id\'].\'</mch_id>
<nonce_str>\'.$post[\'nonce_str\'].\'</nonce_str>
<notify_url>\'.$post[\'notify_url\'].\'</notify_url>
<openid>\'.$post[\'openid\'].\'</openid>
<out_trade_no>\'.$post[\'out_trade_no\'].\'</out_trade_no>
<spbill_create_ip>\'.$post[\'spbill_create_ip\'].\'</spbill_create_ip>
<total_fee>\'.$post[\'total_fee\'].\'</total_fee><trade_type>\'.$post[\'trade_type\'].\'</trade_type><sign>\'.$post[\'sign\'].\'</sign></xml> \';
        Log::write($post_xml);//打印日志
        //统一下单接口prepay_id
        $url = \'https://api.mch.weixin.qq.com/pay/unifiedorder\';
        $xml = $this->http_request($url,$post_xml);     //POST方式请求http
        $array = $this->xml2array($xml);               //将【统一下单】api返回xml数据转换成数组,全要大写
        Log::write($array);
        if($array[\'RETURN_CODE\'] == \'SUCCESS\' && $array[\'RESULT_CODE\'] == \'SUCCESS\'){
            $time = time();
            $tmp=\'\';                            //临时数组用于签名
            $tmp[\'appId\'] = $appid;
            $tmp[\'nonceStr\'] = $nonce_str;
            $tmp[\'package\'] = \'prepay_id=\'.$array[\'PREPAY_ID\'];
            $tmp[\'signType\'] = \'MD5\';
            $tmp[\'timeStamp\'] = "$time";
 //这里的参数都是参考文档来的,地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
            $data[\'state\'] = 1;
            $data[\'timeStamp\'] =  "$time";           //时间戳
            $data[\'nonceStr\'] = $nonce_str;         //随机字符串
            $data[\'signType\'] = \'MD5\';              //签名算法,暂支持 MD5
            $data[\'package\'] = \'prepay_id=\'.$array[\'PREPAY_ID\'];   //统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
            $data[\'paySign\'] = $this->MakeSign($tmp,$KEY);       //签名,具体签名方案参见微信公众号支付帮助文档;
            $data[\'out_trade_no\'] = $out_trade_no;
            $data[\'RETURN_CODE\'] = $array[\'RETURN_CODE\'];
            $data[\'RETURN_MSG\'] = $array[\'RETURN_MSG\'];
            Log::write($tmp);
            Log::write($data);
            return $this->resMap(200, $data, $data);
        }else{
            $data[\'state\'] = 0;
            $data[\'text\'] = "错误";
            $data[\'RETURN_CODE\'] = $array[\'RETURN_CODE\'];
            $data[\'RETURN_MSG\'] = $array[\'RETURN_MSG\'];
            return $this->resMap(4002, $data, $data);
        }

    }
五、回调函数

当支付成功后,我们需要提供一个回调函数,帮助我们更改订单状态

 /**
     * 支付成功后跳转
     */
    public function xiao_notify_url() {
        $post = $this->post_data();    //接受POST数据XML个数
        $post_data = $this->xml_to_array($post);   //微信支付成功,返回回调地址url的数据:XML转数组Array
        $postSign = $post_data[\'sign\'];
        unset($post_data[\'sign\']);
        ksort($post_data);// 对数据进行排序
        $str = $this->ToUrlParams($post_data);//对数组数据拼接成key=value字符串
        $user_sign = strtoupper(md5($str));   //再次生成签名,与$postSign比较
        $out_trade_no = $post_data[\'out_trade_no\'];
        Log::write(\'订单好:\' . $out_trade_no);
        $orderModel = model(\'Order\');
        $res = $orderModel->getOrderMoney($out_trade_no);
        Log::write(\'支付成功后跳转:\' .$orderModel->getLastSql());
        if($post_data[\'return_code\']==\'SUCCESS\'&&$postSign) {
            if ($res[\'g_state\'] == 2) {
                return $this->return_success();
            } else {
                //修改订单状态
                $result = $orderModel->updateOrderStatus($out_trade_no,2);
                Log::write(\'支付成功后跳转2:\' .$orderModel->getLastSql());
                if ($result) {
                    //订单更新成功
                    $this->return_success();
                } else {
                    return $this->resMap(4002,\'订单更新失败\',\'订单更新失败\');
                }
            }
        } else {
            return $this->resMap(4002,\'微信支付失败\',\'微信支付失败\');
        }
    }

    /*
    * 给微信发送确认订单金额和签名正确,SUCCESS信息 -xzz0521
    */
    public function return_success(){
        $return[\'return_code\'] = \'SUCCESS\';
        $return[\'return_msg\'] = \'OK\';
        $xml_post = \'<xml>
                    <return_code>\'.$return[\'return_code\'].\'</return_code>
                    <return_msg>\'.$return[\'return_msg\'].\'</return_msg>
                    </xml>\';
        return $this->resMap(200, $this->xml2array($xml_post), $this->xml2array($xml_post));
    }

以上就是支付接口的核心代码,下一章节我们将讲解小程序中如何使用我们刚刚写的接口,谢谢大家。

以上就是解决签名错误的详细方案,如果还有不明白的地方,大家可以加入我们的微信交流群:731568857,也可以关注我们的微信公众号,大家可以相互交流。

分类:

技术点:

相关文章: