小程序在线支付教程二
2018-10-24 本文已影响12人
陈楠酒肆
上一章节我们讲解了小程序在线支付的前期准备工作,这一章我们将讲解如何编写支付接口。之前我们也说了,我使用的是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));
}
以上就是支付接口的核心代码,下一章节我们将讲解小程序中如何使用我们刚刚写的接口,谢谢大家。
相关章节
小程序在线支付教程一
nodeJS开发一套完整的项目(1、基础配置)
nodeJS开发一套完整的项目(2、相关模块介绍)
nodeJS开发一套完整的项目(3、数据库链接和项目启动)
nodeJS开发一套完整的项目(4、编写底层功能模块)
nodeJS开发一套完整的项目(5、开发用户模块)
nodeJS开发一套完整的项目(6、省市县模块)