【微信小程式】退款功能教程(含申請退款和退款回撥)

weixin_33714884發表於2017-12-06

1、一定要區分小程式和公眾號的退款,唯一的區別就是 appid不一樣,其他的都是一樣的。

不廢話,直接寫程式碼了啊。 放大招!!!

然後,需要注意的:最好是把證照放在下面的php的同級或者下級。

證照的路徑一定要是伺服器的根路徑,比如E:\tupuu\WWW\XXX。而像http://www.xxx.com/../.. 是不行的,會報58錯誤。

DEMO1、用來除錯退款流程,在瀏覽器直接訪問這個php檔案。

 

<?php
/**
 * 微信公眾號和小程式退款申請介面-demo
 * ====================================================
 * 注意:同一筆單的部分退款需要設定相同的訂單號和不同的
 * out_refund_no。一筆退款失敗後重新提交,要採用原來的
 * out_refund_no。總退款金額不能超過使用者實際支付金額(現
 * 金券金額不能退款)。
*/
//include_once(S_ROOT ."xxpay/WxPayPubHelper/WxPayPubHelper.miniprogram.php");
     //輸入需退款的訂單號
     if (!isset($_POST["out_trade_no"]) || !isset($_POST["refund_fee"]))
     {
         $out_trade_no = " ";
         $refund_fee = "1";
     }else{
         //echo "退款訂單號:".$_POST['out_trade_no'];
         $order_msg= array(
            'out_trade_no'=>'homeX20171206155833_1180',
             'out_trade_no'=>'homeX20171206155833_1180',
             'order_amount'=>'0.02',
             
         );
         
         $appid =        'wx60****d';//如果是公眾號 就是公眾號的appid;小程式就是小程式的appid
         $mch_id =       '126****01';
         $KEY = 'xixi***09000908bkj';
         $nonce_str =    randomkeys(32);//隨機字串
         $op_user_id = $mch_id;
         $out_trade_no = $order_msg['out_trade_no'];//商戶傳微信的訂單號
         $out_refund_no = $order_msg['out_trade_no'];//使用者請求退款id對應的out_trade_no訂單號
         $refund_fee =1;//退款金額
         $total_fee = 2;//訂單總金額
         
         //這裡是按照順序的 因為下面的簽名是按照(字典序)順序 排序錯誤 肯定出錯
         $post['appid'] = $appid;
         $post['mch_id'] = $mch_id;
         $post['nonce_str'] = $nonce_str;//隨機字串
         $post['op_user_id'] = $mch_id;
         $post['out_refund_no'] = $out_refund_no;
         $post['out_trade_no'] = $out_trade_no;
         $post['refund_fee'] = $refund_fee;
         $post['total_fee'] = $total_fee;        //總金額 最低為一分錢 必須是整數
         
         $sign = MakeSign($post,$KEY);              //簽名
         
         $post_xml = '<xml>
                       <appid>'.$appid.'</appid>
                       <mch_id>'.$mch_id.'</mch_id>
                       <nonce_str>'.$nonce_str.'</nonce_str>
                       <op_user_id>'.$mch_id.'</op_user_id>
                       <out_refund_no>'.$out_refund_no.'</out_refund_no>
                       <out_trade_no>'.$out_trade_no.'</out_trade_no>
                       <refund_fee>'.$refund_fee.'</refund_fee>
                       <total_fee>'.$total_fee.'</total_fee>
                       <sign>'.$sign.'</sign>
                    </xml>';
         //echo $post_xml;
         //申請退款介面
         $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
         $xml = curl_post_ssl($url,$post_xml);     //POST方式請求http,支付無需證照;申請退款api需要證照校驗
         $array = xml2array($xml);               //將【申請退款】api返回xml資料轉換成陣列,全要大寫
         //var_dump($array);
         if($array['RETURN_CODE'] == 'SUCCESS' && $array['RESULT_CODE'] == 'SUCCESS'){//退款業務已受理//
         
    //          $up['order_status'] = 'refunding';  //退款申請中
    //          $up['order_id'] = $order_id;
    //          $up['order_time'] = time();         //退款申請時間
    //          if(M("home_order","xxf_witkey_")->save($up)){
    //              $model_demo = new \Home\Model\HomeorderModel('home_order','xxf_witkey_');
    //              $res = $model_demo->home_order_list($userwx_info['uid'],19);
    //              if(!$res){
    //                  exit('訂單資訊有誤');
    //              }else{
    //                  echo json_encode($res);exit;
    //              }
    //          }
          // for 除錯
             echo "業務結果:".$array['RETURN_CODE']."<br>";
             echo "錯誤程式碼:".$array['err_code']."<br>";
             echo "錯誤程式碼描述:".$array['err_code_des']."<br>";
             echo "公眾賬號ID:".$array['APPID']."<br>";
             echo "商戶號:".$array['MCH_ID']."<br>";
             echo "子商戶號:".$array['sub_mch_id']."<br>";
             echo "裝置號:".$array['device_info']."<br>";
             echo "簽名:".$array['SIGN']."<br>";
             echo "微信訂單號:".$array['transaction_id']."<br>";
             echo "商戶訂單號:".$array['OUT_TRADE_NO']."<br>";
             echo "商戶退款單號:".$array['OUT_REFUND_NO']."<br>";
             echo "微信退款單號:".$array['refund_idrefund_id']."<br>";
             echo "訂單總金額:".$array['TOTAL_FEE']."<br>";
             echo "退款金額:".$array['REFUND_FEE']."<br>";
             echo "現金券退款金額:".$array['coupon_refund_fee']."<br>";
         
         }else{
            echo "RETURN_CODE:".$array['RETURN_CODE']."<br>";
             echo "RESULT_CODE:".$array['RESULT_CODE']."<br>";
             echo $array['RETURN_MSG']."<br>";
             echo "錯誤程式碼:".$array['ERR_CODE']."<br>";
             echo "錯誤程式碼描述:".$array['ERR_CODE_DES']."<br>";
             exitecho $array['RETURN_MSG'];exit;
         }
     }
     
     /* php獲取隨機字元(數字+英文)函式,長度可以進行控制 */
     function randomkeys($length) {
         $key = null;
         $pattern = '1234567890abcdefghijklmnopqrstuvwxyz
                   ABCDEFGHIJKLOMNOPQRSTUVWXYZ';
         for ($i = 0; $i < $length; $i++) {
             $key .= $pattern {mt_rand(0, 35)};
         }
         return $key;
     }
     /**
      * 生成簽名, $KEY就是支付key
      * @return 簽名
      */
     function MakeSign( $params,$KEY){
         //簽名步驟一:按字典序排序陣列引數
         ksort($params);
         $string = ToUrlParams($params);  //引數進行拼接key=value&k=v
         //簽名步驟二:在string後加入KEY
         $string = $string . "&key=".$KEY;
         //簽名步驟三:MD5加密
         $string = md5($string);
         //簽名步驟四:所有字元轉為大寫
         $result = strtoupper($string);
         return $result;
     }
     /**
      * 將引數拼接為url: key=value&key=value
      * @param $params
      * @return string
      */
     function ToUrlParams( $params ){
         $string = '';
         if( !empty($params) ){
             $array = array();
             foreach( $params as $key => $value ){
                 $array[] = $key.'='.$value;
             }
             $string = implode("&",$array);
         }
         return $string;
     }
     //獲取xml裡面資料,轉換成array
     function xml2array($xml){
         $p = xml_parser_create();
         xml_parse_into_struct($p, $xml, $vals, $index);
         xml_parser_free($p);
         $data = "";
         foreach ($index as $key=>$value) {
             if($key == 'xml' || $key == 'XML') continue;
             $tag = $vals[$value[0]]['tag'];
             $value = $vals[$value[0]]['value'];
             $data[$tag] = $value;
         }
         return $data;
     }
    function curl_post_ssl($url, $vars, $second=30,$aHeader=array())
     {
         $ch = curl_init();
         //超時時間
         curl_setopt($ch,CURLOPT_TIMEOUT,$second);
         curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1);
         //這裡設定代理,如果有的話
         //curl_setopt($ch,CURLOPT_PROXY, '10.206.30.98');
         //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);
     
         //以下兩種方式需選擇一種
     
         //第一種方法,cert 與 key 分別屬於兩個.pem檔案
         //預設格式為PEM,可以註釋
         curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
         curl_setopt($ch,CURLOPT_SSLCERT,getcwd().'/cert/apiclient_cert.pem');//getcwd()=》當前工作目錄,不含最下級的/
         //預設格式為PEM,可以註釋
         curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
         curl_setopt($ch,CURLOPT_SSLKEY,getcwd().'/cert/apiclient_key.pem');
     
         //第二種方式,兩個檔案合成一個.pem檔案
         //curl_setopt($ch,CURLOPT_SSLCERT,getcwd().'/all.pem');
     
         if( count($aHeader) >= 1 ){
             curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
         }
     
         curl_setopt($ch,CURLOPT_POST, 1);
         curl_setopt($ch,CURLOPT_POSTFIELDS,$vars);
         $data = curl_exec($ch); //返回xml
         if($data){
             curl_close($ch);
             return $data;
         }
         else {
             $error = curl_errno($ch);
             echo "call faild, errorCode:$error\n";
             curl_close($ch);
             return false;
         }
     }
    
