上一章节我们讲解了小程序在线支付的前期准备工作,这一章我们将讲解如何编写支付接口。之前我们也说了,我使用的是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,也可以关注我们的微信公众号,大家可以相互交流。