微信小程序实现支付接口

最近做小程序涉及到微信支付,连微信支付都没有做过的我无从下手,在网上搜索到了几篇帖子也没看明白,没办法只好照着某一篇(来源:微信小程序中实现微信支付小程序支付,详细过程)硬着头皮先写了,最后经过几次调试终获成功,所以我对支付做一个总结,分享出来方便他人。

一,准备小程序id,小程序密钥,商户号,商户密钥,需要openid(微信登录后获得,调用https://api.weixin.qq.com/sns/jscode2session)

二,接口开发

接口有3个:预支付,支付,回调,php代码在后面

(一)预支付:(1)获取参数:openid,商品信息,金额;(2)设定回调接口地址,自己生成订单号,可将订单号与openid和商品信息一并写入mysql,等支付完成后在修改状态;(3)把所有字段汇集起来做签名一起提交给统一下单地址(https://api.mch.weixin.qq.com/pay/unifiedorder),成功的话会返回prepay_id,将prepay_id输出(给 支付接口)。

统一下单需要的参数:

<xml>
<appid>小程序id</appid>
<body>商品信息,比如商品名称</body>
<mch_id>商户号</mch_id>
<nonce_str>随机字符串,仅用于加密</nonce_str>
<notify_url>回调接口地址</notify_url>
<openid>openid,即用户id</openid>
<out_trade_no>订单号,自己生成</out_trade_no>
<spbill_create_ip>服务器ip</spbill_create_ip>
<total_fee>金额,单位分</total_fee>
<trade_type>交易类型,默认JSAPI</trade_type>
<sign>以上所有字段的签名</sign>
</xml>

统一下单返回数据:

<xml><return_code>状态码,SUCCESS|FAIL</return_code>
<return_msg>状态信息</return_msg>
<appid>小程序id</appid>
<mch_id>商户号</mch_id>
<nonce_str>微信生成的随机字符串</nonce_str>
<sign>签名</sign>
<result_code>同return_code?</result_code>
<prepay_id>预支付id,重要</prepay_id>
<trade_type>交易类型,默认JSAPI</trade_type>
</xml>

(二)支付:支付接口很简单,获取prepay_id后计算签名返回即可,小程序拿到签名后会发起真正的支付。此时会提醒用户输入密码,如下图:

(三)回调:当支付成功时,微信会调用此接口。可以实现自己业务逻辑,比如订单完成后修改状态。

接口输入xml数据:

<xml><appid>小程序id</appid>
<bank_type>银行类型,CFT</bank_type>
<cash_fee>金额</cash_fee>
<fee_type>金额类型,CNY</fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id>商户号</mch_id>
<nonce_str>随机字符串,自己生成发给微信的</nonce_str>
<openid>openid</openid>
<out_trade_no>订单号</out_trade_no>
<result_code>状态码,SUCCESS|FAIL</result_code>
<return_code>状态码,SUCCESS|FAIL</return_code>
<sign>签名</sign>
<time_end>支付完成时间</time_end>
<total_fee>金额</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id>微信交易号</transaction_id>
</xml>

验证签名正确后,处理自己业务流程。

如果返给微信的数据不是SUCCESS的话,微信会间隔一段时间后再次请求(15秒后,15秒后,30秒后,3分钟后,30分钟后,30分钟后,30分钟后,30分钟后,1小时后,。。。)

三,小程序端调用流程

小程序流程不懂,流程可参考文档 微信小程序中实现微信支付 在提交prepay_id时需要从字符串中截取出来。

四,php代码

(1)预支付prepay.php:

/**
 * 预支付请求接口(POST)
 * @param string $openid   openid
 * @param string $body    商品简单描述
 * @param string $total_fee 金额,单位分
 * @return  json的数据
 */
require_once("config.php"); // config.php记录了小程序id,密钥,商户号及密钥
require_once("utils.php");  // 一些功能函数

$openid = Request('openid');
$body = Request('body'); // 商品信息,比如title
$total_fee = RequestInt('total_fee', 1); // 金额

flog("prepay openid:$openid total_fee:$total_fee ");

$nonce_str =    nonce_str(); //随机字符串
$notify_url =   "https://你的域名/notify.php"; // 回调地址
$out_trade_no = trade_no();      //商户订单号,随机生成
$spbill_create_ip = '你的ip';  // 服务器ip
$trade_type = 'JSAPI';//交易类型 默认

// 订单发起时,可将信息存入mysql
$sql = "insert into table";
...
flog("prepay:$sql");


//这里是按照顺序的 因为下面的签名是按照顺序 排序错误 肯定出错
$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'] = $total_fee;
$post['trade_type'] = $trade_type;
$sign = sign($post);//签名


$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> ';
/*
$arr = xml($post_xml);//全要大写
print_r($arr);
exit();
*/

//统一接口prepay_id
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$xml = http_request($url,$post_xml);
flog("prepay: get xml: $xml");

$arr = xml2arr($xml);
if ($arr['return_code'] == 'SUCCESS' && $arr['return_code'] == 'SUCCESS') {
  $time = time();
  $tmp=array();//临时数组用于签名
  $tmp['appId'] = $appid;
  $tmp['nonceStr'] = $nonce_str;
  $tmp['package'] = 'prepay_id='.$arr['prepay_id'];
  $tmp['signType'] = 'MD5';
  $tmp['timeStamp'] = "$time";

  $data['error'] = 0;
  $data['timeStamp'] = "$time";//时间戳
  $data['nonceStr'] = $nonce_str;//随机字符串
  $data['signType'] = 'MD5';//签名算法,暂支持 MD5
  $data['package'] = 'prepay_id='.$arr['prepay_id'];//统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
  $data['paySign'] = sign($tmp);//签名,具体签名方案参见微信公众号支付帮助文档;
  $data['out_trade_no'] = $out_trade_no;
} else {
  $data['error'] = 1;
  $data['msg'] = $arr['return_msg'];
  $data['wxcode'] = $arr['return_code'];
}

flog("prepay ok:".json_encode($data));

echo json_encode($data); //小程序需要的数据 返回前端

(2)支付pay.php

/**
 * 进行支付接口(POST)
 * @param string $prepay_id 预支付ID(调用prepay()方法之后的返回数据中获取)
 * @return  json的数据
 */

require_once("config.php");
require_once("utils.php");

$prepay_id = Request('prepay_id');

$data = array(
      'appId'   => $appid,
      'nonceStr'  => nonce_str(),
      'package' => 'prepay_id='.$prepay_id,
      'signType'  => 'MD5',
      'timeStamp' => time()
);

$data['paySign'] = sign($data);

flog("pay:".json_encode($data));
echo json_encode($data);

(3)回调notify.php

require_once("config.php");
require_once("utils.php");

  $xml = file_get_contents("php://input"); // 获取输入
  //$xml = $GLOBALS['HTTP_RAW_POST_DATA'];

  flog("notify: get xml:$xml");

  //将服务器返回的XML数据转化为数组
  $data = xml2arr($xml);
  // 保存微信服务器返回的签名sign
  $data_sign = $data['sign'];
  // sign不参与签名算法
  unset($data['sign']);
  $sign = sign($data);

  flog("notify sign:$sign");
  // 判断签名是否正确  判断支付状态
  if (($sign===$data_sign) && ($data['return_code']=='SUCCESS') && ($data['return_code']=='SUCCESS') ) {
    $result = $data;
    //获取服务器返回的数据
    $order_sn = $data['out_trade_no'];      //订单单号
    $openid = $data['openid'];          //付款人openID
    $total_fee = $data['total_fee'];      //付款金额
    $transaction_id = $data['transaction_id'];  //微信支付流水号

    //可根据订单号更新数据库
    $sql = "update table set where";
    ...

    // TODO: 支付完成后,业务流程需处理

  } 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>';
  }
  flog("notify result:$str");
  echo $str; // 返回给微信
  //return $result; // 这一步没必要

(4)utils.php

  // Author: [email protected] (Kunlong She)
  // Created Time:2018-08-20 11:29:24
function RequestInt($str, $default = 0) {
  if (isset($_REQUEST[$str])) {
    return intval($_REQUEST[$str]);
  } else {
    return $default;
  }
}

function Request($str) {
  if (isset($_REQUEST[$str])) {
    $rst = str_replace("'", "''", $_REQUEST[$str]);
    $rst = str_replace("\\", "\\\\", $rst);
    return $rst;
  } else {
    return "";
  }
}

function Str($str) {
  if (empty($str)) {
    return "";
  } else {
    return $str;
  }
}

function flog($str) {
  $fp = fopen("/tmp/a.log", "a+");
  $date = date("Y-m-d H:i:s");
  fprintf($fp, "$date $str\n");
  fclose($fp);
}

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

// 订单号 随机字符串
// 随便怎么实现,尽量保证唯一,可以根据它来确定订单状态
function trade_no() {
  $result = date("YmdHis", time());
  $str = "0123456789";
  for ($i=0;$i<4;$i++) {
    $result .= $str[rand(0,9)];
  }
  return $result;
}

//curl
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;
}


