最近做快钱支付平台的退款功能,遇到不少问题,在解决问题的过程中,也获得了不少心得,现在将它们整理出来,以备不时之需。
首先,在做退款功能前,需要研究《快钱人民币网关商户退款接口规范》,弄明白退款所必需的流程,根据接口规范定义,完整的退款流程应该包含以下步骤:
1. 在我们的web应用中,首先要确定两个要素:需要退款的订单和本次退款金额,接下来,便是根据快钱接口规范,拼接退款Url地址。
2. 拼接退款地址时,我们需要先了解接口规范,理解每个参数的含义,下图是接口规范的定义。
merchant_id为商户的编号,即快钱的客户编号,找他们要就是了。
version与command_type固定不变。
txOrder为退款流水号,由你自己制定生成规则,对于快钱来说,你只需要保证生成出来的号码唯一就OK了。
amount为本次退款金额,在提交时最好格式化一下,统一带两位小数。
postdata为退款提交时间,一般取当前时间,格式为“yyyyMMddHHMMSS”。
orderid为订单号,是缴费时,由你自己制定规则,生成的订单编号,同样要保证唯一性,快钱通过这个编号,找到对应的缴费订单。
mac为加密串,MD5加密,偏移量由快钱提供,加密内容包含退款流水线号、退款金额、提交时间、订单号等等,用于安全校验,避免其他参数被修改,导致一系列安全问题。
以上参数中,merchant_id、version、command_type,这三个是固定不变的,可以放到配置文件中,也可以放到数据库表格中。
3. 第三步便是由我们的web应用来访问这个拼接好的地址了,这个地址的返回值是一段XML文档,只有接受到返回值,才能说明退款流程顺利,我们才能进行后续的退款修改数据库的操作。
问题是在这个阶段发生的,由于某些原因,我们的web服务器是无法连接外网的,也就是说,如果我们的web应用采用服务端访问这段地址,是永远获取不到快钱返回给我们的数据。
于是,我们决定将访问操作放在客户端,由客户端使用Ajax来访问这个链接,并且由于是Ajax访问,我们写了一段非常漂亮,用户体验超棒的代码,但是,很快我们发现又遇到了另外一个问题,那就是js跨域访问的问题。
尝试过很多解决办法,例如:
JavaSrcipt跨域解决方案(这也是我说服我老大的一个凭据, - - 还是专家博客比较好使!)
关于跨域有三个办法,第一个是使用服务端代理,这是一个最常见也最好使的办法,但是,这个办法被我们当前的实际条件所拒绝(服务端不能连外网,公司不会因为这个功能而做出让步)。
第二个是使用<script>标签,利用src的设置来解决问题,这个也做过了尝试,但是它要求返回的数据必须是Json格式或js文件的格式,而我们又无法要求快钱修改他们的接口定义,所以,这个方案也被否决了。
第三个便是使用隐藏Iframe,然后,我们在尝试过程中发现,Iframe只能解决一级域名相同情况下的跨域访问,在一级域名不同的情况下,一样存在权限被限制的问题,虽然能看到快钱返回的数据,但根本无法访问。
至此,我们似乎走到了胡同的死角,基本上,在技术层面已经无法解决这个问题了,只能通过沟通,来获得更多的一些东西。
后来,一番交涉,我们获得了一台内外网中转的机器,是公司的邮件服务器,解决办法就是,我在客户端使用Ajax请求后台的Action,后台的Action请求部署在邮件服务器上的web services,然后,这个web services访问快钱的退款链接,终于把这个问题给解决了。
上面解决了思路问题,下面就附贴一些代码吧!贴点代码,篇幅都要长一点 - -
退款相关js
function SubmitRefundment(url, callbackUrl, imgUrl) { // 这里获得本次退款的一些信息,订单号、退款金额、退款流水号…… // 多个订单的批量退款,写for循环没用,最好用递归来做,否则第一轮的ajax请求,会丢失js的状态,后面的循环不会继续操作 // 递归调用,执行退款操作(服务端xml文档请求快钱回调Url) DoRefundment(0, url, callbackUrl, imgUrl); } function DoRefundment(flag, hide, url, callbackUrl, imgUrl) { if (flag == hide.length) return; $.ajax({ url: url, // 提交本次退款信息,拼接退款地址,拼接前,做一次与数据库的比对操作是必须的,哪怕页面提交的退款数据进行了加密 type: "Post", cache: false, data: { hideData: $(hide[flag]).val(), fServiceId: $('#ServiceId').val() }, async: false, success: function (x) { if (x != null && x.length > 0) { $.ajax({ url: $("#ServiceUrl").val(), // 访问Action,传递退款地址,在Action中传递地址,访问web serviceis type: "Post", cache: false, data: { Url: x }, async: false, success: function (x) { if (x != null && x.length > 0) { $.ajax({ url: callbackUrl, // 获得快钱返回数据,处理退款剩余操作 type: "Post", cache: false, data: { xml: x }, async: false, success: function (x) { // 根据剩余操作执行状况,提示用户 // 递归调用 DoRefundment(++flag, hide, url, callbackUrl, imgUrl); }, error: function (x, e) { Alert("!ERROR"); flag++; }, complete: function (x) { //flag++; } }); } else { // 提示错误信息,排除程序出错的状况,如果信息为空,应该是web services出了问题,要不就是中转的服务器也连不上外网 // 递归调用 DoRefundment(++flag, hide, url, callbackUrl, imgUrl); } }, error: function (x, e) { Alert("!ERROR"); }, complete: function (x) { var j = 0; } }); } else { // 提示错误信息,这里应该是你拼接程序的问题 // 递归调用 DoRefundment(++flag, hide, url, callbackUrl, imgUrl); } }, error: function (x, e) { Alert("!ERROR"); }, complete: function (x, e) { } }); }
退款Action
// 退款CallBack public JsonResult RefundmentCallBack(string url) { // 调用webservices EmailService es = new EmailService(); string xml = es.GetRefundmentMsg(url); if (xml != null && xml.Length > 0) return Json(xml); else return Json(null); }
web services接口定义
/// <summary> /// 获得快钱退款回调Url返回的xml /// </summary> /// <param name="url">回调Url</param> /// <returns></returns> [WebMethod] public string GetRefundmentMsg(string url) { if (url != null && url.Length > 0) { try { System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); doc.Load(url); return doc.InnerXml.Substring(doc.InnerXml.IndexOf("<DRAWBACKAPI>")); } catch (System.Threading.ThreadAbortException ee) { } catch (Exception ex) { // 写日志 } } return null; }