微信支付之退款(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);
    }
 } 
tag(s): Wechat
show comments · back · home
Edit with markdown
召唤看板娘