微信小程序—微信自动退款

一、业务背景

微信自动退款串接基于酷客多小程序商城系统,为方便财务人员进行订单退款而开发,将酷客多小程序系统财务退款流程和微信退款系统打通。实现一个系统管理运营。

二、业务流程设计

1.退款单状态:待退款、退款中、退款完成、自动退款失败等

2.由于微信申请退款接口接受请求后不会立即进行退款处理,微信此处有延迟,因此在实际业务串接中,不能依据申请退款接口调用是否成功来修改业务系统中退款单的状态;必须以微信退款通知的状态或者自行调用查看微信退款状态接口的状态为准

微信退款串接流程图,见下图

三、微信后台配置

1.申请退款接口调用需要微信证书;因此需要先到微信商户平台——账户中心——API安全中下载证书;

此证书是用于调用申请退款接口时使用;需要先安装到系统中

2.到微信商户平台开启退款通知配置;并配置退款通知接口地址;此地址用于接受退款结果通知的

四、微信申请退款接口串接常见问题

1.参数错误问题,接口要求商户订单号、退款单号、退款金额、订单金额为业务要求必传字段;

a)其中商户订单号为你要退的订单支付时传入的订单号

b)退款单号为当前微信商户号下唯一编号,按照微信官方文档生成规则即可

c)指当前订单多次退款金额合计不得超过订单金额

2.申请退款接口调用前;安装微信API证书,调用时需带着证书请求

3.接受不到微信退款通知信息问题

a)检查是否在微信商户平台配置开启退款通知,并配置通知路径

b)检查接受通知接口是否正式访问

c)通知接口必须是部署在服务器,且外网正常访问得

4.接受微信退款结果通知接口;无法解密参数问题

微信公布解密步骤如下: 

(1)对加密串A做base64解码,得到加密串B

(2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )

(3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)

实际串接时注意

a) 微信通知信息数据格式采用xml;从xml节点中取出加密数据时 ,第一步用base64解码步骤省略,因为从xml节点取出得数据已是base64解码过的字符串了

b)商户key做md5加密;此处注意商户key指得再微信商户平台配置得32商户密钥;md5加密也一定注意是32位小写;

五、关键代码块

1.微信官方未提供得代码; 解密微信退款通知信息方法

/// <summary>
        /// 解密微信支付退款结果通知
        /// </summary>
        /// <param name="s">要解密字符串</param>
        /// <param name="shkey">商户key</param>
        /// <returns></returns>
        public static string DecodeReqInfo(string s, string
shkey)
        {
            string result = null;
            string key =
System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(shkey,
"md5").ToLower();   //32位小写md5加密
            result  =
DecodeAES256ECB(s, key);
            return result ;
        }

/// <summary>
        /// AES-256-ECB字符解密
        /// </summary>
        /// <param name="s">要解密字符串</param>
        /// <param name="key">密钥</param>
        /// <returns></returns>
        public static string DecodeAES256ECB(string s,
string key)
        {
            string result  = null;
            try
            {
                byte[] keyArray =
UTF8Encoding.UTF8.GetBytes(key);
                byte[] toEncryptArray =
Convert.FromBase64String(s);
                RijndaelManaged rDel =
new RijndaelManaged();
                rDel.Key = keyArray;
                rDel.Mode =
CipherMode.ECB;
                rDel.Padding =
PaddingMode.PKCS7;
                ICryptoTransform
cTransform = rDel.CreateDecryptor();
                byte[] resultArray =
cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
             
  result  = UTF8Encoding.UTF8.GetString(resultArray);
            }
            catch { }
            return result ;
        }

2.微信退款接口串接代码段, 此代码是按照微信官方提供demo修改使用;

/// <summary>
        /// 微信退款接口
        /// </summary>
        /// <param
