退款要注意的地方
1、证书的路径–证书在商户平台下载并放在public里面引用即可
2、退款回调地址-在商户平台设置,不带参数
3、金额都要x100变元为分
4、商户退款单号out_refund_no–自定义-相当于支付时订单号
5、微信单号transaction_id–在支付回调时需要返回存储的参数,见另一篇小程序支付回调方法
6、商户订单号out_trade_no和微信单号transaction_id任选其一,我只用了transaction_id没有报错
//退款
//@param order_id订单号
public function refund()
{
$token=input('post.token');
$res=checkToken($token);
if($res ==90001){
$order_id=input('post.order_id');
$info=Db::name('order')->where('order_id',$order_id)->find();
if ($info['expire_time']< time()){
$data['code']=5;
$data['msg']='订单已过期';
$data['date']=null;
return json($data);
}
$result=Db::name('refund')->where('order_id',$order_id)->find();
if ($result){
$data['code']=4;
$data['msg']='您已申请退款,请勿重复申请';
$data['date']=null;
return json($data);
}
$res1=$this->refundOrder($order_id,$info);
if ($res1){
$appid=config('base.web_appid');
$mch_id=config('base.wev_mch_id');
$key=config('base.web_appsecret');
$apiKey=config('base.web_apiKey');
$wxPay = new WxpayService($mch_id,$appid,$key,$apiKey);
$order_no=$order_id;//退款的订单号
$total_fee=intval($info['amount']*100);;//订单金额
$refund_fee=intval($res1['amount']*100);//退款的金额
$transaction_id=$info['transaction_id'];
$notify_url='https://域名/xxx/xx/xx/refundNotify';
$result= $wxPay->wx_refund($appid,$mch_id,$order_no,$notify_url,$total_fee,$refund_fee,$transaction_id,$apiKey);
if ($result){
$data['code']=1;
$data['msg']='退款申请成功';
$data['date']=$order_no;
return json($data);
}else{
$data['code']=0;
$data['msg']='退款申请失败';
$data['date']=null;
return json($data);
}
}else{
$data['code']=0;
$data['msg']='退款申请失败';
$data['date']=null;
return json($data);
}
}else if($res == 90002){
$data['code']=2;
$data['msg']='token验证出错';
$data['date']=null;
}else if($res == 90003){
$data['code']=3;
$data['msg']='token超时,请重新登录';
$data['date']=null;
}
return json($data);
}
//退款回调
public function refundNotify(){
$data = file_get_contents('php://input');
if (empty($data)){
$data=$GLOBALS['HTTP_RAW_POST_DATA'];;
}
$arr = $this -> xmlToArray($data);
//判断返回状态
if($arr['return_code'] == 'SUCCESS'){
$req_info = $arr['req_info'];
$rep=$this->decipheringReqInfo($req_info);
if($rep["refund_status"] == "SUCCESS") {
//修改订单状态
$res = Db::name('order')->where('order_id', $rep['out_trade_no'])->update(['status' => 9]);
$res1 = Db::name('refund')->where('order_id', $rep['out_trade_no'])->update(['status' => 2,'update_time'=>$rep['success_time']]);
return '<xml> <return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
}
}
return '<xml> <return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
}
//解密退款成功时,微信回调返回的信息
public static function decipheringReqInfo($str){
//微信商户key
$key = config('base.web_apiKey');
$str = base64_decode($str);
$xml = openssl_decrypt($str,'aes-256-ecb',md5($key),OPENSSL_RAW_DATA);
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
}
//将xml格式转换成数组
public function xmlToArray($xml) {
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$val = json_decode(json_encode($xmlstring), true);
return $val;
}
<?php
namespace WxpayService;
class WxpayService
{
// 退款地址
private $refund_url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
// 证书文件放在与入口文件同级的目录下
private $SSLCERT_PATH = './apiclient_cert.pem'; // api证书路径
private $SSLKEY_PATH = './apiclient_key.pem'; // 密钥证书路径
protected $mchid;
protected $appid;
protected $appKey;
protected $apiKey;
public $data = null;
public function __construct($mchid, $appid, $appKey,$key)
{
$this->mchid = $mchid; //https://pay.weixin.qq.com 产品中心-开发配置-商户号
$this->appid = $appid; //微信支付申请对应的公众号的APPID
$this->appKey = $appKey; //微信支付申请对应的公众号的APP Key
$this->apiKey = $key; //https://pay.weixin.qq.com 帐户设置-安全设置-API安全-API密钥-设置API密钥
}
/**
* 微信退款
* [wx_refund description]
* @Author 念天地之悠悠
* @DateTime 2019-03-11
* @param [type] $appid [description] APPID
* @param [type] $mch_id [description] 商户号
* @param [type] $order_no [description] 商户退款单号
* @param [type] $notify_url [description] 异步通知地址
* @param [type] $total_fee [description] 订单金额
* @param [type] $refund_fee [description] 退款金额 单位分
* @param [type] $transaction_id [description] 微信单号
* @param [type] $wx_key [description] 申请支付后设置的key
* @return [type] [description]
*/
public function wx_refund($appid,$mch_id,$order_no,$notify_url,$total_fee,$refund_fee,$transaction_id,$wx_key){
$post_data = [];
$nonce_str = $this->nonce_str();
$post_data['appid'] = $appid; // APPID
$post_data['mch_id'] = $mch_id; // 商户号
$post_data['nonce_str'] = $nonce_str; // 随机码
$post_data['out_refund_no'] = $order_no; // 商户退款单号
$post_data['refund_fee'] = (int)$refund_fee; // 退款金额
$post_data['total_fee'] = (int)$total_fee; // 订单金额
$post_data['transaction_id'] = $transaction_id; // 微信单号
$sign = $this->sign($post_data,$wx_key); // 签名
$post_xml = '<xml>
<appid>'.$appid.'</appid>
<mch_id>'.$mch_id.'</mch_id>
<nonce_str>'.$nonce_str.'</nonce_str>
<out_refund_no>'.$order_no.'</out_refund_no>
<refund_fee>'.$refund_fee.'</refund_fee>
<total_fee>'.$total_fee.'</total_fee>
<transaction_id>'.$transaction_id.'</transaction_id>
<sign>'.$sign.'</sign>
</xml>';
$xml = $this->postXmlSSLCurl($this->refund_url,$post_xml);
// 将xml转数组
$arr = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
if ($arr === false) {
die('parse xml error');
}
if ($arr->return_code != 'SUCCESS') {
die($arr->return_msg);
}
if ($arr->result_code != 'SUCCESS') {
die($arr->err_code);
}
return true;
}
private function nonce_str(){
$result = '';
$str = 'QWERTYUIOPASDFGHJKLZXVBNMqwertyuioplkjhgfdsamnbvcxz';
for ($i = 0; $i < 32; $i++) {
$result .= $str[rand(0,48)];
}
return $result;
}
/**
* 签名
* [sign description]
* @Author 念天地之悠悠
* @DateTime 2019-03-11
* @param [type] $data [description] 数据集
* @param [type] $wx_key [description] 微信支付key
* @return [type] [description]
*/
private function sign($data,$wx_key = ''){
$stringA = '';
foreach ($data as $key => $value) {
if (!$value) continue;
if ($stringA) $stringA .= '&'.$key."=".$value;
else $stringA = $key."=".$value;
}
if ($wx_key == '') {
$stringSignTemp = $stringA;
}else{
$stringSignTemp = $stringA.'&key='.$wx_key;
}
return strtoupper(md5($stringSignTemp));
}
/**
* 需要证书的curl请求
* [postXmlSSLCurl description]
* @Author 念天地之悠悠
* @DateTime 2019-03-12
* @param [type] $url [description] 访问地址
* @param [type] $xml [description] mxl数据体
* @param integer $second [description] 超时时间
* @return [type] [description]
*/
public function postXmlSSLCurl($url,$xml,$second=30){
$ch = curl_init();
//超时时间
curl_setopt($ch,CURLOPT_TIMEOUT,$second);
//这里设置代理,如果有的话
//curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
//curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
//设置header
curl_setopt($ch,CURLOPT_HEADER,FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE);
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
//默认格式为PEM,可以注释
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLCERT, $this->SSLCERT_PATH);
//默认格式为PEM,可以注释
curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLKEY, $this->SSLKEY_PATH);
//post提交方式
curl_setopt($ch,CURLOPT_POST, true);
curl_setopt($ch,CURLOPT_POSTFIELDS,$xml);
$data = curl_exec($ch);
//返回结果
if($data){
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
echo "curl出错,错误码:$error"."<br>";
curl_close($ch);
return false;
}
}
}