?>

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <title>微信安全支付</title>
</head>
<body>
    </br></br></br></br>
    <div align="center">
        <form  action="./refund_miniprogram.php" method="post">
            <p>申請退款:</p>
            <p>退款單號: <input type="text" name="out_trade_no" value=<?php echo $out_trade_no; ?> ></p>
            <p>退款金額(分): <input type="text" name="refund_fee" value=<?php echo $refund_fee; ?> ></p>
            <button type="submit" >提交</button>
        </form>
        
        </br>
        <a href="../index.php">返回首頁</a>

    </div>
</body>
</html>    

 

 

DEMO2、在其他的php檔案中引用該退款流程

//在這裡將訂單的id、out_trade_no、out_refund_no、總金額、退款金額都拿到,然後進行下面步驟
include_once
S_ROOT.'xxpay/wxzf/refund_miniprogram.php'; //小程式退款
<?php
/**
 * 退款申請介面-demo
 * ====================================================
 * 注意:同一筆單的部分退款需要設定相同的訂單號和不同的
 * out_refund_no。一筆退款失敗後重新提交,要採用原來的
 * out_refund_no。總退款金額不能超過使用者實際支付金額(現
 * 金券金額不能退款)。
*/
         
         $appid =        'wx6***1d';//如果是公眾號 就是公眾號的appid;小程式就是小程式的appid
         $mch_id =       '12*****201';
         $KEY = 'xi***********kj';
         $nonce_str =    randomkeys(32);//隨機字串
         $op_user_id = $mch_id;
         $out_trade_no = $order_no['out_trade_no'];//商戶傳微信的訂單號
         $out_refund_no = $order_no['out_trade_no'];//使用者請求退款id對應的out_trade_no訂單號