name="transaction_id"></param>
        /// <param name="out_trade_no">订单号</param>
        /// <param name="out_refund_no">退款单号</param>
        /// <param name="total_fee">总金额</param>
        /// <param name="refund_fee">退款金额</param>
        /// <param name="wxPayConfig">微信配置参数类</param>
        /// <returns></returns>
        public static string Run(string transaction_id,
string out_trade_no, string out_refund_no, string total_fee, string refund_fee,
WxPayConfig wxPayConfig)
        {
            Log.Info("Refund",
"Refund is processing...");

WxPayData data = new WxPayData();
            if
(!string.IsNullOrEmpty(transaction_id))//微信订单号存在的条件下,则已微信订单号为准
            {
               
data.SetValue("transaction_id", transaction_id);
            }
            else//微信订单号不存在,才根据商户订单号去退款
            {
               
data.SetValue("out_trade_no", out_trade_no);
            }

data.SetValue("total_fee",
total_fee);//订单总金额
            data.SetValue("refund_fee",
refund_fee);//退款金额
           
data.SetValue("out_refund_no", out_refund_no);//随机生成商户退款单号
            data.SetValue("op_user_id",
wxPayConfig.MCHID);//操作员,默认为商户号

WxPayData result = Refund(data,
wxPayConfig);//提交退款申请给API,接收返回数据

Log.Info("Refund",
"Refund process complete, result : " + result.ToXml());
            return result.ToPrintStr();
        }

/**
        * 
        * 申请退款
        * @param WxPayData inputObj 提交给申请退款API的参数
        * @param int timeOut 超时时间
        * @throws WxPayException
        * @return 成功时返回接口调用结果,其他抛异常
        */
        public static WxPayData Refund(WxPayData inputObj,WxPayConfig
wxConfig ,int timeOut = 6)
        {
            string url =
"https://api.mch.weixin.qq.com/secapi/pay/refund";
            //检测必填参数
            if
(!inputObj.IsSet("out_trade_no") &&
!inputObj.IsSet("transaction_id"))
            {
                throw new
WxPayException("退款申请接口中,out_trade_no、transaction_id至少填一个!");
            }
            else if
(!inputObj.IsSet("out_refund_no"))
            {
                throw new
WxPayException("退款申请接口中,缺少必填参数out_refund_no!");
            }
            else if
(!inputObj.IsSet("total_fee"))
            {
                throw new
WxPayException("退款申请接口中,缺少必填参数total_fee!");
            }
            else if
(!inputObj.IsSet("refund_fee"))
            {
                throw new
WxPayException("退款申请接口中,缺少必填参数refund_fee!");
            }
            else if
(!inputObj.IsSet("op_user_id"))
            {
                throw new
WxPayException("退款申请接口中,缺少必填参数op_user_id!");
            }

inputObj.SetValue("appid",
wxConfig.APPID);//公众账号ID
            inputObj.SetValue("mch_id",
wxConfig.MCHID);//商户号
           
inputObj.SetValue("nonce_str",
Guid.NewGuid().ToString().Replace("-", ""));//随机字符串
            inputObj.SetValue("sign",
inputObj.MakeSign(wxConfig));//签名
            
            string xml = inputObj.ToXml();
            var start = DateTime.Now;

Log.Debug("WxPayApi",
"Refund request : " + xml);
            string response =
HttpService.Post(xml, url, true, timeOut, wxConfig);//调用HTTP通信接口提交数据到API
            Log.Debug("WxPayApi",
"Refund response : " + response);

var end = DateTime.Now;
            int timeCost = (int)((end -
start).TotalMilliseconds);//获得接口耗时

//将xml格式的结果转换为对象以返回
            WxPayData result = new WxPayData();
            result.FromXml(response, wxConfig);

//ReportCostTime(url, timeCost,
result, wxConfig);//测速上报

return result;

}

