微信支付之退款(PHP版本)
这段日子在写微信支付退款的时候,发现很多问题,当然主要问题还是发生在异步回调的路上。
申请退款
总的来说呢,申请退款是挺简单的。
但是还是有几个重点的。比如说SSL证书的POST请求
,再比如说商家用户的KEY
。
这几个点可以在百度中找到答案。
我接下来就直接上申请退款的代码好了
代码实现之类文件
1.Xwechat.php
class Xwechat{
protected static function getConfig(){
$config = \think\facade\Config::get('tool.wechat.wechat_app');
if (!isset($config) || !isset($config['appid']) || !isset($config['appsecret']) || empty($config['appid']) || empty($config['appsecret'])){
outErr(31,'wechat app config is missing');
}
return $config;
}
public static function refund($orderSn,$refundOrderSn,$total_fee,$refund_fee,$transaction_id){
$time = date('Y-m-d H:i:s',$time);
$config = self::getConfig();
$wechatConfig = \think\facade\Config::get('tool.wechat.wechat_pay');
$unifiedData = [
'appid' => $config['appid'],
'mch_id' => $wechatConfig['mchid'],
'nonce_str' => md5($time),
// 'body' => $body,
'out_trade_no' => $orderSn,
'out_refund_no' => $refundOrderSn,
'refund_fee' => $refund_fee * 100,
'total_fee' => $total_fee * 100,
'transaction_id' => $transaction_id,
'notify_url' => $wechatConfig['refund_notify']
];
$unifiedData['sign'] = self::wechatPayCreateSign($unifiedData,$wechatConfig);
$xmlData = Xstring::arrayToXml($unifiedData);
$postData = HttpRequest::postRefundWechatPaySSLData($xmlData,'https://api.mch.weixin.qq.com/secapi/pay/refund');
$res = Xstring::xmlToArray($postData);
if ($res['return_code'] == 'FAIL'){
if (!empty($content['err_code_des'])) {
outErr(1,$content['err_code_des']);
}else{
outErr(1,$content['return_msg']);
}
}
return $res;
}
public static function wechatPayCreateSign($arr,$config){
ksort($arr);
$string="";
$i=1;
foreach($arr as $key=>$val){
if($i == 1){
$string .= $key."=".$val;
}else{
$string .= "&".$key."=".$val;
}
$i++;
}
$signtemp = $string."&key=".$config['key'];
$sign = MD5($signtemp);
$sign = strtoupper($sign);
return $sign;
}
}
2.Xstring.php
class Xstring{
public static function xmlToArray($data){
libxml_disable_entity_loader(true);
$data = json_decode(json_encode(simplexml_load_string($data,'SimpleXMLElement',LIBXML_NOCDATA)),true);
return $data;
}
public static function arrayToXml($arr){
$xml = "<xml>";
foreach ($arr as $key=>$val){
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
}
3.HttpRequest.php
class HttpRequest{
public static function postRefundWechatPaySSLData($xml,$url,$second = 30){
$path = dirname(__DIR__).'/file/wechatRefundPaySSL/';
if (is_array($xml)) {
$xml = http_build_query($xml);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数
if (!empty($options)) {
curl_setopt_array($ch, $options);
}
//https请求 不验证证书和host
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
//第一种方法,cert 与 key 分别属于两个.pem文件
//默认格式为PEM,可以注释
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLCERT,$path.'apiclient_cert.pem');//证书路径
//默认格式为PEM,可以注释
curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLKEY,$path.'apiclient_key.pem');//证书路径
$data = curl_exec($ch);
curl_close($ch);
return $data;
}
}
代码实现之实现
1.Refund.php
class Refund{
Xwechat::refund('商家订单号','商家退款单号','订单总价','退款金额','transaction_id');
}
退款回调
回调这里有个很严重的问题,那就是php>7.0的开发者注意,解密函数mcrypt_decrypt()不可用,要用openssl_decrypt()
代码实现
1.Notify.php
class Notify{
public function refund(){
//先获取微信服务器过来的XML信息
//最主要是解密
$wechatConfig = \think\facade\Config::get('tool.wechat.wechat_pay');
$datas = Xstring::xmlToArray(self::refundDecrypt($data['req_info'],md5($wechatConfig['key'])));
if ('SUCCESS' == $datas['refund_status']){
RefundAction::_updateStateById('success',[
'refundSn' => $datas['out_refund_no']
]);
$result = $datas;
}else{
$result = false;
}
}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>';
}
echo $str;
}
//php>7.0
public static function refundDecrypt($str,$key){
$str = openssl_decrypt($str , 'aes-256-ecb',$key, OPENSSL_ZERO_PADDING);
$pad = ord($str[($len = strlen($str)) - 1]);
$len = strlen($str);
$pad = ord($str[$len - 1]);
return substr($str, 0, strlen($str) - $pad);
}
//php<=7.0
public static function refundDecrypt($str,$key){
$str = base64_decode($str);
$str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB);
$block = mcrypt_get_block_size('rijndael_128', 'ecb');
$pad = ord($str[($len = strlen($str)) - 1]);
$len = strlen($str);
$pad = ord($str[$len - 1]);
return substr($str, 0, strlen($str) - $pad);
}
}