下面是我封装的关于微信小程序退款功能,自己在线上项目中是可以实现功能的,所以分享给大家。
注意以下几点:1.我使用的框架偏原生,所以里面关于mysql的部分写法都是偏原生的,不必在意;
2.对于此项功能,我建了一个退款记录表,如图所示:此表用于记录退款记录【可根据实际业务逻辑进行修改】
3.以下功能仅实现退款操作,实际业务逻辑,可根据返回值进行再次进行具体业务逻辑。
/***************************************
****************************************
***微信小程序退款***********************
****************************************
****************************************/
/**
* 退款
*
* 传参:
* @param outTradeNo stirng 商户退款单号
* @param totalFee int 订单金额[单位分]
* @param refundFee int 退款金额[单位分]
* @param refund_desc string 退款原因
* @param openid string 用户openid
* @param appid string 微信小程序appid
* @param mch_id string 微信商户号
* @param tableName string 数据表名称
*
* 注意事项:
* 1.交易时间超过一年的订单无法提交退款
* 2.微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。
* 【特别注意】*****一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号*****
* 3.请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
* 4.每个支付订单的部分退款次数不能超过50次
*/
function refund($outTradeNo,$totalFee,$refundFee,$refund_desc,$openid,$appid,$mch_id,$key,$tableName){
//查询是否有此商户订单号退款失败的记录;如果有,继续使用已有记录中的商户退款单号来进行再次退款;没有,则生成一个新的商户退款单号
$mysql = new Mysql();
$row = $mysql->fetchRow("select * from {$tableName} where openid='{$openid}' and out_trade_no='{$outTradeNo}' order by createTime desc ");
unset($mysql);
if($row['orderStatus']==3 && $row['cashStatus']==3 && $row['result_code']==2){//表示此商户订单号最近一次业务为退款失败,继续使用之前的商户退款单号
$outRefundNo = $row['outRefundNo'];
}else{//表示此商户订单号最近一次业务成功,生成一个新的商户退款单号
$outRefundNo = "tk".date("YmdHis").time();//商户退款单号
}
$parma = array(
'appid'=>$appid,//小程序ID
'mch_id'=>$mch_id,//商户号
'nonce_str'=> $this->getNonceStr(),//随机字符串
'out_refund_no'=> $outRefundNo,//商户退款单号
'out_trade_no'=> $outTradeNo,//商户订单号
'total_fee'=> $totalFee,//订单金额[单位分]
'refund_fee'=> $refundFee,//退款金额[单位分]
'refund_desc'=>$refund_desc,//退款原因
);
$parma['sign'] = $this->makeSign($parma,$key);
$data = [
'openid'=>$openid,
'outRefundNo'=>$outRefundNo,
'out_trade_no'=>$outTradeNo,
'refund_desc'=>$refund_desc,
'total_fee'=>$totalFee,
'refund_fee'=>$refundFee,
'createTime'=>time(),
'createIp'=>get_client_ip()
];
$mysql = new Mysql();
$res = $mysql->insert($data,$tableName);//退款记录
if($res){
unset($mysql);
$xmldata = $this->arrToXml($parma);
$result =$this->curl_post_ssl('https://api.mch.weixin.qq.com/secapi/pay/refund',$xmldata);
return $result;
}else{
unset($mysql);
jReturn(ErrorCode::INSERT_FAIL, '由于系统原因,退款失败');
}
}
/**
* 随机字符串
* @param int $length
* @return string
*/
function getNonceStr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* 签名
* @param $data
* @return string
*/
function makeSign($data,$key)
{
// 关联排序
ksort($data);
// 字典排序
$str = http_build_query($data);
// 添加商户密钥
$str .= '&key=' . $key;
// 清理空格
$str = urldecode($str);
$str = md5($str);
// 转换大写
$result = strtoupper($str);
return $result;
}
/**
* 数组转XML
* @param $data
* @return string
*/
function arrToXml($data)
{
$xml = "<xml>";
// 遍历组合
foreach ($data as $k => $v) {
$xml .= '<' . $k . '>' . $v . '</' . $k . '>';
}
$xml .= '</xml>';
return $xml;
}
/**
* [curl_post_ssl 发送curl_post数据]
* @param [type] $url [发送地址]
* @param [type] $xmldata [发送文件格式]
* @param [type] $second [设置执行最长秒数]
* @param [type] $aHeader [设置头部]
* @return [type] [description]
*/
function curl_post_ssl($url, $xmldata, $second = 30, $aHeader = array())
{
$isdir = $_SERVER['DOCUMENT_ROOT'] . "/oio/global/config/cert/oio/";//证书位置;绝对路径
$ch = curl_init();//初始化curl
curl_setopt($ch, CURLOPT_TIMEOUT, $second);//设置执行最长秒数
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_URL, $url);//抓取指定网页
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// 终止从服务端进行验证
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);//
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');//证书类型
curl_setopt($ch, CURLOPT_SSLCERT, $isdir . 'apiclient_cert.pem');//证书位置
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');//CURLOPT_SSLKEY中规定的私钥的加密类型
curl_setopt($ch, CURLOPT_SSLKEY, $isdir . 'apiclient_key.pem');//证书位置
curl_setopt($ch, CURLOPT_CAINFO, 'PEM');
curl_setopt($ch, CURLOPT_CAINFO, $isdir . 'rootca.pem');
if (count($aHeader) >= 1) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);//设置头部
}
curl_setopt($ch, CURLOPT_POST, 1);//post提交方式
curl_setopt($ch, CURLOPT_POSTFIELDS, $xmldata);//全部数据使用HTTP协议中的"POST"操作来发送
$data = curl_exec($ch);//执行回话
if ($data) {
curl_close($ch);
return $this->xmlToArray($data);
} else {
$error = curl_errno($ch);
echo "call faild, errorCode:$error\n";
curl_close($ch);
return false;
}
}
最后希望大家多多给我点赞,谢谢啦!