/// <summary>
    /// 微信支付协议接口数据类,所有的API接口通信都依赖这个数据结构,
    /// 在调用接口之前先填充各个字段的值,然后进行接口通信,
    /// 这样设计的好处是可扩展性强,用户可随意对协议进行更改而不用重新设计数据结构,
    /// 还可以随意组合出不同的协议数据包,不用为每个协议设计一个数据包结构
    /// </summary>
    public class WxPayData
    {
        public WxPayData()
        {

}
        //采用排序的Dictionary的好处是方便对数据包进行签名,不用再签名之前再做一次排序
        private SortedDictionary<string, object>
m_values = new SortedDictionary<string, object>();

/**
        * 设置某个字段的值
        * @param key 字段名
         * @param value 字段值
        */
        public void SetValue(string key, object value)
        {
            m_values[key] = value;
        }

/**
        * 根据字段名获取某个字段的值
        * @param key 字段名
         * @return key对应的字段值
        */
        public object GetValue(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            return o;
        }

/**
         * 判断某个字段是否已设置
         * @param key 字段名
         * @return 若字段key已被设置,则返回true,否则返回false
         */
        public bool IsSet(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            if (null != o)
                return true;
            else
                return false;
        }

/**
        * @将Dictionary转成xml
        * @return 经转换得到的xml串
        * @throws WxPayException
        **/
        public string ToXml()
        {
            //数据为空时不能转化为xml格式
            if (0 == m_values.Count)
            {
               
Log.Error(this.GetType().ToString(), "WxPayData数据为空!");
                throw new
WxPayException("WxPayData数据为空!");
            }

string xml = "<xml>";
            foreach (KeyValuePair<string,
object> pair in m_values)
            {
                //字段值不能为null,会影响后续流程
                if (pair.Value == null)
                {
                   
Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
                    throw new
WxPayException("WxPayData内部含有值为null的字段!");
                }

if
(pair.Value.GetType() == typeof(int))
                {
                    xml +=
"<" + pair.Key + ">" + pair.Value + "</"
+ pair.Key + ">";
                }
                else if
(pair.Value.GetType() == typeof(string))
                {
                    xml +=
"<" + pair.Key + ">" + "<![CDATA[" +
pair.Value + "]]></" + pair.Key + ">";
                }
                else//除了string和int类型不能含有其他数据类型
                {
                   
Log.Error(this.GetType().ToString(), "WxPayData字段数据类型错误!");
                    throw new
WxPayException("WxPayData字段数据类型错误!");
                }
            }
            xml += "</xml>";
            return xml;
        }

/**
        * @将xml转为WxPayData对象并返回对象内部的数据
        * @param string 待转换的xml串
        * @return 经转换得到的Dictionary
        * @throws WxPayException
        */
        public SortedDictionary<string, object>
FromXml(string xml, WxPayConfig wxConfig)
        {
            if (string.IsNullOrEmpty(xml))
            {
               
Log.Error(this.GetType().ToString(), "将空的xml串转换为WxPayData不合法!");
                throw new
WxPayException("将空的xml串转换为WxPayData不合法!");
            }

XmlDocument xmlDoc = new
XmlDocument();
            xmlDoc.LoadXml(xml);
            XmlNode xmlNode =
xmlDoc.FirstChild;//获取到根节点<xml>
            XmlNodeList nodes =
xmlNode.ChildNodes;
            foreach (XmlNode xn in nodes)
            {
                XmlElement xe =
(XmlElement)xn;
                m_values[xe.Name] =
xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
            }
                
            try
            {
                       //2015-06-29 错误是没有签名
                       if(m_values["return_code"]
!= "SUCCESS")
                       {
                             return
m_values;
                       }
                CheckSign(wxConfig);//验证签名,不通过会抛异常
            }
            catch(WxPayException ex)
            {
                throw new
WxPayException(ex.Message);
            }

return m_values;
        }

/**
        * @Dictionary格式转化成url参数格式
        * @ return url格式串, 该串不包含sign字段值
        */
        public string ToUrl()
        {
            string buff = "";
            foreach (KeyValuePair<string,
object> pair in m_values)
            {
                if (pair.Value == null)
                {
                   
Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
                    throw new
WxPayException("WxPayData内部含有值为null的字段!");
                }

if (pair.Key !=
"sign" && pair.Value.ToString() != "")
                {
                    buff +=
pair.Key + "=" + pair.Value + "&";
                }
            }
            buff = buff.Trim('&');
            return buff;
        }

/**
        * @Dictionary格式化成Json
         * @return json串数据
        */
        public string ToJson()
        {
            string jsonStr =
JsonMapper.ToJson(m_values);
            return jsonStr;
        }

/**
        * @values格式化成能在Web页面上显示的结果(因为web页面上不能直接输出xml格式的字符串)
        */
        public string ToPrintStr()
        {
            string str = "";
            foreach (KeyValuePair<string,
object> pair in m_values)
            {
                if (pair.Value == null)
                {
                   
Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
                    throw new
WxPayException("WxPayData内部含有值为null的字段!");
                }

str +=
string.Format("{0}={1}<br>", pair.Key, pair.Value.ToString());
            }
            Log.Debug(this.GetType().ToString(),
"Print in Web Page : " + str);
            return str;
        }

/**
        * @生成签名,详见签名生成算法
        * @return 签名, sign字段不参加签名
        */
        public string MakeSign(WxPayConfig wxConfig)
        {
            //转url格式
            string str = ToUrl();
            //在string后加入API KEY
            str += "&key=" +
wxConfig.KEY;
            //MD5加密
            var md5 = MD5.Create();
            var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
            var sb = new StringBuilder();
            foreach (byte b in bs)
            {
               
sb.Append(b.ToString("x2"));
            }
            //所有字符转为大写
            return sb.ToString().ToUpper();
        }

/**
        * 
        * 检测签名是否正确
        * 正确返回true,错误抛异常
        */
        public bool CheckSign(WxPayConfig wxConfig)
        {
            //如果没有设置签名,则跳过检测
            if (!IsSet("sign"))
            {
             
 Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
               throw new
WxPayException("WxPayData签名存在但不合法!");
            }
            //如果设置了签名但是签名为空,则抛异常
            else if(GetValue("sign") ==
null || GetValue("sign").ToString() == "")
            {
                Log.Error(this.GetType().ToString(),
"WxPayData签名存在但不合法!");
                throw new
WxPayException("WxPayData签名存在但不合法!");
            }

//获取接收到的签名
            string return_sign =
GetValue("sign").ToString();

//在本地计算新的签名
            string cal_sign = MakeSign(wxConfig);

if (cal_sign == return_sign)
            {
                return true;
            }

Log.Error(this.GetType().ToString(),
"WxPayData签名验证错误!");
            throw new WxPayException("WxPayData签名验证错误!");
        }

/**
        * @获取Dictionary
        */
        public SortedDictionary<string, object>
GetValues()
        {
            return m_values;
        }

}

