工商银行银企互联Java开发
2019-05-20 本文已影响141人
Helloword_Cc
由于微信转账限额(大概是每个月20万的样子),公司业务上需要将公账的金额转出到用户被金额限制,所以准备接入工商银行的银企互联(公司银行公账——>用户账户),开发中发现工商的工作人员丢过来的开发文档不是最新的,导致折腾了半天,今天折腾出来了做个笔记,也让大家少走弯路。。
-
安装工商银行银企互联工具(NSAE_NC),这个东西必须安装在和调用服务的功能同一台服务器。因为工商限制本机调用访问,为了提高安全性。
安装完成打开127.0.0.1,默认80端口,如果80端口跑了其他服务器,则修改config.xml文件的端口号。
image.png
好了,startup启动。访问127.0.0.1:8081(由于作者端口号改为8081)
image.png
启动成功,修改客户端,服务端,签名的配置,
image.png
本地监听IP为本地IP,端口自己定,不被占用就好。后台服务IP填写directbank.icbc.com.cn端口号为 446,再其次就是导入证书跟秘钥了。image.png
三个端都显示运行中则证明运行成功。如果有报错可以看日志文件再自己分析解决。日志路径如下图:
![](https://img.haomeiwen.com/i14814666/bb479198e1db5fa7.png)
好了,终于把东西安装好了,抽根烟继续。😄
-
我们先来看看这个***开发文档怎么说。
image.png
1. **结算类:**
1. 企业按照工行提供的xml包格式进行打包,在局域网内与NetSafe Client的签名端口建立Socket连接,通过此连接向签名端口发送http数据包。http包头中需包含“**Content-Length**”和“**Content-Type**”两个属性。其中“**Content-Length:**”后面是需要签名的二进制数据包的长度,“**Content-Type:**”后面是需要签名的标记,为**INFOSEC_SIGN/1.0**。(注意大小写)
**http****请求格式:****action=**”[http://客户端NetSafe Client的地址和签名端口号](http://%E5%AE%A2%E6%88%B7%E7%AB%AFNetSafe%20Client%E7%9A%84%E5%9C%B0%E5%9D%80%E5%92%8C%E7%AD%BE%E5%90%8D%E7%AB%AF%E5%8F%A3%E5%8F%B7)**”**
**请求数据格式:**结算类请求提交的xml包
NetSafe Client对xml包进行签名后,通过http协议将签名结果返回给企业系统。如签名成功<sign>标签与</sign>标签之间的部分为签名结果。
NetSafe Client返回的签名包如下:
<html>
<head>
<title>签名结果</title>
<result>0</result>
</head>
<body>
<sign>MIIIXAYJKovcNAQcCo…………. 0BlLdSgw=</sign>
</body>
</html>
2. 企业按照工行提供的xml包格式进行打包,在局域网内通过http协议以POST方式将交易包发送到NetSafe Client的安全http协议服务器。
http请求格式:action=”http://客户端NetSafe Client的地址和加密端口号/servlet/ICBCCMPAPIReqServlet?userID=证书ID&PackageID=包序列ID &SendTime=请求时间”
请求数据格式(post方式):Version=版本号(区分版本时间,暂定0.0.0.1) &TransCode=交易代码(区分交易类型,每个交易固定)&BankCode=客户的归属单位&GroupCIS=客户的归属编码&ID=客户的证书ID(无证书客户可空)&PackageID=客户的指令包序列号(由客户ERP系统产生,不可重复)&Cert=客户的证书公钥信息(进行BASE64编码;NC客户送空) &reqData=客户的xml请求数据
其中:包序列ID、证书ID应根据实际情况进行更改,请求时间为企业发出该交易请求包的当前系统时间。post方式最后不允许有回车等其他乱字符,TransCode交易名称应与xml包内标签<TransCode></TransCode>中的值一致,action中的证书ID、PackageID与请求数据格式中的证书ID、PackageID、xml包中的证书ID、PackageID的值三者相一致。
3. NetSafe Client将企业送来的签名包加密后按照https协议,通过互联网/专线发送到工行端的NetSafe Server,再发往工行网银进行处理。(本步由NetSafe Client完成,企业无需处理)。
额,看懂了吗,看不懂听我说。把xml报文请求到刚才安装的签名端进行签名,签名完再http请求到刚才安装的客户端,客户端再帮你请求服务端,服务端再帮你请求工行系统。你觉得很绕吗,你的感觉是对的,😄
其实这些都是小事情,主要还是xml报文,工行丢过来的文档是旧的,导致总是报错xml解析包失败。作者自己在网上找了好几天,拼拼凑凑,现在给个成功的xml大家,让大家可以绕坑。
// 明文组包
xmlcontent = "<?xml version=\"1.0\" encoding = \"GBK\"?>" +
"<CMS><eb>" +
"<pub>" +
"<TransCode>PAYENT</TransCode>" +
"<CIS></CIS>" + //你的cis,自己找工商获取
"<BankCode>102</BankCode>" +
"<ID></ID>" + // 证书id
"<TranDate>"+DateFormatUtil.formatDate(new Date(),DateFormatUtil.PT_YYYYMMDD)+"</TranDate>" +//日期,格式自己看
"<TranTime>"+DateFormatUtil.formatDate(new Date(),"hhmmssSSS")+"</TranTime>" +
"<fSeqno>"+fSeqno+"</fSeqno>" + //FDK随机字符 跟下面post到客户端的PackageID一致
"</pub>" +
"<in>" +
"<OnlBatF>1</OnlBatF>" +
"<SettleMode>0</SettleMode>" +
"<TotalNum>1</TotalNum>" + //这个为此次转账的发起数量(一个xml报文可以发起对多个账号提现)
"<TotalAmt>10</TotalAmt>" +//所有转账的总金额,注意这个单位为分
"<SignTime>"+time+"</SignTime>" +
"<ReqReserved1></ReqReserved1>" +
"<ReqReserved2></ReqReserved2>" +
"<rd>" +
"<iSeqno>1</iSeqno>" +
"<ReimburseNo></ReimburseNo>" +
"<ReimburseNum></ReimburseNum>" +
"<StartDate></StartDate>" +
"<StartTime></StartTime>" +
"<PayType>2</PayType>" +
"<PayAccNo></PayAccNo>" +// 付款方的银行卡账号
"<PayAccNameCN></PayAccNameCN>" + //支付的账户名称,需要跟刚才填写的账号对应,实名的,否则报错
"<PayAccNameEN></PayAccNameEN>" +
"<RecAccNo></RecAccNo>" +//收款方的账号
"<RecAccNameCN></RecAccNameCN>" +//收款方的账户名称,也需要跟账号对应
"<RecAccNameEN></RecAccNameEN>" +
"<SysIOFlg>1</SysIOFlg>" +//第一笔发起
"<IsSameCity></IsSameCity>" +
"<Prop>1</Prop>" +
"<RecICBCCode></RecICBCCode>" +
"<RecCityName></RecCityName>" +
"<RecBankNo></RecBankNo>" +
"<RecBankName></RecBankName>" +
"<CurrType>001</CurrType>" +
"<PayAmt>10</PayAmt>" +//第一笔发起的金额
"<UseCode></UseCode>" +
"<UseCN>上线测试</UseCN>" +
"<EnSummary></EnSummary>" +
"<PostScript></PostScript>" +
"<Summary></Summary>" +
"<Ref>"+fSeqno+"</Ref>" +
"<Oref></Oref>" +
"<ERPSqn></ERPSqn>" +
"<BusCode></BusCode>" +
"<ERPcheckno></ERPcheckno>" +
"<CrvouhType></CrvouhType>" +
"<CrvouhName></CrvouhName>" +
"<CrvouhNo></CrvouhNo>" +
"<ReqReserved3></ReqReserved3>" +
"<ReqReserved4></ReqReserved4>" +
"</rd>" +
"</in></eb></CMS>";
xml的报文需要注意的是,文档写着的:xml包内标签<TransCode></TransCode>中的值一致,action中的证书ID、PackageID与请求数据格式中的证书ID、PackageID、xml包中的证书ID、PackageID的值三者相一致。
基本的必填项目我已经备注,其他的可以跟着填写。
现在上整个请求代码
public Map<String,Object> settleAccounts(HttpServletRequest request) {
Map result = new HashMap();
String xmlcontent = ""; // xml报文格式的字符串
String signcontent = ""; // 签名返回的信息
String repcontent = ""; // 工行返回的信息
String NCIp = ""; //你安装工行系统服务器的ip
// String NCIp = "127.0.0.1";
String NCPort = ""; // 加密端口号
String NCPort2 = ""; // 客户端端口号
String fSeqno ="FRK"+ DateFormatUtil.formatDate(new Date(),"yyyyMMddHHmmss");
String time =DateFormatUtil.formatDate(new Date(),"yyyyMMddHHmmssSSS");
// 明文组包
xmlcontent = "<?xml version=\"1.0\" encoding = \"GBK\"?>" +
"<CMS><eb>" +
"<pub>" +
"<TransCode>PAYENT</TransCode>" +
"<CIS></CIS>" +
"<BankCode>102</BankCode>" +
"<ID></ID>" +
"<TranDate>"+DateFormatUtil.formatDate(new Date(),DateFormatUtil.PT_YYYYMMDD)+"</TranDate>" +
"<TranTime>"+DateFormatUtil.formatDate(new Date(),"hhmmssSSS")+"</TranTime>" +
"<fSeqno>"+fSeqno+"</fSeqno>" +
"</pub>" +
"<in>" +
"<OnlBatF>1</OnlBatF>" +
"<SettleMode>0</SettleMode>" +
"<TotalNum>1</TotalNum>" +
"<TotalAmt>10</TotalAmt>" +
"<SignTime>"+time+"</SignTime>" +
"<ReqReserved1></ReqReserved1>" +
"<ReqReserved2></ReqReserved2>" +
"<rd>" +
"<iSeqno>1</iSeqno>" +
"<ReimburseNo></ReimburseNo>" +
"<ReimburseNum></ReimburseNum>" +
"<StartDate></StartDate>" +
"<StartTime></StartTime>" +
"<PayType>2</PayType>" +
"<PayAccNo></PayAccNo>" +
"<PayAccNameCN></PayAccNameCN>" +
"<PayAccNameEN></PayAccNameEN>" +
"<RecAccNo></RecAccNo>" +
"<RecAccNameCN></RecAccNameCN>" +
"<RecAccNameEN></RecAccNameEN>" +
"<SysIOFlg>1</SysIOFlg>" +
"<IsSameCity></IsSameCity>" +
"<Prop>1</Prop>" +
"<RecICBCCode></RecICBCCode>" +
"<RecCityName></RecCityName>" +
"<RecBankNo></RecBankNo>" +
"<RecBankName></RecBankName>" +
"<CurrType>001</CurrType>" +
"<PayAmt>10</PayAmt>" +
"<UseCode></UseCode>" +
"<UseCN>上线测试</UseCN>" +
"<EnSummary></EnSummary>" +
"<PostScript></PostScript>" +
"<Summary></Summary>" +
"<Ref>"+fSeqno+"</Ref>" +
"<Oref></Oref>" +
"<ERPSqn></ERPSqn>" +
"<BusCode></BusCode>" +
"<ERPcheckno></ERPcheckno>" +
"<CrvouhType></CrvouhType>" +
"<CrvouhName></CrvouhName>" +
"<CrvouhNo></CrvouhNo>" +
"<ReqReserved3></ReqReserved3>" +
"<ReqReserved4></ReqReserved4>" +
"</rd>" +
"</in></eb></CMS>";
String dataTime =DateFormatUtil.formatDate(new Date(),"yyyyMMddHHmmss");
logger.error("xml报文明文组包:" + xmlcontent);
HttpClient myclient = null;
PostMethod httppost = null;
// 如果明文xmlcontent中包括SignTime节点,即该交易需要签名
if (xmlcontent.indexOf("<SignTime>") > -1) {
try {
myclient = new HttpClient(); // 构建http客户端
httppost = new PostMethod("http://" + NCIp + ":" + NCPort2); // 加密端口
httppost.addRequestHeader("Content-Type", "INFOSEC_SIGN/1.0");
InputStream in = new ByteArrayInputStream(xmlcontent.getBytes());
httppost.setRequestBody(in);
int returnFlag = myclient.executeMethod(httppost); // 获得http返回码
String postResult = httppost.getResponseBodyAsString();
signcontent = new String(postResult.getBytes("ISO8859-1"),"gb2312");
System.out.println("NC签名返回数据如下:" + signcontent);
// String is ="";
logger.error("NC签名返回数据如下:" + signcontent);
} catch (Exception e) {
logger.error("签名出错:"+ e.toString());
e.printStackTrace();
} finally {
try {
httppost.releaseConnection(); // 释放http连接
myclient.getHttpConnectionManager().closeIdleConnections(0);
} catch (Exception e2) {
logger.error("签名释放出错:"+ e2.toString());
}
myclient = null;
httppost = null;
}
signcontent.replaceAll("\n", "");
int beginSign = signcontent.indexOf("<sign>") + 6;
int endSign = signcontent.indexOf("</sign>");
signcontent = signcontent.substring(beginSign, endSign);
logger.error("签名:"+ signcontent);
} else {
//如果不需要签名直接放明文
signcontent = xmlcontent;
}
String urlStr1 = "http://" + NCIp + ":" + NCPort+ "/servlet/ICBCCMPAPIReqServlet";
try {
myclient = new HttpClient(); // 构建http客户端
httppost = new PostMethod(urlStr1); // 加密端口
httppost.addRequestHeader("Content-Type","application/x-www-form-urlencoded");
/* httppost.addParameter("Version", "0.1");
;*/
httppost.addParameter("PackageID", fSeqno);
httppost.addParameter("Version", "1.0");
httppost.addParameter("TransCode", "PAYENT");
httppost.addParameter("BankCode", "102");
httppost.addParameter("Cert", "");
httppost.addParameter("ID", ");//证书id
httppost.addParameter("GroupCIS", "");//CIS
httppost.addParameter("reqData", signcontent);
int returnFlag = myclient.executeMethod(httppost); // 获得http返回码
String postResult = httppost.getResponseBodyAsString();
repcontent = new String(postResult.getBytes("ISO8859-1"), "gb2312");
System.out.println("工行返回数据如下:" + repcontent);
logger.error("工行返回数据如下:" + repcontent);
logger.error("http返回码:" + returnFlag);
result.put("httpCode",returnFlag);
} catch (Exception e) {
e.printStackTrace();
logger.error("银企互联报错:" + e.toString());
ExceptionUntil.setLogger(logger,e);
} finally {
try {
httppost.releaseConnection(); // 释放http连接
myclient.getHttpConnectionManager().closeIdleConnections(0);
} catch (Exception e2) {
logger.error("银企互联释放http连接报错:" + e2.toString());
ExceptionUntil.setLogger(logger,e2);
}
myclient = null;
httppost = null;
}
try {
repcontent = repcontent.substring(8);
logger.error("银企互联返回----"+repcontent);
byte[] decodeResult = TesterpTrans.getFromBASE64(repcontent);
repcontent = new String(decodeResult);
System.out.println("base64解码如下:" + repcontent);
logger.error("base64解码如下:" + repcontent);
result.put("xml",repcontent);
result.put("result","true");
} catch (Exception e) {
e.printStackTrace();
logger.error("银企互联返回base64报错:" + e.toString());
result.put("result","false");
result.put("message",e.toString());
ExceptionUntil.setLogger(logger,e);
}
return result;
}
基本看了作者的代码demo也可以实现了。接口成功后iRetMsg返回成功,查询提现的账户。有一笔提现,终于成功接通。😄
![](https://img.haomeiwen.com/i14814666/62fdf342d0c45cc1.png)