微信小程序实现支付接口

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

一,准备小程序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
————————————————

原文链接:https://blog.csdn.net/hbuxiaoshe/article/details/83987275


发表评论

电子邮件地址不会被公开。 必填项已用*标注