///
<summary>
    /// 微信支付协议接口数据类,所有的API接口通信都依赖这个数据结构,
    /// 在调用接口之前先填充各个字段的值,然后进行接口通信,
    /// 这样设计的好处是可扩展性强,用户可随意对协议进行更改而不用重新设计数据结构,
    /// 还可以随意组合出不同的协议数据包,不用为每个协议设计一个数据包结构
    /// </summary>
    public class WxPayData
    {
        public WxPayData()
        {

}

//采用排序的Dictionary的好处是方便对数据包进行签名,不用再签名之前再做一次排序
        private SortedDictionary<string, object>
m_values = new SortedDictionary<string, object>();

/**
        * 设置某个字段的值
        * @param key 字段名
         * @param value 字段值
        */
        public void SetValue(string key, object value)
        {
            m_values[key] = value;
        }

/**
        * 根据字段名获取某个字段的值
        * @param key 字段名
         * @return key对应的字段值
        */
        public object GetValue(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            return o;
        }

/**
         * 判断某个字段是否已设置
         * @param key 字段名
         * @return 若字段key已被设置,则返回true,否则返回false
         */
        public bool IsSet(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            if (null != o)
                return true;
            else
                return false;
        }

/**
        * @将Dictionary转成xml
        * @return 经转换得到的xml串
        * @throws WxPayException
        **/
        public string ToXml()
        {
            //数据为空时不能转化为xml格式
            if (0 == m_values.Count)
            {
               
Log.Error(this.GetType().ToString(), "WxPayData数据为空!");
                throw new WxPayException("WxPayData数据为空!");
            }

string xml = "<xml>";
            foreach (KeyValuePair<string,
object> pair in m_values)
            {
                //字段值不能为null,会影响后续流程
                if (pair.Value == null)
                {
                   
Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
                    throw new
WxPayException("WxPayData内部含有值为null的字段!");
                }

if
(pair.Value.GetType() == typeof(int))
                {
                    xml +=
"<" + pair.Key + ">" + pair.Value + "</"
+ pair.Key + ">";
                }
                else if
(pair.Value.GetType() == typeof(string))
                {
                    xml +=
"<" + pair.Key + ">" + "<![CDATA[" +
pair.Value + "]]></" + pair.Key + ">";
                }
                else//除了string和int类型不能含有其他数据类型
                {
                   
Log.Error(this.GetType().ToString(), "WxPayData字段数据类型错误!");
                    throw new
WxPayException("WxPayData字段数据类型错误!");
                }
            }
            xml += "</xml>";
            return xml;
        }

/**
        * @将xml转为WxPayData对象并返回对象内部的数据
        * @param string 待转换的xml串
        * @return 经转换得到的Dictionary
        * @throws WxPayException
        */
        public SortedDictionary<string, object>
FromXml(string xml, WxPayConfig wxConfig)
        {
            if (string.IsNullOrEmpty(xml))
            {
               
Log.Error(this.GetType().ToString(), "将空的xml串转换为WxPayData不合法!");
                throw new
WxPayException("将空的xml串转换为WxPayData不合法!");
            }

XmlDocument xmlDoc = new
XmlDocument();
            xmlDoc.LoadXml(xml);
            XmlNode xmlNode =
xmlDoc.FirstChild;//获取到根节点<xml>
            XmlNodeList nodes =
xmlNode.ChildNodes;
            foreach (XmlNode xn in nodes)
            {
                XmlElement xe =
(XmlElement)xn;
                m_values[xe.Name] =
xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
            }
                
            try
            {
                       //2015-06-29 错误是没有签名
                       if(m_values["return_code"]
!= "SUCCESS")
                       {
                             return
m_values;
                       }
                CheckSign(wxConfig);//验证签名,不通过会抛异常
            }
            catch(WxPayException ex)
            {
                throw new
WxPayException(ex.Message);
            }

return m_values;
        }

/**
        * @Dictionary格式转化成url参数格式
        * @ return url格式串, 该串不包含sign字段值
        */
        public string ToUrl()
        {
            string buff = "";
            foreach (KeyValuePair<string,
object> pair in m_values)
            {
                if (pair.Value == null)
                {
                   
Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
                    throw new
WxPayException("WxPayData内部含有值为null的字段!");
                }

if (pair.Key !=
"sign" && pair.Value.ToString() != "")
                {
                    buff +=
pair.Key + "=" + pair.Value + "&";
                }
            }
            buff = buff.Trim('&');
            return buff;
        }

/**
        * @Dictionary格式化成Json
         * @return json串数据
        */
        public string ToJson()
        {
            string jsonStr =
JsonMapper.ToJson(m_values);
            return jsonStr;
        }

/**
        * @values格式化成能在Web页面上显示的结果(因为web页面上不能直接输出xml格式的字符串)
        */
        public string ToPrintStr()
        {
            string str = "";
            foreach (KeyValuePair<string,
object> pair in m_values)
            {
                if (pair.Value == null)
                {
                   
Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
                    throw new
WxPayException("WxPayData内部含有值为null的字段!");
                }

str += string.Format("{0}={1}<br>",
pair.Key, pair.Value.ToString());
            }
            Log.Debug(this.GetType().ToString(),
"Print in Web Page : " + str);
            return str;
        }

/**
        * @生成签名,详见签名生成算法
        * @return 签名, sign字段不参加签名
        */
        public string MakeSign(WxPayConfig wxConfig)
        {
            //转url格式
            string str = ToUrl();
            //在string后加入API KEY
            str += "&key=" +
wxConfig.KEY;
            //MD5加密
            var md5 = MD5.Create();
            var bs =
md5.ComputeHash(Encoding.UTF8.GetBytes(str));
            var sb = new StringBuilder();
            foreach (byte b in bs)
            {
               
sb.Append(b.ToString("x2"));
            }
            //所有字符转为大写
            return sb.ToString().ToUpper();
        }

/**
        * 
        * 检测签名是否正确
        * 正确返回true,错误抛异常
        */
        public bool CheckSign(WxPayConfig wxConfig)
        {
            //如果没有设置签名,则跳过检测
            if (!IsSet("sign"))
            {
             
 Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
               throw new
WxPayException("WxPayData签名存在但不合法!");
            }
            //如果设置了签名但是签名为空,则抛异常
            else if(GetValue("sign") ==
null || GetValue("sign").ToString() == "")
            {
               
Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
                throw new
WxPayException("WxPayData签名存在但不合法!");
            }

//获取接收到的签名
            string return_sign =
GetValue("sign").ToString();

//在本地计算新的签名
            string cal_sign = MakeSign(wxConfig);

if (cal_sign == return_sign)
            {
                return true;
            }

Log.Error(this.GetType().ToString(),
"WxPayData签名验证错误!");
            throw new
WxPayException("WxPayData签名验证错误!");
        }

/**
        * @获取Dictionary
        */
        public SortedDictionary<string, object>
GetValues()
        {
            return m_values;
        }
    }

