zoukankan      html  css  js  c++  java
  • 【微信小程序】小程序和公众号 退款功能教程(含申请退款和退款回调,退款回调地址在商户后台配置或者代码自定义)

    1、一定要区分小程序和公众号的退款,唯一的区别就是 appid不一样,其他的都是一样的。

    不废话,直接写代码了啊。 放大招!!!

    然后,需要注意的:最好是把证书放在下面的php的同级或者下级。

    证书的路径一定要是服务器的根路径,比如E: upuuWWWXXX。而像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 HomeModelHomeorderModel('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
    ";
                 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:putuuWWW你的项目放置文件夹名,而非站点目录
             //默认格式为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
    ";
                 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; } ?>
  • 相关阅读:
    关于Certificate、Provisioning Profile
    苹果开发者账号类型
    在 iTunes Connect 中,无法找到“My Apps”选项
    iOS 开发,相关网址
    dart 使用
    initState 必须调用 super.initState(); 否则报错
    TabBar 设置可滚动:isScrollable: true
    flutter 从创建到渲染的大体流程
    获取对象State的方法
    beforeRouteEnter 与 beforeRouteUpdate(watch $route 对象) 的区别
  • 原文地址:https://www.cnblogs.com/xuzhengzong/p/7994090.html
Copyright © 2011-2022 走看看