不知道张(zhāng)雱(pāng)是谁?你out了!
张(zhāng)雱(pāng)是谁?也许你已经听说过了,也许你还没听说过呢,不过你一定听说过老刘——刘强东,没错,这二人是有关系的,什么关系,京东是老刘的,而张雱呢?张雱是京东旗下52家关联公司法人代表或执行董事。
好了,爆料至此,本文也不是为了要说这些事。 只是为了找个话题营销一下,目的是让你点进来,为什么这么玩呢?也许你会开骂,不过求你放过我,我辛辛苦苦写了这么长的文字,是想求一名观众的。每个人都渴望被关注,不是么?
言归正传,本文的主题是一个订单退款处理问题的解决过程。
近期,运营人员经常反映,在线支付的订单在做退票处理时,总是停滞在TPZ(退票中)状态,无法改成YTP(已退票)。
系统部署方面,支付中心是一个单独的应用,负责内部支付请求与外部的第三方支付平台的信息桥接。商旅的saas是发布给客户用来预定商旅产品的平台。
支付中心退款流程如下:
查看业务系统支付发起的日志,如下:
时间:2016-07-18 23:56:03
请求调用支付中心,目标地址:http://paycenter.***.cn/PayAndRefund/RefundPage.aspx,参数:{"BusinessSystemId":1,"BusinessCode":1,"ThirdPayPlatform":3,"PayType":1,"PaySource":1,"OrderAmount":"880","OrderNo":"AZDOW1607131356","UserName":"李佳祺"}
---------------------------------------------------------------------------------------- 时间:2016-07-18 23:56:05
调用支付中心返回结果:{"Code":"9999","MsgStr":"已有退款单不可以重复发起!"}
----------------------------------------------------------------------------------------
哦,支付中心返回的不是退款成功,而是9999(“已有退款单不可以重复发起”)。
那接下来,去支付中心来进一步排障。支付中心针对这张订单的日志记录如下:
23:56:26.175 ---------------类:ASP.payandrefund_refundpage_aspx
23:56:26.691 接收到退款申请,反序列化正常,原始退款消息是{"BusinessSystemId":1,"BusinessCode":1,"ThirdPayPlatform":3,"PayType":1,"PaySource":1,"OrderAmount":"880","OrderNo":"AZDOW1607131356","UserName":"李佳祺"}---类名:ASP.payandrefund_refundpage_aspx
23:56:26.691 请求的域名(paycenter.***.cn)与Partner支付协议使用的域名(pay.***.cn)不同,用后者域名发起流请求---类名:ASP.payandrefund_refundpage_aspx
23:56:26.785 ---------------类:ASP.payandrefund_refundpage_aspx
23:56:26.785 接收到退款申请,反序列化正常,原始退款消息是{"BusinessSystemId":1,"BusinessCode":1,"ThirdPayPlatform":3,"PayType":1,"PaySource":1,"OrderAmount":"880","OrderNo":"AZDOW1607131356","UserName":"李佳祺"}---类名:ASP.payandrefund_refundpage_aspx
23:56:26.863 持久化正常共添加1条数据---类名:ASP.payandrefund_refundpage_aspx
23:56:26.863 接收支付数据成功并发送数据---类名:ASP.payandrefund_refundpage_aspx
23:56:26.879 ---------------类:ASP.yibao_refundform_aspx
23:56:27.160 进入易宝退款,发起消息: p0_Cmd=RefundExt&p1_MerId=10012414996&p1_RequestId=AZDOW1607131356Refund2&p2_TrxId=218565910862142I&p3_Desc=nothing&p4_Details=&p5_Amt=880&hmac=d1dc1eab0d9553c99788587b20405bdd<=GMX=>返回消息:r0_Cmd=RefundExt
r1_Code=1
r1_RequestId=AZDOW1607131356Refund2
r1_Time=20160718235637
errorMsg=
r2_OrderAmount=
r3_SrcAmount=
r4_RefundAmount=
r5_MerRefundAmount=
r6_Details=
r7_Fee=0.0
r9_BType=2
hmac=f76ac405de03233ad6e92eef9dea2b8e---类:ASP.yibao_refundform_aspx
分析这日志,可以看出,支付中心接收到这笔订单的退款请求,然后向第三方支付平台(易宝)接口发起了退款请求,并得到了退款成功的响应。貌似没什么问题。 但,为什么业务系统接收到支付的响应不是退款成功反而是“已有退款单”呢?
这时不得不说支付中心的日志记录得不够完整。没有记录给业务系统回写了什么。只记录了一个易宝返回退款成功的日志,之后就什么都没有了。
那么,想查明原因,接下来只能分析程序代码了。
退款代码比较多,而且涉及到页面跳转,伪代码是这样子的:
public partial class RefundPage : System.Web.UI.Page
{ /// <summary>
/// 根据Request字符串反序列化得到的实体对象
/// </summary>
private CommonModel.Domains.RefundApply _refundApply; protected void Page_Load(object sender, EventArgs e)
{
验证请求参数,反序列化成_refundApply
ValidDomainNameAndRedirect(); //验证支付中心域名
if(没有该订单的退款记录)
{
持久化退款申请记录
拼接易宝退款请求报文
Hmac加密
拿到易宝退款接口url
流请求方式调用易宝接口
对响应值做处理
if(是退款成功)
{
回写“退款成功”
}
else
{
回写“退款失败”
}
}
else
{
回写9999"已有退款单不可以重复发起!"
}
} /// <summary>
/// 判断请求的域名是否与Partner支付协议使用的域名相同。
/// 如果不同,则发起流请求,修改为Partner支付协议使用的域名
/// </summary>
private void ValidDomainNameAndRedirect()
{
string domainName = CommonModel.PayCenterConfig.PayAddress.PartnerDomainNameMap[_refundApply.Partner];
if (Request.Url.Authority != new Uri(domainName).Authority)
{
string url = string.Format("{0}/PayAndRefund/RefundPage.aspx", domainName);
CommonUtils.LogHelper.Write(this,
string.Format("请求的域名({0})与Partner支付协议使用的域名({1})不同,用后者域名发起流请求",
Request.Url.Authority, new Uri(domainName).Authority));
CommonModel.WebCommon.SendStreamStr(_requestStringInStream, url);
}
}
}
需要再次强调的是ValidDomainNameAndRedirect()方法,这是什么意思呢? 支付中心是我司的一个通用的支付平台,对内公开的域名统一为paycenter.sbh.cn(即内部所有系统对接支付中心统一用这个域名), 而支付中心在与第3方支付平台对接时,是协议商定的域名(我司与连连支付协议域名是pay.yqs.cn,我司与易宝协议域名是payment.sbh.cn,与连连的代付协议域名是payagent.yqs.cn)。 支付中心作为一个站点部署在服务器的iis上,并绑定了这些域名。 基于这个情况,支付中心网站在接收到内部系统的支付请求后,首先要判断域名决定跳到相应的协议域名上(同一个iis站点),然后再做后续的处理。
通过进一步分析上面的代码,问题正是出现在了这个方法上,见如上代码的第49行:用协议域名发起了流请求。那么针对这种情况下的退款操作,最终的回写值应该是流请求的返回值才对。 调整完后的代码如下:
public partial class RefundPage : System.Web.UI.Page
{ /// <summary>
/// 根据Request字符串反序列化得到的实体对象
/// </summary>
private CommonModel.Domains.RefundApply _refundApply; protected void Page_Load(object sender, EventArgs e)
{
验证请求参数,反序列化成_refundApply
var model = ValidDomainNameAndRedirect(); //验证支付中心域名
if(model != null)
{
回写序列化model后的字符串 // 即 “退款成功” or “退款失败”
}
...... //下面代码同上
} private ReturnValue ValidDomainNameAndRedirect()
{
string domainName = CommonModel.PayCenterConfig.PayAddress.PartnerDomainNameMap[_refundApply.Partner];
if (Request.Url.Host != new Uri(domainName).Host)
{
string url = string.Format("{0}/PayAndRefund/RefundPage.aspx", domainName);
CommonUtils.LogCommon.instance.writePay(this,
string.Format("请求的域名({0})与Partner支付协议使用的域名({1})不同,用后者域名发起流请求",
Request.Url.Host, new Uri(domainName).Host));
// 发起流请求,并获取返回值
string retValue = CommonModel.WebCommon.SendStreamStr(_requestStringInStream, url);
if (!string.IsNullOrEmpty(retValue))
{
return JsonConvert.DeserializeObject<ReturnValue>(retValue);
}
}
return null;
}
}
经过仔细的推敲,这么改的逻辑应该是比较严谨了。 支付中心是去年上线的项目,因为缺乏测试用例,加上测试环境无法调用3方支付平台,给这次测试增加了难度。加上时间久远,加上
经测试,整个退款过程终于正常了。发布到生产环境,搞定!
下班后,走在路上,突然想起来文章开始的问题,为什么支付中心返回给业务系统的结果是已有退款单呢?
通过下面的demo就能看出来: 新建2个空的web窗体,.cs里的代码如下:
public partial class SendStreamTest1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write("Application[\"SendStreamStr\"] is: " + Application["SendStreamStr"]);
Application["SendStreamStr"] = null; Uri url = new Uri(string.Format("http://{0}/Test/SendStreamTest2.aspx", Request.Url.Authority));
CommonModel.WebCommon.SendStreamStr("", url.ToString());
Response.Write("the value is:" + Application["SendStreamStr"]);
}
}
public partial class SendStreamTest2 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Application["SendStreamStr"] = "";
Response.Write("Application[\"SendStreamStr\"] is initialized as " + Application["SendStreamStr"]);
}
}
用ie访问SendStreamTest1.aspx,可以看出来,流请求方式调用SendStreamTest2.aspx以后,Application["SendStreamStr"]被赋值了。(ps:注意是用Application来模拟测试的,不能用Session测试哦~你懂的)
通过这个demo不难理解,上面之所以返回已有退款单,就是流请求调用之后,退款申请记录被持久化了,所以这里判断是否已有申请单自然是有了,那么,按照逻辑就会返回已有退款单了。
不知道张(zhāng)雱(pāng)是谁?你out了!的更多相关文章
- 一道月薪3W的java面试题 (小明和小强都是张老师的学生,张老师的生日是某月某日,2人都不知道张老师的生日)
小明和小强都是张老师的学生,张老师的生日是M月N日,2人都知道张老师的生日 是下列10组中的一天,张老师把M值告诉了小明,把N值告诉了小强,张老师问他们知道他的生日是那一天吗? 3月4日 3月5日 3 ...
- Angular6之ng build | ng build --aot | ng build --prod 差异
由于写了大半年的项目终于要告一段落并且即将进行第二阶段优化开发,emmm 基础版本已经二十多个模块了,必不可少的优化是很重要的,尽管项目上使用多层嵌套懒加载,但是在首屏加载的时候,任然很慢啊,因为一直 ...
- 在库中使用schematics——ng add与ng update
起步 创建一个angular库 ng new demo --create-application=false ng g library my-lib 可见如下目录结构 ├── node_modules ...
- Part 14 ng hide and ng show in AngularJS
ng-hide and ng-show directives are used to control the visibility of the HTML elements. Let us under ...
- ThinkPHP 汉字转成多种形式拼音
模型: <?php namespace Admin\Model; use Think\Model; /** * 汉字转拼音 * @author huangguojin */ class ZHMo ...
- angular 2 - 001 ng cli的安装和使用
angular cli 创建项目和组件 ng new my-app --skip-install cd my-app cnpm install ng serve localhost:4200 angu ...
- Angular 中后台前端解决方案 - Ng Alain 介绍
背景 之前项目使用过vue.js+iview,习惯了后端开发的我,总觉得使用不习惯,之前分析易企秀前端代码,接触到了angular js,完备的相关功能,类似后端开发的体验,让人耳目一新,全新的ang ...
- How to Pronounce the Letters NG – No Hard G
How to Pronounce the Letters NG – No Hard G Share Tweet Share Most of the time when you see the lett ...
- angular2 ng build --prod 报错:Module not found: Error: Can't resolve './$$_gendir/app/app.module.ngfactory'
调试页面 ng serve 正常 ng build 也正常 ng build --prod 异常:Module not found: Error: Can't resolve './$$_gendir ...
随机推荐
- 解决EditText和ScrollView滑动冲突问题
该类需要调用 OnTouchListener接口 黄色部分是需要更改部分,改为自己的edittext@Override public boolean onTouch(View view, Motion ...
- D3数据绑定
这里转载一个非常经典的关于D3数据绑定的帖子,由D3作者自己写的,非常棒,以至于我忍不住全文copy到此. 原文地址 Thinking with Joins Say you’re making a b ...
- JS应用,表单上的一些东西
例: <body> <form>我的生日是哪一年? <input type="text" value="" id="t1 ...
- css箭头
.aui-list-item-arrow:before { content: ''; width: 0.4rem; height: 0.4rem; position: absolute; top: 5 ...
- jQuery.zTree的跳坑记录
最近项目用到树型结构的交互,一开始并不打算选择zTree,为了项目进度我妥协了,这一妥协后果就是我进坑了,在2天的挣扎中,我终于跳出坑了,活了下来,有一些感慨纪录下来. 有一个业务场景需要2个树型结构 ...
- 深入理解JavaScript运行机制
深入理解JavaScript运行机制 前言 本文是写作在给团队新人培训之际,所以其实本文的受众是对JavaScript的运行机制不了解或了解起来有困难的小伙伴.也就是说,其实真正的原理和本文阐述的并不 ...
- 【模拟】POJ 3087
直达–>POJ 3087 Shuffle'm Up 题意:一开始没怎么看明白,注意现是从S2里拿牌放在最底下,再放S1,这样交叉放(我一开始以为是S1和S2随意哪个先放,分别模拟取最小),然后在 ...
- Thinkphp3.2.3 执行query命令 包括在模板中使用<php> </php>时 query的使用方法
$sql="select * from `rjshop_productbase` where `id`=1"; $Model =M();$query=$Model->quer ...
- 如何对tableview进行自定义多选
前言: 很多入门不久的程序员或许都会遇到系统自带的tableview多选时cell选择时不能选择自己想要的点击时的颜色或者图片,这让初级开发者们很烦恼.今天,我试着花了几个小时的时间用了自己的想法,去 ...
- Apache多站点配置及启动失败解决办法
一. Apache多站点配置方法 1.打开Apache安装目录下conf/httpd.conf文件,找到下面两行文字,把最前面的 # 号去掉,然后保存. #LoadModule vhost_alias ...