/***
        * 退款查询完整业务流程逻辑
        * @param refund_id 微信退款单号(优先使用)
        * @param out_refund_no 商户退款单号
        * @param transaction_id 微信订单号
        * @param out_trade_no 商户订单号
        * @return 退款查询结果(xml格式)
        */
        public static string Run(string refund_id, string
out_refund_no, string transaction_id, string out_trade_no, WxPayConfig
wxConfig)
        {
            Log.Info("RefundQuery",
"RefundQuery is processing...");

WxPayData data = new WxPayData();
            if(!string.IsNullOrEmpty(refund_id))
            {
               
data.SetValue("refund_id", refund_id);//微信退款单号,优先级最高
            }
            else
if(!string.IsNullOrEmpty(out_refund_no))
            {
               
data.SetValue("out_refund_no", out_refund_no);//商户退款单号,优先级第二
            }
            else
if(!string.IsNullOrEmpty(transaction_id))
            {
               
data.SetValue("transaction_id", transaction_id);//微信订单号,优先级第三
            }
            else
            {
               
data.SetValue("out_trade_no", out_trade_no);//商户订单号,优先级最低
            }

WxPayData result = RefundQuery(data,
wxConfig);//提交退款查询给API,接收返回数据

Log.Info("RefundQuery",
"RefundQuery process complete, result : " + result.ToXml());
            return result.ToPrintStr();

}

/**
          * 
          * 查询退款
          * 提交退款申请后,通过该接口查询退款状态。退款有一定延时,
          * 用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。
          * out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个
          * @param WxPayData
inputObj 提交给查询退款API的参数
          * @param int timeOut 接口超时时间
          * @throws
WxPayException
          * @return 成功时返回,其他抛异常
          */
          public static
WxPayData RefundQuery(WxPayData inputObj,WxPayConfig wxConfig, int timeOut = 6)
          {
                string url =
"https://api.mch.weixin.qq.com/pay/refundquery";
                //检测必填参数
               
if(!inputObj.IsSet("out_refund_no") &&
!inputObj.IsSet("out_trade_no") &&
                    
!inputObj.IsSet("transaction_id") &&
!inputObj.IsSet("refund_id"))
            {
                     throw new
WxPayException("退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个!");
                }

inputObj.SetValue("appid",
wxConfig.APPID);//公众账号ID
            inputObj.SetValue("mch_id",
wxConfig.MCHID);//商户号
               
inputObj.SetValue("nonce_str",GenerateNonceStr());//随机字符串
            inputObj.SetValue("sign",
inputObj.MakeSign(wxConfig));//签名

string xml =
inputObj.ToXml();
           
                var start =
DateTime.Now;//请求开始时间

Log.Debug("WxPayApi",
"RefundQuery request : " + xml);
            string response = HttpService.Post(xml,
url, false, timeOut, wxConfig);//调用HTTP通信接口以提交数据到API
            Log.Debug("WxPayApi",
"RefundQuery response : " + response);

var end = DateTime.Now;
            int timeCost =
(int)((end-start).TotalMilliseconds);//获得接口耗时

//将xml格式的结果转换为对象以返回
                WxPayData result
= new WxPayData();
            result.FromXml(response, wxConfig);

ReportCostTime(url, timeCost, result,
wxConfig);//测速上报
           
                return result;

}

微信小程序—微信自动退款的更多相关文章

  1. 微信小程序-微信自动退款(Java后台)

    微信小程序-微信自动退款 1.首先分享 微信自动退款接口: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4 微信付款 代码案例 ...

  2. 在微信小程序里自动获得当前手机所在的经纬度并转换成地址

    效果:我在手机上打开微信小程序,自动显示出我当前所在的地理位置: 具体步骤: 1. 使用微信jssdk提供的getLocation API拿到经纬度: 2. 调用高德地图的api使用经纬度去换取地址的 ...

  3. 微信小程序+微信管理后台+微信用户前台

    代码地址如下:http://www.demodashi.com/demo/15043.html #### 微信小程序+微信管理后台+微信用户前台 #### 产品介绍 基础功能开发:景区微信地图导游.天 ...

  4. 微信小程序支付及退款流程详解

    微信小程序的支付和退款流程 近期在做微信小程序时,涉及到了小程序的支付和退款流程,所以也大概的将这方面的东西看了一个遍,就在这篇博客里总结一下. 首先说明一下,微信小程序支付的主要逻辑集中在后端,前端 ...

  5. 微信小程序——微信支付

    这个讲起来也就比较麻烦一点,因为需要的不仅仅是咱们代码上的技术,嘿嘿! 先整理一下思路.如果想做微信支付: 1.现有一个公司账户(非个人账户),并且实名认证过的. 2.微信号 必须开通微信支付功能. ...

  6. [微信小程序] 微信小程序获取用户定位信息并加载对应城市信息,wx.getLocation,腾讯地图小程序api,微信小程序经纬度逆解析地理信息

    因为需要在小程序加个定位并加载对应城市信息 然而小程序自带api目前只能获取经纬度不能逆解析,虽然自己解析方式,但是同时也要调用地图,难道用户每次进小程序还要强行打开地图选择地址才定位吗?多麻烦也不利 ...

  7. [微信小程序] 微信小程序富文本-wxParse的使用

    最近小程序蛮火的,公司要做于是学了一点点小程序 不知道你们有没有遇到过这种问题: 从公司服务器获取的文章内容是有HTML标签格式的一段内容,但是微信是不支持这些标签的,怎么办呢? 1.一般网站后台的文 ...

  8. 微信小程序 微信支付

    微信小程序前端自处理: //时间戳 timeStamp() { return parseInt(new Date().getTime() / 1000) + '' }, //随机数 randomStr ...

  9. 微信小程序------微信支付模块

    最近项目涉及到小程序开发:需要进行微信支付模块,接下来通过叙述,记录一下微信小程序中微信支付模块的开发,以便日后翻阅和使用. 学习指南----------微信支付开发文档:https://pay.we ...

随机推荐

  1. oneNote总结

    22.添加附加文件删除后,文件大小没有发生改变的(优化文件和清空回收站)

  2. python+opencv2相机位姿估计

    最近在做基于图像的室内定位方面的研究,于是使用到了百度最新的室内数据库Image-based Localization (IBL) .由于该数据库给出的数据是每幅图像和其对应相机的内外参数和光心投影方 ...

  3. b继承a

    有下面这样的一段代码: function a(){ this.foo = function(){ console.log('foo'); } } var b = {}; 请问如何让b继承a? b.__ ...

  4. MySQL的字符编码设置

    -- 创建数据库时,设置数据库的编码方式 -- CHARACTER SET:指定数据库采用的字符集,utf8不能写成utf-8-- COLLATE:指定数据库字符集的排序规则,utf8的默认排序规则为 ...

  5. 在 Mac 中安装 MySQLdb (Python mysql )

    安装环境:OS X操作系统,Python 2.7.3. MySQLdb其实包含在MySQL-python包中,因此无论下载还是在pip中search,都应该是搜寻MySQL-python. 以下将说明 ...

  6. 在linux内核中修改TCP MSS值

    MTU: Maxitum Transmission Unit 最大传输单元 MSS: Maxitum Segment Size 最大分段大小 MSS最大传输大小的缩写,是TCP协议里面的一个概念.MS ...

  7. PyCharm安装Pygame方法

    最近在自学Python,然后终于到了项目实战的时候了,而且还是做一个游戏,热情直接被调动起来了,嘿嘿 首先要安装一个Pygame 环境 win7 先去 这里 下载对应Python的Pygame版本 如 ...

  8. springboot入门_helloworld

    开始学习springboot,在此做记录,有不正确之处,还望读者指正. springboot框架的设计目的是用来简化新Spring应用的初始环境搭建以及开发过程.主要体现有:1 xml配置文件,使用s ...

  9. Asp.Net Core 基于QuartzNet任务管理系统

    之前一直想搞个后台任务管理系统,零零散散的搞到现在,也算完成了. 这里发布出来,请园里的dalao批评指导! 废话不多说,进入正题. github地址:https://github.com/YANGK ...

  10. slf4j-logback 日志以json格式导入ELK

    同事整理的,在此分享.logback,log4j2 等slf4j的日志实现都可以以json格式输出日志, 这里采用的是logback.当然也可以以文本行的格式输出,然后在logstash里通过grok ...