//签名函数,未排序,传入数据需要对key按字母序排好,否则签名不正确
function sign($data){
  $stringA = '';
  foreach ($data as $key=>$value){
    if(!$value) continue;
    if($stringA) $stringA .= '&'.$key."=".$value;
    else $stringA = $key."=".$value;
  }
  $wx_key = '你的key';//申请支付后有给予一个商户账号和密码,登陆后自己设置key
  $stringSignTemp = $stringA.'&key='.$wx_key;//申请支付后有给予一个商户账号和密码,登陆后自己设置key 
  return strtoupper(md5($stringSignTemp));
}


//获取xml,注意key统一转成了小写
function xml2arr($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[strtolower($tag)] = $value;
  }
  return $data;
}

五,一些问题

(1)代码根据网上提供的改编而来,如果需要thinkphp上运行,要么csdn上找一份源码,要么依照这个改写

(2)回调函数是给微信调用的,里面可以不写return(thinkphp中有return),签名验证成功后可以写业务逻辑,可通过订单号处理,预支付写入订单号,回调修改订单号完成订单及后续处理。

(3)小程序id修改后,小程序与接口都需要修改,数据库中的openid需要重新登录,否则appid与openid不匹配。

(4)签名函数需要商户key

。。。

猜你喜欢

转载自blog.csdn.net/hbuxiaoshe/article/details/83987275