//          $refund_fee = 1;//退款金額
//          $total_fee = 2;//訂單總金額
         $refund_fee = intval($refund_amount*100);//退款金額
         $total_fee = intval($total_amount*100);//訂單總金額
         
         //這裡是按照順序的 因為下面的簽名是按照(字典序)順序 排序錯誤 肯定出錯
         $post['appid'] = $appid;
         $post['mch_id'] = $mch_id;
         $post['nonce_str'] = $nonce_str;//隨機字串
         $post['op_user_id'] = $mch_id;
         $post['out_refund_no'] = $out_refund_no;
         $post['out_trade_no'] = $out_trade_no;
         $post['refund_fee'] = $refund_fee;
         $post['total_fee'] = $total_fee;        //總金額 最低為一分錢 必須是整數
         
         $sign = MakeSign($post,$KEY);              //簽名
         
         $post_xml = '<xml>
                       <appid>'.$appid.'</appid>
                       <mch_id>'.$mch_id.'</mch_id>
                       <nonce_str>'.$nonce_str.'</nonce_str>
                       <op_user_id>'.$mch_id.'</op_user_id>
                       <out_refund_no>'.$out_refund_no.'</out_refund_no>
                       <out_trade_no>'.$out_trade_no.'</out_trade_no>
                       <refund_fee>'.$refund_fee.'</refund_fee>
                       <total_fee>'.$total_fee.'</total_fee>
                       <sign>'.$sign.'</sign>
                    </xml>';
         //echo $post_xml;
         //申請退款介面
         $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
         $xml = curl_post_ssl($url,$post_xml);     //POST方式請求http,支付無需證照;申請退款api需要證照校驗
         $array = xml2array($xml);               //將【申請退款】api返回xml資料轉換成陣列,全要大寫
         //var_dump($array);
         if($array['RETURN_CODE'] == 'SUCCESS' && $array['RESULT_CODE'] == 'SUCCESS'){//退款業務已受理//
            //資料庫更新操作
    
             /* for 除錯  */
//              echo "業務結果:".$array['RETURN_CODE']."<br>";
//              echo "錯誤程式碼:".$array['err_code']."<br>";
//              echo "錯誤程式碼描述:".$array['err_code_des']."<br>";
//              echo "公眾賬號ID:".$array['APPID']."<br>";
//              echo "商戶號:".$array['MCH_ID']."<br>";
//              echo "子商戶號:".$array['sub_mch_id']."<br>";
//              echo "裝置號:".$array['device_info']."<br>";
//              echo "簽名:".$array['SIGN']."<br>";
//              echo "微信訂單號:".$array['transaction_id']."<br>";
//              echo "商戶訂單號:".$array['OUT_TRADE_NO']."<br>";
//              echo "商戶退款單號:".$array['OUT_REFUND_NO']."<br>";
//              echo "微信退款單號:".$array['refund_idrefund_id']."<br>";
//              echo "訂單總金額:".$array['TOTAL_FEE']."<br>";
//              echo "退款金額:".$array['REFUND_FEE']."<br>";
//              echo "現金券退款金額:".$array['coupon_refund_fee']."<br>";
         
         }else{
             echo "RETURN_CODE:".$array['RETURN_CODE']."<br>";
             echo "RESULT_CODE:".$array['RESULT_CODE']."<br>";
             echo $array['RETURN_MSG']."<br>";
             echo "錯誤程式碼:".$array['ERR_CODE']."<br>";
             echo "錯誤程式碼描述:".$array['ERR_CODE_DES']."<br>";
             exit;
         }
