新春即将来临,首先给大家拜个早年,祝攻城狮们新年快乐、万事如意、合家欢乐、团团圆圆、幸福健康、来年更能大展宏图 实现各自的梦想! 同时预祝各大科技公司大佬们事业蒸蒸日上、公司转型突破创新、冲出突围带领员工们早日实现上市梦想!
今天研究了下银联在线支付功能,特地记录下以表码农们还在坚守岗位。此功能主要是一般的.NET实现的,有机会转为标准的MVC模式实现以及应用到asp.net core 中。
首先第一是支付首页代码:
PayIndex.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="PayIndex.aspx.cs" Inherits="UnionPayNET.PayIndex" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> </head> <body> <form id="form1" runat="server" method="post" action="UnionPay.aspx"> <div> <p> <label>商户号:</label> <input id="merId" type="text" pattern="\d{15}" name="merId" placeholder="商户号" value="777290058110048" title="默认商户号仅作为联调测试使用,正式上线还请使用正式申请的商户号。" required="required"/> </p> <p> <label>交易金额:</label> <input id="txnAmt" type="text" pattern="\d{1,12}" name="txnAmt" placeholder="交易金额" value="1000" title="单位为分,1-12位数字。" required="required"/> </p> <p> <label>订单发送时间:</label> <input id="txnTime" type="text" pattern="\d{14}" name="txnTime" placeholder="订单发送时间,YYYYMMDDhhmmss格式" value="<%= DateTime.Now.ToString("yyyyMMddHHmmss") %>" title="取北京时间,YYYYMMDDhhmmss格式。" required="required"/> </p> <p> <label>商户订单号:</label> <input id="orderId" type="text" pattern="[0-9a-zA-Z]{8,32}" name="orderId" placeholder="商户订单号" value="<%= DateTime.Now.ToString("yyyyMMddHHmmssfff") %>" title="8-32位数字字母,自行定义内容。" required="required"/> </p> <p> <label> </label> <input type="submit" class="button" value="跳转银联页面支付" /> </p> </div> </form> </body> </html>
第二是跳转到支付:UnionPay.aspx
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { Dictionary<string, string> param = new Dictionary<string, string>(); //以下信息非特殊情况不需要改动 param["version"] = SDKConfig.Version;//版本号 param["encoding"] = "UTF-8";//编码方式 param["txnType"] = "01";//交易类型 param["txnSubType"] = "01";//交易子类 param["bizType"] = "000201";//业务类型 param["signMethod"] = SDKConfig.SignMethod;//签名方法 param["channelType"] = "08";//渠道类型 param["accessType"] = "0";//接入类型 param["frontUrl"] = SDKConfig.FrontUrl; //前台通知地址 param["backUrl"] = SDKConfig.BackUrl; //后台通知地址 param["currencyCode"] = "156";//交易币种 // 订单超时时间。 // 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。 跳转银行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。 // 此时间建议取支付时的北京时间加15分钟。 // 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。 param["payTimeout"] = DateTime.Now.AddMinutes(15).ToString("yyyyMMddHHmmss"); //TODO 以下信息需要填写 param["merId"] = Request.Form["merId"].ToString();//商户号,请改自己的测试商户号,此处默认取demo演示页面传递的参数 param["orderId"] = Request.Form["orderId"].ToString();//商户订单号,8-32位数字字母,不能含“-”或“_”,此处默认取demo演示页面传递的参数,可以自行定制规则 param["txnTime"] = Request.Form["txnTime"].ToString();//订单发送时间,格式为YYYYMMDDhhmmss,取北京时间,此处默认取demo演示页面传递的参数,参考取法: DateTime.Now.ToString("yyyyMMddHHmmss") param["txnAmt"] = Request.Form["txnAmt"].ToString();//交易金额,单位分,此处默认取demo演示页面传递的参数 AcpService.Sign(param, System.Text.Encoding.UTF8); string html = AcpService.CreateAutoFormHtml(SDKConfig.FrontTransUrl, param, System.Text.Encoding.UTF8);// 将SDKUtil产生的Html文档写入页面,从而引导用户浏览器重定向 Response.ContentEncoding = Encoding.UTF8; // 指定输出编码 Response.Write(html); } }
第三是:前台通知地址,填写接收银联前台通知的地址
FrontRcvResponse
protected string html; protected void Page_Load(object sender, EventArgs e) { log4net.ILog log = log4net.LogManager.GetLogger(this.GetType()); if (Request.HttpMethod == "POST") { // 使用Dictionary保存参数 Dictionary<string, string> resData = new Dictionary<string, string>(); NameValueCollection coll = Request.Form; string[] requestItem = coll.AllKeys; for (int i = 0; i < requestItem.Length; i++) { resData.Add(requestItem[i], Request.Form[requestItem[i]]); } //商户端根据返回报文内容处理自己的业务逻辑 ,DEMO此处只输出报文结果 StringBuilder builder = new StringBuilder(); log.Info("receive front notify: " + SDKUtil.CreateLinkString(resData, false, true, System.Text.Encoding.UTF8)); builder.Append("<tr><td align=\"center\" colspan=\"2\"><b>商户端接收银联返回报文并按照表格形式输出结果</b></td></tr>"); for (int i = 0; i < requestItem.Length; i++) { builder.Append("<tr><td width=\"30%\" align=\"right\">" + requestItem[i] + "</td><td style=‘word-break:break-all‘>" + Request.Form[requestItem[i]] + "</td></tr>"); } if (AcpService.Validate(resData, System.Text.Encoding.UTF8)) { builder.Append("<tr><td width=\"30%\" align=\"right\">商户端验证银联返回报文结果</td><td>验证签名成功.</td></tr>"); string respcode = resData["respCode"]; //00、A6为成功,其余为失败。其他字段也可按此方式获取。 //如果卡号我们业务配了会返回且配了需要加密的话,请按此方法解密 //if(resData.ContainsKey("accNo")) //{ // string accNo = SecurityUtil.DecryptData(resData["accNo"], System.Text.Encoding.UTF8); //} //customerInfo子域的获取 if (resData.ContainsKey("customerInfo")) { Dictionary<string, string> customerInfo = AcpService.ParseCustomerInfo(resData["customerInfo"], System.Text.Encoding.UTF8); if (customerInfo.ContainsKey("phoneNo")) { string phoneNo = customerInfo["phoneNo"]; //customerInfo其他子域均可参考此方式获取 } foreach (KeyValuePair<string, string> pair in customerInfo) { builder.Append(pair.Key + "=" + pair.Value + "<br>\n"); } } } else { builder.Append("<tr><td width=\"30%\" align=\"right\">商户端验证银联返回报文结果</td><td>验证签名失败.</td></tr>"); } html = builder.ToString(); } }
第四是:后台通知地址,填写后台接收银联后台通知的地址,必须外网能访问
BackRcvResponse
protected string html; protected void Page_Load(object sender, EventArgs e) { log4net.ILog log = log4net.LogManager.GetLogger(this.GetType()); // **************后台接收银联返回报文交易结果展示*********************** if (Request.HttpMethod == "POST") { // 使用Dictionary保存参数 Dictionary<string, string> resData = new Dictionary<string, string>(); NameValueCollection coll = Request.Form; string[] requestItem = coll.AllKeys; for (int i = 0; i < requestItem.Length; i++) { resData.Add(requestItem[i], Request.Form[requestItem[i]]); } //商户端根据返回报文内容处理自己的业务逻辑 , 处理订单状态相关业务数据处理 //DEMO此处只输出报文结果 StringBuilder builder = new StringBuilder(); log.Info("receive back notify: " + SDKUtil.CreateLinkString(resData, false, true, System.Text.Encoding.UTF8)); builder.Append("<tr><td align=\"center\" colspan=\"2\"><b>商户端接收银联返回报文并按照表格形式输出结果</b></td></tr>"); for (int i = 0; i < requestItem.Length; i++) { builder.Append("<tr><td width=\"30%\" align=\"right\">" + requestItem[i] + "</td><td style=‘word-break:break-all‘>" + Request.Form[requestItem[i]] + "</td></tr>"); } if (AcpService.Validate(resData, System.Text.Encoding.UTF8)) { builder.Append("<tr><td width=\"30%\" align=\"right\">商户端验证银联返回报文结果</td><td>验证签名成功.</td></tr>"); string respcode = resData["respCode"]; //00、A6为成功,其余为失败。其他字段也可按此方式获取。 //如果卡号我们业务配了会返回且配了需要加密的话,请按此方法解密 //if(resData.ContainsKey("accNo")) //{ // string accNo = SecurityUtil.DecryptData(resData["accNo"], System.Text.Encoding.UTF8); //} //customerInfo子域的获取 if (resData.ContainsKey("customerInfo")) { Dictionary<string, string> customerInfo = AcpService.ParseCustomerInfo(resData["customerInfo"], System.Text.Encoding.UTF8); if (customerInfo.ContainsKey("phoneNo")) { string phoneNo = customerInfo["phoneNo"]; //customerInfo其他子域均可参考此方式获取 } foreach (KeyValuePair<string, string> pair in customerInfo) { builder.Append(pair.Key + "=" + pair.Value + "<br>\n"); } } } else { builder.Append("<tr><td width=\"30%\" align=\"right\">商户端验证银联返回报文结果</td><td>验证签名失败.</td></tr>"); } html = builder.ToString(); } }
第五是:web.config
<?xml version="1.0" encoding="utf-8"?> <!-- 有关如何配置 ASP.NET 应用程序的详细信息,请访问 https://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <appSettings> <!-- ##############SDK配置文件(密钥方式签名)################ # 说明: # 1. 使用时请删除后缀的“.密钥”,并将此文件替换原来的Web.config。 # 2. 具体配置项请根据注释修改。 # 3. 请注意无跳转、代收付等涉及敏感信息加密的产品一定要用证书方式签名的,请勿使用此文件。 ######################################################### --> <!-- 签名密钥(配置业务邮件发送的密钥 ,测试环境固定88888888) --> <add key="acpsdk.secureKey" value="88888888" /> <!-- ##############SDK配置文件(证书方式签名)################ # 说明: # 1. 使用时请删除后缀的“.证书”,并将此文件替换原来的Web.config。 # 2. 具体配置项请根据注释修改。 ######################################################### --> <!-- 签名证书路径,证书位于assets/测试环境证书/文件夹下,请复制到d:/certs文件夹 --> <add key="acpsdk.signCert.path" value="d:/UnionPayCert/acp_test_sign.pfx" /> <!-- 签名证书密码,测试证书默认000000 --> <add key="acpsdk.signCert.pwd" value="000000" /> <!-- 加密证书路径 --> <add key="acpsdk.encryptCert.path" value="d:/UnionPayCert/acp_test_enc.cer" /> <!-- 验签中级证书路径 --> <add key="acpsdk.middleCert.path" value="d:/UnionPayCert/acp_test_middle.cer" /> <!-- 验签根证书路径 --> <add key="acpsdk.rootCert.path" value="d:/UnionPayCert/acp_test_root.cer" /> <!-- 签名方式,证书方式固定01,请勿改动。 --> <add key="acpsdk.signMethod" value="01" /> <!-- 报文版本号,固定5.1.0,请勿改动。。 --> <add key="acpsdk.version" value="5.1.0" /> <!-- 是否验证https证书,测试环境请设置false,生产环境建议优先尝试true,不行再false。非true的值默认都当false处理 --> <add key="acpsdk.ifValidateRemoteCert" value="false" /> <!-- 是否验证验签证书的CN,测试环境请设置false,生产环境请设置true。非false的值默认都当true处理 --> <add key="acpsdk.ifValidateCNName" value="false" /> <!-- 前台通知地址,填写接收银联前台通知的地址 --> <add key="acpsdk.frontUrl" value="http://localhost:3000/UnionPayNotiy/FrontRcvResponse.aspx" /> <!-- 后台通知地址,填写后台接收银联后台通知的地址,必须外网能访问 --> <add key="acpsdk.backUrl" value="http://8.20.7.8:3000/UnionPayNotiy/BackRcvResponse.aspx" /> <!--########################## 测试环境地址(生产环境地址见assets文件夹下面的生产环境配置文件) #############################--> <!-- 前台交易地址 --> <add key="acpsdk.frontTransUrl" value="https://gateway.test.95516.com/gateway/api/frontTransReq.do" /> <!-- 后台交易地址 --> <add key="acpsdk.backTransUrl" value="https://gateway.test.95516.com/gateway/api/backTransReq.do" /> <!-- 交易状态查询地址 --> <add key="acpsdk.singleQueryUrl" value="https://gateway.test.95516.com/gateway/api/queryTrans.do" /> <!-- 文件传输类交易地址 --> <add key="acpsdk.fileTransUrl" value="https://filedownload.test.95516.com/" /> <!-- 批量交易地址 --> <add key="acpsdk.batTransUrl" value="https://gateway.test.95516.com/gateway/api/batchTrans.do" /> <!-- 有卡交易地址 --> <add key="acpsdk.cardRequestUrl" value="https://gateway.test.95516.com/gateway/api/cardTransReq.do" /> <!-- app交易地址 手机控件支付使用该地址--> <add key="acpsdk.appRequestUrl" value="https://gateway.test.95516.com/gateway/api/appTransReq.do" /> <!-- 前台交易地址 --> <add key="acpsdk.jf.frontTransUrl" value="https://gateway.test.95516.com/jiaofei/api/frontTransReq.do" /> <!-- 后台交易地址 --> <add key="acpsdk.jf.backTransUrl" value="https://gateway.test.95516.com/jiaofei/api/backTransReq.do" /> <!-- 交易状态查询地址 --> <add key="acpsdk.jf.singleQueryUrl" value="https://gateway.test.95516.com/jiaofei/api/queryTrans.do" /> <!-- 有卡交易地址 --> <add key="acpsdk.jf.cardRequestUrl" value="https://gateway.test.95516.com/jiaofei/api/cardTransReq.do" /> <!-- app交易地址 手机控件支付使用该地址--> <add key="acpsdk.jf.appRequestUrl" value="https://gateway.test.95516.com/jiaofei/api/appTransReq.do" /> <!--########################## 测试环境地址(生产环境地址见assets文件夹下面的生产环境配置文件) #############################--> <!--########################## 生产环境地址配置文件) #############################--> <!-- --> <!-- 前台交易地址 --> <!-- <add key="acpsdk.frontTransUrl" value="https://gateway.95516.com/gateway/api/frontTransReq.do" /> --> <!-- 后台交易地址 --> <!-- <add key="acpsdk.backTransUrl" value="https://gateway.95516.com/gateway/api/backTransReq.do" /> --> <!-- 交易状态查询地址 --> <!-- <add key="acpsdk.singleQueryUrl" value="https://gateway.95516.com/gateway/api/queryTrans.do" /> --> <!-- 文件传输类交易地址 --> <!-- <add key="acpsdk.fileTransUrl" value="https://filedownload.95516.com/" /> --> <!-- 批量交易地址 --> <!-- <add key="acpsdk.batTransUrl" value="https://gateway.95516.com/gateway/api/batchTrans.do" /> --> <!-- 有卡交易地址 --> <!-- <add key="acpsdk.cardRequestUrl" value="https://gateway.95516.com/gateway/api/cardTransReq.do" /> --> <!-- app交易地址 手机控件支付使用该地址--> <!-- <add key="acpsdk.appRequestUrl" value="https://gateway.95516.com/gateway/api/appTransReq.do" /> --> <!-- 前台交易地址 --> <!-- <add key="acpsdk.jf.frontTransUrl" value="https://gateway.95516.com/jiaofei/api/frontTransReq.do" /> --> <!-- 后台交易地址 --> <!-- <add key="acpsdk.jf.backTransUrl" value="https://gateway.95516.com/jiaofei/api/backTransReq.do" /> --> <!-- 交易状态查询地址 --> <!-- <add key="acpsdk.jf.singleQueryUrl" value="https://gateway.95516.com/jiaofei/api/queryTrans.do" /> --> <!-- 有卡交易地址 --> <!-- <add key="acpsdk.jf.cardRequestUrl" value="https://gateway.95516.com/jiaofei/api/cardTransReq.do" /> --> <!-- app交易地址 手机控件支付使用该地址--> <!-- <add key="acpsdk.jf.appRequestUrl" value="https://gateway.95516.com/jiaofei/api/appTransReq.do" />--> <!--##########################log4net配置#############################--> <add key="log4net.Config" value="log4net.config"/> <add key="log4net.Config.Watch" value="True"/> </appSettings> <system.web> <compilation debug="true" targetFramework="4.6.1"/> <httpRuntime targetFramework="4.6.1"/> </system.web> <system.codedom> <compilers> <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701"/> <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+"/> </compilers> </system.codedom> </configuration>
第六是:引用两个重要的DLL
CSharpCode.SharpZipLib.dll
BouncyCastle.Crypto.dll
第七就是:照搬官网SDK的相关类即可
最终显示的支付效果页面是: