微信小程序如何实现自动退款功能?
/// <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)
{
("Refund", "Refund is processing...");
WxPayData data = new WxPayData();
if (!string.IsNullOrEmpty(transaction_id))//微信订单号存在的条件下,则已微信订单号为准
{
("transaction_id", transaction_id);
}
else//微信订单号不存在,才根据商户订单号去退款
{
("out_trade_no", out_trade_no);
}
("total_fee", total_fee);//订单总金额
("refund_fee", refund_fee);//退款金额
("out_refund_no", out_refund_no);//随机生成商户退款单号
("op_user_id", );//操作员,默认为商户号
WxPayData result = Refund(data, wxPayConfig);//提交退款申请给API,接收返回数据
("Refund", "Refund process complete, result : " + ());
return ();
}
/**
*
* 申请退款
* @param WxPayData inputObj 提交给申请退款API的参数
* @param int timeOut 超时时间
* @throws WxPayException
* @return 成功时返回接口调用结果,其他抛异常
*/
public static WxPayData Refund(WxPayData inputObj,WxPayConfig wxConfig ,int timeOut = 6)
{
string url = "/secapi/pay/refund";
//检测必填参数
if (!("out_trade_no") && !("transaction_id"))
{
throw new WxPayException("退款申请接口中,out_trade_no、transaction_id至少填一个!");
}
else if (!("out_refund_no"))
{
throw new WxPayException("退款申请接口中,缺少必填参数out_refund_no!");
}
else if (!("total_fee"))
{
throw new WxPayException("退款申请接口中,缺少必填参数total_fee!");
}
else if (!("refund_fee"))
{
throw new WxPayException("退款申请接口中,缺少必填参数refund_fee!");
}
else if (!("op_user_id"))
{
throw new WxPayException("退款申请接口中,缺少必填参数op_user_id!");
}
("appid", );//公众账号ID
("mch_id", );//商户号
("nonce_str", ().ToString().Replace("-", ""));//随机字符串
("sign", (wxConfig));//签名
string xml = ();
var start = ;
("WxPayApi", "Refund request : " + xml);
string response = (xml, url, true, timeOut, wxConfig);//调用HTTP通信接口提交数据到API
("WxPayApi", "Refund response : " + response);
var end = ;
int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时
//将xml格式的结果转换为对象以返回
WxPayData result = new WxPayData();
(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)
{
(this.GetType().ToString(), "WxPayData数据为空!");
throw new WxPayException("WxPayData数据为空!");
}
string xml = "<xml>";
foreach (KeyValuePair<string, object> pair in m_values)
{
//字段值不能为null,会影响后续流程
if ( == null)
{
(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
throw new WxPayException("WxPayData内部含有值为null的字段!");
}
if (() == typeof(int))
{
xml += "<" + + ">" + + "</" + + ">";
}
else if (() == typeof(string))
{
xml += "<" + + ">" + "<![CDATA[" + + "]]></" + + ">";
}
else//除了string和int类型不能含有其他数据类型
{
(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))
{
(this.GetType().ToString(), "将空的xml串转换为WxPayData不合法!");
throw new WxPayException("将空的xml串转换为WxPayData不合法!");
}
XmlDocument xmlDoc = new XmlDocument();
(xml);
XmlNode xmlNode = ;//获取到根节点<xml>
XmlNodeList nodes = ;
foreach (XmlNode xn in nodes)
{
XmlElement xe = (XmlElement)xn;
m_values[] = ;//获取xml的键值对到WxPayData内部的数据中
}
try
{
//2015-06-29 错误是没有签名
if(m_values["return_code"] != "SUCCESS")
{
return m_values;
}
CheckSign(wxConfig);//验证签名,不通过会抛异常
}
catch(WxPayException ex)
{
throw new WxPayException();
}
return m_values;
}
/**
* @Dictionary格式转化成url参数格式
* @ return url格式串, 该串不包含sign字段值
*/
public string ToUrl()
{
string buff = "";
foreach (KeyValuePair<string, object> pair in m_values)
{
if ( == null)
{
(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
throw new WxPayException("WxPayData内部含有值为null的字段!");
}
if ( != "sign" && () != "")
{
buff += + "=" + + "&";
}
}
buff = ('&');
return buff;
}
/**
* @Dictionary格式化成Json
* @return json串数据
*/
public string ToJson()
{
string jsonStr = (m_values);
return jsonStr;
}
/**
* @values格式化成能在Web页面上显示的结果(因为web页面上不能直接输出xml格式的字符串)
*/
public string ToPrintStr()
{
string str = "";
foreach (KeyValuePair<string, object> pair in m_values)
{
if ( == null)
{
(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
throw new WxPayException("WxPayData内部含有值为null的字段!");
}
str += string.Format("{0}={1}<br>", , ());
}
(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=" + ;
//MD5加密
var md5 = ();
var bs = (Encoding.(str));
var sb = new StringBuilder();
foreach (byte b in bs)
{
(("x2"));
}
//所有字符转为大写
return ().ToUpper();
}
/**
*
* 检测签名是否正确
* 正确返回true,错误抛异常
*/
public bool CheckSign(WxPayConfig wxConfig)
{
//如果没有设置签名,则跳过检测
if (!IsSet("sign"))
{
(this.GetType().ToString(), "WxPayData签名存在但不合法!");
throw new WxPayException("WxPayData签名存在但不合法!");
}
//如果设置了签名但是签名为空,则抛异常
else if(GetValue("sign") == null || GetValue("sign").ToString() == "")
{
(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;
}
(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)
{
(this.GetType().ToString(), "WxPayData数据为空!");
throw new WxPayException("WxPayData数据为空!");
}
string xml = "<xml>";
foreach (KeyValuePair<string, object> pair in m_values)
{
//字段值不能为null,会影响后续流程
if ( == null)
{
(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
throw new WxPayException("WxPayData内部含有值为null的字段!");
}
if (() == typeof(int))
{
xml += "<" + + ">" + + "</" + + ">";
}
else if (() == typeof(string))
{
xml += "<" + + ">" + "<![CDATA[" + + "]]></" + + ">";
}
else//除了string和int类型不能含有其他数据类型
{
(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))
{
(this.GetType().ToString(), "将空的xml串转换为WxPayData不合法!");
throw new WxPayException("将空的xml串转换为WxPayData不合法!");
}
XmlDocument xmlDoc = new XmlDocument();
(xml);
XmlNode xmlNode = ;//获取到根节点<xml>
XmlNodeList nodes = ;
foreach (XmlNode xn in nodes)
{
XmlElement xe = (XmlElement)xn;
m_values[] = ;//获取xml的键值对到WxPayData内部的数据中
}
try
{
//2015-06-29 错误是没有签名
if(m_values["return_code"] != "SUCCESS")
{
return m_values;
}
CheckSign(wxConfig);//验证签名,不通过会抛异常
}
catch(WxPayException ex)
{
throw new WxPayException();
}
return m_values;
}
/**
* @Dictionary格式转化成url参数格式
* @ return url格式串, 该串不包含sign字段值
*/
public string ToUrl()
{
string buff = "";
foreach (KeyValuePair<string, object> pair in m_values)
{
if ( == null)
{
(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
throw new WxPayException("WxPayData内部含有值为null的字段!");
}
if ( != "sign" && () != "")
{
buff += + "=" + + "&";
}
}
buff = ('&');
return buff;
}
/**
* @Dictionary格式化成Json
* @return json串数据
*/
public string ToJson()
{
string jsonStr = (m_values);
return jsonStr;
}
/**
* @values格式化成能在Web页面上显示的结果(因为web页面上不能直接输出xml格式的字符串)
*/
public string ToPrintStr()
{
string str = "";
foreach (KeyValuePair<string, object> pair in m_values)
{
if ( == null)
{
(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
throw new WxPayException("WxPayData内部含有值为null的字段!");
}
str += string.Format("{0}={1}<br>", , ());
}
(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=" + ;
//MD5加密
var md5 = ();
var bs = (Encoding.(str));
var sb = new StringBuilder();
foreach (byte b in bs)
{
(("x2"));
}
//所有字符转为大写
return ().ToUpper();
}
/**
*
* 检测签名是否正确
* 正确返回true,错误抛异常
*/
public bool CheckSign(WxPayConfig wxConfig)
{
//如果没有设置签名,则跳过检测
if (!IsSet("sign"))
{
(this.GetType().ToString(), "WxPayData签名存在但不合法!");
throw new WxPayException("WxPayData签名存在但不合法!");
}
//如果设置了签名但是签名为空,则抛异常
else if(GetValue("sign") == null || GetValue("sign").ToString() == "")
{
(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;
}
(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)
{
("RefundQuery", "RefundQuery is processing...");
WxPayData data = new WxPayData();
if(!string.IsNullOrEmpty(refund_id))
{
("refund_id", refund_id);//微信退款单号,优先级最高
}
else if(!string.IsNullOrEmpty(out_refund_no))
{
("out_refund_no", out_refund_no);//商户退款单号,优先级第二
}
else if(!string.IsNullOrEmpty(transaction_id))
{
("transaction_id", transaction_id);//微信订单号,优先级第三
}
else
{
("out_trade_no", out_trade_no);//商户订单号,优先级最低
}
WxPayData result = RefundQuery(data, wxConfig);//提交退款查询给API,接收返回数据
("RefundQuery", "RefundQuery process complete, result : " + ());
return ();
}
/**
*
* 查询退款
* 提交退款申请后,通过该接口查询退款状态。退款有一定延时,
* 用零钱支付的退款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 = "/pay/refundquery";
//检测必填参数
if(!("out_refund_no") && !("out_trade_no") &&
!("transaction_id") && !("refund_id"))
{
throw new WxPayException("退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个!");
}
("appid", );//公众账号ID
("mch_id", );//商户号
("nonce_str",GenerateNonceStr());//随机字符串
("sign", (wxConfig));//签名
string xml = ();
var start = ;//请求开始时间
("WxPayApi", "RefundQuery request : " + xml);
string response = (xml, url, false, timeOut, wxConfig);//调用HTTP通信接口以提交数据到API
("WxPayApi", "RefundQuery response : " + response);
var end = ;
int timeCost = (int)((end-start).TotalMilliseconds);//获得接口耗时
//将xml格式的结果转换为对象以返回
WxPayData result = new WxPayData();
(response, wxConfig);
ReportCostTime(url, timeCost, result, wxConfig);//测速上报
return result;
}