//      }
     
     /* php獲取隨機字元(數字+英文)函式,長度可以進行控制 */
     function randomkeys($length) {
         $key = null;
         $pattern = '1234567890abcdefghijklmnopqrstuvwxyz
                   ABCDEFGHIJKLOMNOPQRSTUVWXYZ';
         for ($i = 0; $i < $length; $i++) {
             $key .= $pattern {mt_rand(0, 35)};
         }
         return $key;
     }
     /**
      * 生成簽名, $KEY就是支付key
      * @return 簽名
      */
     function MakeSign( $params,$KEY){
         //簽名步驟一:按字典序排序陣列引數
         ksort($params);
         $string = ToUrlParams($params);  //引數進行拼接key=value&k=v
         //簽名步驟二:在string後加入KEY
         $string = $string . "&key=".$KEY;
         //簽名步驟三:MD5加密
         $string = md5($string);
         //簽名步驟四:所有字元轉為大寫
         $result = strtoupper($string);
         return $result;
     }
     /**
      * 將引數拼接為url: key=value&key=value
      * @param $params
      * @return string
      */
     function ToUrlParams( $params ){
         $string = '';
         if( !empty($params) ){
             $array = array();
             foreach( $params as $key => $value ){
                 $array[] = $key.'='.$value;
             }
             $string = implode("&",$array);
         }
         return $string;
     }
     //獲取xml裡面資料,轉換成array,key鍵均大寫
     function xml2array($xml){
         $p = xml_parser_create();
         xml_parse_into_struct($p, $xml, $vals, $index);
         xml_parser_free($p);
         $data = "";
         foreach ($index as $key=>$value) {
             if($key == 'xml' || $key == 'XML') continue;
             $tag = $vals[$value[0]]['tag'];
             $value = $vals[$value[0]]['value'];
             $data[$tag] = $value;
         }
         return $data;
     }
    function curl_post_ssl($url, $vars, $second=30,$aHeader=array())
     {
         $ch = curl_init();
         //超時時間
         curl_setopt($ch,CURLOPT_TIMEOUT,$second);
         curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1);
         //這裡設定代理,如果有的話
         //curl_setopt($ch,CURLOPT_PROXY, '10.206.30.98');
         //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);
     
         //以下兩種方式需選擇一種
     
         //第一種方法,cert 與 key 分別屬於兩個.pem檔案
         //預設格式為PEM,可以註釋
         curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
         //curl_setopt($ch,CURLOPT_SSLCERT,getcwd().'/cert/apiclient_cert.pem');//getcwd()=》當前工作目錄,不含最下級的/
         curl_setopt($ch,CURLOPT_SSLCERT,S_ROOT.'x**ay/wxzf/cert/apiclient_cert.pem');//這種獲取絕對路徑,請使用檔案根目錄E:\putuu\WWW\你的專案放置資料夾名\,而非站點目錄
         //預設格式為PEM,可以註釋
         curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
         //curl_setopt($ch,CURLOPT_SSLKEY,getcwd().'/cert/apiclient_key.pem');
         curl_setopt($ch,CURLOPT_SSLKEY,S_ROOT.'x**ay/wxzf/cert/apiclient_key.pem');
     
         //第二種方式,兩個檔案合成一個.pem檔案
         //curl_setopt($ch,CURLOPT_SSLCERT,getcwd().'/all.pem');
     
         if( count($aHeader) >= 1 ){
             curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
         }
     
         curl_setopt($ch,CURLOPT_POST, 1);
         curl_setopt($ch,CURLOPT_POSTFIELDS,$vars);
         $data = curl_exec($ch); //返回xml
         if($data){
             curl_close($ch);
             return $data;
         }
         else {
             $error = curl_errno($ch);
             echo "call faild, errorCode:$error\n";
             curl_close($ch);
             return false;
         }
     }
    
