php申请微信用户退款接口。
@auth Milo
当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家账号上。
文档地址:pay.weixin.qq.com/wiki/doc/ap…
特别注意:需要双向证书!
什么是双向证书?
微信支付双向证书是指在进行微信支付时,服务器与客户端之间进行通信时,双方相互进行签名校验的一种机制。这主要是为了确保双方的身份安全,保护支付交易的安全性。 具体来说,双向证书包括服务器证书和客户端证书。服务器证书用于验证微信支付服务器的身份,确保客户端与正确的服务器进行通信。而客户端证书则是商户自行申请的,用于验证商户的身份,确保微信支付系统能够正确识别商户的证书,从而保证支付接口的安全性。 在通信过程中,服务器和客户端会相互交换证书并进行验证,通过比对签名等信息来确认对方的身份是否真实有效。如果验证通过,双方将建立起安全的通信连接,从而进行后续的支付交易操作。
简单理解,就是微信服务器上生成证书,你本地也需要用微信证书工具生成,然后放到你的客户端!
工作开始前准备内容:
- wxappid【微信公众号|微信小程序appid】
- wxmchid【微信商户平台商户号】
- paykey【微信商户平台支付秘钥】
- apiclient_cert.pem【证书文件】
- apiclient_key.pem【私钥文件】
实现代码:
调用接口
// 退款
public function refund_price() {
$id = input('id/d',0);
if(empty($id)) return ajaxArray(0,'数据异常');
// 查询订单
$order = model('order')->get($id);
$appid = pay_config('wxappid');
$mch_id = pay_config('wxmchid'); // 商户号昂
$key = pay_config('paykey'); //Api密钥
$notify_url = '您的退款结果通知URL'; // 如果需要接收退款结果通知,请设置
// 退款请求参数
$refund_params = array(
'appid' => $appid,
'mch_id' => $mch_id,
'nonce_str' => $this->getNonceStr(), // 生成随机字符串,用于签名
'out_trade_no' => $order['out_trade_no'],
'total_fee' => floatval($order['price']) * 100, // 单位为分,* 100,不允许小数点的出现
'refund_fee' => floatval($order['price']) * 100,// 单位为分,* 100,不允许小数点的出现
'out_refund_no' => $this->generateRefundNo(),
// 其他可能需要的参数,如 refund_desc、op_user_id 等
);
// 对退款请求参数按字典序排序并生成签名
ksort($refund_params);
$refund_params['sign'] = $this->generateSign($refund_params, $key); // 使用您的签名生成函数
// 将退款请求参数转换为 XML 格式
$xml = $this->arrayToXml($refund_params);
// 使用 cURL 或其他 HTTP 客户端发起 POST 请求
$url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
// 添加证书相关设置
$cert_path = ROOT_PATH . 'public/wxpay/apiclient_*****_cert.pem'; // 替换为您的证书文件的实际路径
$key_path = ROOT_PATH . 'public/wxpay/apiclient_*****_key.pem'; // 替换为您的私钥文件的实际路径
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, $cert_path);
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, $key_path);
$response = curl_exec($ch);
//返回结果
if($response){
curl_close($ch);
} else {
$error = curl_errno($ch);
$errorMsg = "curl出错,错误码:$error" . "<br>";
curl_close($ch);
return ajaxArray(0, '退款失败', $errorMsg);
}
// 解析退款响应 XML 并检查退款结果
$response_data = $this->xmlToArray($response);
if ($this->checkResponseSign($response_data, $key)) { // 使用您的验签函数验证响应签名
// 退款成功,处理退款结果数据
// 修改订单状态
model('order')->where('id',$id)->update(['status' => 4]);
return ajaxArray(1,'退款成功');
} else {
// 验签失败,可能存在安全风险,应记录并处理异常
return ajaxArray(0,'退款失败',$response_data);
}
}
封装的一些方法在这里
// 生成随机字符串
function getNonceStr($length = 32)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$nonceStr = '';
for ($i = 0; $i < $length; $i++) {
$nonceStr .= $chars[mt_rand(0, strlen($chars) - 1)];
}
return $nonceStr;
}
// 签名生成函数
function generateSign(array $params, string $key)
{
unset($params['sign']); //移除 sign 字段并重新计算签名
ksort($params); // 字典序排序
$stringA = urldecode(http_build_query($params)) . "&key=" . $key; // 拼接字符串
$sign = strtoupper(md5($stringA)); // MD5加密并转为大写
return $sign;
}
// 请求参数转换为 XML 格式
function arrayToXml(array $data)
{
$xml = '<xml>';
foreach ($data as $key => $value) {
if (is_numeric($value)) {
$xml .= "<{$key}>{$value}</{$key}>";
} else {
$xml .= "<{$key}><![CDATA[{$value}]]></{$key}>";
}
}
$xml .= '</xml>';
return $xml;
}
// 解析 XML
function xmlToArray(string $xml)
{
libxml_disable_entity_loader(true);
$xml = html_entity_decode($xml);
$data = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$json = json_encode($data);
$response_data = json_decode($json, true);
return $response_data;
}
// 验签函数验证响应签名
function checkResponseSign(array $responseData, string $key)
{
if (!isset($responseData['return_code']) || $responseData['return_code'] !== 'SUCCESS') {
return false; // 如果返回码不是 SUCCESS,直接返回验签失败
}
if (!isset($responseData['sign'])) {
return false; // 如果没有 sign 字段,直接返回验签失败
}
$calculatedSign = $this->generateSign($responseData, $key);
// 比较计算出的签名与返回的签名
return $calculatedSign === $responseData['sign'];
}
// 退货订单号
function generateRefundNo()
{
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-|*@';
$randomPart = '';
for ($i = 0; $i < 30; $i++) { // 减少随机部分长度为30,为时间戳留出空间
$randomPart .= $chars[random_int(0, strlen($chars) - 1)];
}
$timestamp = strval(time()); // 当前时间戳(以秒为单位)
// 将时间戳拼接到随机部分前面,保持总长度为64个字符
$refundNo = substr($timestamp, -24) . $randomPart; // 取最近24位(大约8年)时间戳
return $refundNo;
}
至此,恭喜您,退款完成喽,需要注意的是,我们请求的接口是申请退款,并非直接退款,为确保退款成功且有效,请访问查询退款接口,查询是否真实退款完成。 谢谢观看,不足之处还望指出,大家共同进步~加油。
Tomorrow will be better!