?>

退款部分結束。

下面是退款回撥

DEMO3、退款回撥

1)首先去:商戶平臺-交易中心-退款配置中配置notify_url。(和支付回撥類似)

2)解密步驟:  

3)上面的加密串A是什麼?回答:XML裡面的<req_info>欄位。

 4)如何獲取?==》核心:  refund_decrypt($str, $key)    :其中引數$str是加密串A<req_info>(string型別),引數$key是md5後的商戶key。

//明文=refund_decrypt(密文,MD5(商戶祕鑰));明文格式如下:

 

 5)那麼獲得xml格式明文後,咋辦?再次xml轉array,得到裡面的out_trade_no,訂單號都得到了,你想幹嘛幹嘛去。至此,整個退款和回撥結束。

下面附上程式碼:

<?php 
//define ( "IN_KEKE", TRUE );
//require_once (dirname ( dirname ( dirname ( __FILE__ ) ) ) . DIRECTORY_SEPARATOR . 'app_comm.php');//引入資料庫db工廠和其他配置

//申請退款,微信公眾號和小程式退款成功的回撥url是同一個
//舉例如下:
// <xml>
// <return_code>SUCCESS</return_code>
// <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
// <mch_id><![CDATA[10000100]]></mch_id>
// <nonce_str><![CDATA[TeqClE3i0mvn3DrK]]></nonce_str>
// <req_info><![CDATA[T87GAHG17TGAHG1TGHAHAHA1Y1CIOA9UGJH1GAHV871HAGAGQYQQPOOJMXNBCXBVNMNMAJAA]]></req_info>
// </xml>
//解密步驟如下: 
// (1)對加密串A<req_info>做base64解碼,得到加密串B
// (2)對商戶key做md5,得到32位小寫key* ( key設定路徑:微信商戶平臺(pay.weixin.qq.com)-->賬戶設定-->API安全-->金鑰設定 )
// (3)用key*對加密串B做AES-256-ECB解密

//這裡測試微信是否訪問了當前url
//$li = db_factory::execute ( sprintf ( " update %switkey_ho*** set order_status='refunded' where out_trade_no= '%s' ",TABLEPRE,'ho*****2') );
//var_dump($li);

///////////////////////////////////////////////////-------//////////////////////////////////
//商戶金鑰$weixin_key,解密必要,按照上面步驟(2)(3)解密
$weixin_key = '**********************';

$post = post_data();    //接受微信POST過來的XML資料
$post_data = xml_to_array($post);   //XML轉陣列Array
$weixin_post_string = $post_data['req_info'];   //微信post過來的加密串A,字串型別

//明文=refund_decrypt(密文,MD5(商戶祕鑰));返回XML格式明文,包含out_trade_no/out_refund_no等資訊
$refund_xml_string = refund_decrypt($weixin_post_string, md5($weixin_key));
$refundArr = xml_to_array($refund_xml_string);
if(isset($refundArr['out_trade_no'])){//$refundArr['out_trade_no']字串型別
    //資料庫操作,將訂單狀態改為退款成功和其他操作

echo return_msg(); } //對引數加密串A<req_info>進行AES-256(ECB模式,PKCS7Padding)解密,得到加密前引數。 function refund_decrypt($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); } /* * 微信是用$GLOBALS['HTTP_RAW_POST_DATA'];這個函式接收微信支付成功post給商戶的資料 */ function post_data(){ $receipt = $_REQUEST; if($receipt==null){ $receipt = file_get_contents("php://input"); if($receipt == null){ $receipt = $GLOBALS['HTTP_RAW_POST_DATA']; } } return $receipt; } /** * 將xml轉為array */ function xml_to_array($xml){ if(!$xml){ return false; } //將XML轉為array //禁止引用外部xml實體 libxml_disable_entity_loader(true); $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $data; } /* 傳送給微信的退款回撥訊息 -xzz1207 */ function return_msg(){ $msg = '<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>'; return $msg; } ?>

 

相關文章