相关NC中间件的文章,已经放到个人公众号(“开发谈”栏目),扫码可以进入。

  1. 适用对象:自己动手开发银企互联的企业客户。
  2. 非官方产品,客户应优先根据官方开发手册自行开发。
  3. 开发者免责:开发者力求正确,也经过测试,但无法避免潜在的错误。任何软件都可能存在错误。
  4. 客户使用银企互联,应遵从谨小慎微的原则,从查询业务、小金额业务着手,必要时采用银企互联提交指令、企业网银授权的模式。
  5. 本WebService程序在Windows操作系统上部署,需要.net4.0框架。
  6. 未尽事宜,请参阅官方开发手册。

常规开发模式中,数据交互方式如下:

如果是签名类交易,业务发起流程如上图。客户开发的工作量主要是 1、3,较为繁琐。
如果是查询类交易,则上述步骤中 1、2 不做。

企业客户开发过程通常遇到的问题:

  1. 不太愿意看官方开发文档。实际上,我自己开始着手练习银企互联技术,就是按照官方文档来的。
  2. 不知道如何去做签名。
  3. 银行测试环境日期非标准,无法灵活应对请求信息中的日期、时间相关参数。
  4. 很少自己产生日志文件,遇到错误难以确定。向银行陈述错误没有依据。
  5. 采用过程化,很少对象化,代码复用性差,程序不健壮。
  6. 官方提供的DEMO比较少。
  7. 部分企业客户的系统不支持socket协议,需要银行或者开发商提供辅助手段。
  8. 一些高级技巧、应用无从谈起,比如大批量指令发送,制作PDF格式的电子回单。

本方案设计的数据交互为:

本方案图中步骤 2、5,相当于第一图中的 1、2、3、6,由 WebService 自动执行。

特点:

  1. ERP系统只需要通过HTTP协议将参数和发送内容一次性POST到WebService指定页面,接收应答即可。开发者不需要关注签名、提交页面格式、报文头参数、压缩模式等,实现了跨平台、跨语言的简单开发。
  2. 支持压缩模式提交大批量指令:rd最外层头尾套<zip>、</zip>即可,比如:……<zip><rd>……</rd>……<rd>……</rd></zip>……。WebService会自行压缩数据。如将压缩好的数据放入zip节点(官方做法)后使用本程序提交反而会出错。
  3. XML报文中日期/时间关键参数自动配置,大部分情况下自适应银行主机时间。特别是在测试环境的时候,不需要调整本机时间。
  4. ICBC_Log文件夹可以保存日志文件,方便跟踪。
  5. 提供PDF文件的高级应用。

部署本WebService,需要.NET4框架。

  1. 如果没有按照.net4框架,请安装。如果没有安装IIS,请安装。为减少错误,其中应用程序开发功能请全选。
  2. 添加网站,选择物理路径,自己定义一个不常用的端口,本例使用1398。
  3. 运行。浏览器打开对应地址,比如http://127.0.0.1:1398,点击WebService.asmx,可以看到提供的服务。点击CheckNC或者CheckWS,可以查看到对应返回内容,则表明部署成功。
  4. 如果需要从局域网其他电脑访问本WebService,则需要在防火墙中添加的本服务端口(入站、TCP)。

WebService中Web.Config信息:

nc_ip:NetSafeClient的IP地址。

nc_hp、nc_sp:NetSafeClient的HTTPS服务端口以及签名端口。

cis、id:客户企业网银对应的CIS编号和证书名称。使用ICBC_YQHL方法时与XML中信息校验。

log、pdf_save:是否产生日志文件,是否保留pdf回单文档。方便客户调试。

如果防火墙端口未添加,从其他电脑访问出错:

添加端口后,访问正常:

提供的方法以及需要的参数,可以自行点击WebService.asmx查看。比如QACCBAL,从图中看到就是acct一个参数:

本项目的第一个测试客户,是因为sap不支持socket协议,提出“银行给出中间服务层”的需求。给了他们第一个版本(参数个数和现在有些不同),经过调试,畅通。下图是客户sap界面,他们用了get方式,已经建议修改为post。

对于最重要的ICBC_YQHL方法做些说明:

  • 程序自动替换内容的空白节点请使用<node></node>这种格式,如果写成<node/>这种,程序无法自动填充其数值。TranDate、TranTime、SignTime等字段,程序将自动补充,为避免部分系统自动将<TranDate></TranDate>变成<TranDate/>这种格式,可以随意输入些内容,比如<TranDate>NotCare</TranDate>。
  • fSeqno节点内容如果空白或内容长度<=3,则由系统自动填写。个人建议:查询类的报文该字段可以空白或输入长度小于3的内容,交易类的请指定fseqno内容(长度>3),避免发送请求后没有接收到回执而无从判断银行究竟接收到了指令没有。

从网上摘录一些代码,作为本项目的多语言测试。

PHP:先查询余额;做一笔支付;再查询余额。由于是工行系统内支付,实时处理,故第二次查询的时候,余额已经变化了。

<?php
header('charset: GBK');function send_post($url, $post_data) {$postdata = http_build_query($post_data);$options = array('http' => array('method' => 'POST','header' => 'Content-type:application/x-www-form-urlencoded','content' => $postdata,'timeout' => 30 // 超时时间(单位:s)));$context = stream_context_create($options);$result = file_get_contents($url, false, $context);return $result;
}
$xml_info='<?xml version="1.0"encoding="GBK"?><CMS><eb><pub><TransCode>PAYENT</TransCode><CIS>46694306-XAAAAA</CIS>'
.'<BankCode>102</BankCode><ID>suzhouTest.y.1102</ID><TranDate></TranDate>NotCare<TranTime>NotCare</TranTime><fSeqno>X</fSeqno>'
.'</pub><in><OnlBatF>1</OnlBatF><SettleMode>0</SettleMode><TotalNum>1</TotalNum><TotalAmt>1122</TotalAmt><SignTime></SignTime>'
.'<ReqReserved1></ReqReserved1><ReqReserved2></ReqReserved2><rd><iSeqno>1</iSeqno><ReimburseNo></ReimburseNo><ReimburseNum>'
.'</ReimburseNum><StartDate></StartDate><StartTime></StartTime><PayType>1</PayType><PayAccNo>1102020109000009078</PayAccNo>'
.'<PayAccNameCN>剥滥火判酬</PayAccNameCN><PayAccNameEN></PayAccNameEN><RecAccNo>1102020109000203242</RecAccNo>'
.'<RecAccNameCN>剥滥判幕耕婚舒憾</RecAccNameCN><RecAccNameEN></RecAccNameEN><SysIOFlg>1</SysIOFlg><IsSameCity></IsSameCity>'
.'<Prop></Prop><RecICBCCode></RecICBCCode><RecCityName>工行系统内无需注明</RecCityName><RecBankNo></RecBankNo>'
.'<RecBankName>工行系统内无需注明</RecBankName><CurrType>001</CurrType><PayAmt>1122</PayAmt><UseCode></UseCode>'
.'<UseCN>上线测试</UseCN><EnSummary></EnSummary><PostScript></PostScript><Summary></Summary><Ref></Ref><Oref></Oref>'
.'<ERPSqn></ERPSqn><BusCode></BusCode><ERPcheckno></ERPcheckno><CrvouhType></CrvouhType><CrvouhName></CrvouhName>'
.'<CrvouhNo></CrvouhNo><BankType></BankType><FileNames></FileNames><Indexs></Indexs><PaySubNo></PaySubNo>'
.'<RecSubNo></RecSubNo><MCardNo></MCardNo><MCardName></MCardName></rd></in></eb></CMS>';
$post_data = array('ver'=>'0.0.1.0','b64_xml'=>base64_encode($xml_info)
);
$post_data2 = array('acct'=>'1102020109000009078');
echo send_post('http://127.0.0.1:1398/WebService.asmx/QACCBAL', $post_data2);
echo "\r\n";
echo send_post('http://127.0.0.1:1398/WebService.asmx/ICBC_YQHL', $post_data);
echo "\r\n";
echo send_post('http://127.0.0.1:1398/WebService.asmx/QACCBAL', $post_data2);?>

对ReqResult信息做BASE64解码,就能得到原文了,在此不述。

Python3.7:相同的演示。

#!/usr/bin/python
# -*- coding: GBK -*-
import base64
from urllib import request
from urllib import parse
from urllib.request import urlopentest_data = {'acct':'1102020109000009078'}
test_data_urlencode = parse.urlencode(test_data).encode('GBK')
requrl = 'http://10.0.0.5:1398/WebService.asmx/QACCBAL'
req = request.Request(requrl,test_data_urlencode)
res_data = urlopen(req)
res = res_data.read()
print (res.decode())xml_info='<?xml version="1.0"encoding="GBK"?><CMS><eb><pub><TransCode>PAYENT</TransCode><CIS>46694306-XAAAAA</CIS>'\
'<BankCode>102</BankCode><ID>suzhouTest.y.1102</ID><TranDate></TranDate>NotCare<TranTime>NotCare</TranTime><fSeqno>X</fSeqno>'\
'</pub><in><OnlBatF>1</OnlBatF><SettleMode>0</SettleMode><TotalNum>1</TotalNum><TotalAmt>4455</TotalAmt><SignTime></SignTime>'\
'<ReqReserved1></ReqReserved1><ReqReserved2></ReqReserved2><rd><iSeqno>1</iSeqno><ReimburseNo></ReimburseNo><ReimburseNum>'\
'</ReimburseNum><StartDate></StartDate><StartTime></StartTime><PayType>1</PayType><PayAccNo>1102020109000009078</PayAccNo>'\
'<PayAccNameCN>剥滥火判酬</PayAccNameCN><PayAccNameEN></PayAccNameEN><RecAccNo>1102020109000203242</RecAccNo>'\
'<RecAccNameCN>剥滥判幕耕婚舒憾</RecAccNameCN><RecAccNameEN></RecAccNameEN><SysIOFlg>1</SysIOFlg><IsSameCity></IsSameCity>'\
'<Prop></Prop><RecICBCCode></RecICBCCode><RecCityName>工行系统内无需注明</RecCityName><RecBankNo></RecBankNo>'\
'<RecBankName>工行系统内无需注明</RecBankName><CurrType>001</CurrType><PayAmt>4455</PayAmt><UseCode></UseCode>'\
'<UseCN>上线测试</UseCN><EnSummary></EnSummary><PostScript></PostScript><Summary></Summary><Ref></Ref><Oref></Oref>'\
'<ERPSqn></ERPSqn><BusCode></BusCode><ERPcheckno></ERPcheckno><CrvouhType></CrvouhType><CrvouhName></CrvouhName>'\
'<CrvouhNo></CrvouhNo><BankType></BankType><FileNames></FileNames><Indexs></Indexs><PaySubNo></PaySubNo>'\
'<RecSubNo></RecSubNo><MCardNo></MCardNo><MCardName></MCardName></rd></in></eb></CMS>'test_data = {'ver':'0.0.1.0','b64_xml':base64.b64encode(xml_info.encode('GBK'))}
test_data_urlencode = parse.urlencode((test_data)).encode('GBK')
requrl = 'http://10.0.0.5:1398/WebService.asmx/ICBC_YQHL'
req = request.Request(requrl,test_data_urlencode)
res_data = urlopen(req)
res = res_data.read()
print (res.decode())test_data = {'acct':'1102020109000009078'}
test_data_urlencode = parse.urlencode(test_data).encode('GBK')
requrl = 'http://10.0.0.5:1398/WebService.asmx/QACCBAL'
req = request.Request(requrl,test_data_urlencode)
res_data = urlopen(req)
res = res_data.read()
print (res.decode())

JAVA:

import java.util.*;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;public class YQHL
{
public static void main(String[] args)throws Exception
{//Base64.Decoder decoder = Base64.getDecoder();Base64.Encoder encoder = Base64.getEncoder();String xml_text = "<?xml version=\"1.0\"encoding=\"GBK\"?><CMS><eb><pub><TransCode>PAYENT</TransCode><CIS>46694306-XAAAAA</CIS>"+
"<BankCode>102</BankCode><ID>suzhouTest.y.1102</ID><TranDate></TranDate>NotCare<TranTime>NotCare</TranTime><fSeqno>X</fSeqno>"+
"</pub><in><OnlBatF>1</OnlBatF><SettleMode>0</SettleMode><TotalNum>1</TotalNum><TotalAmt>6789</TotalAmt><SignTime></SignTime>"+
"<ReqReserved1></ReqReserved1><ReqReserved2></ReqReserved2><rd><iSeqno>1</iSeqno><ReimburseNo></ReimburseNo><ReimburseNum>"+
"</ReimburseNum><StartDate></StartDate><StartTime></StartTime><PayType>1</PayType><PayAccNo>1102020109000009078</PayAccNo>"+
"<PayAccNameCN>剥滥火判酬</PayAccNameCN><PayAccNameEN></PayAccNameEN><RecAccNo>1102020109000203242</RecAccNo>"+
"<RecAccNameCN>剥滥判幕耕婚舒憾</RecAccNameCN><RecAccNameEN></RecAccNameEN><SysIOFlg>1</SysIOFlg><IsSameCity></IsSameCity>"+
"<Prop></Prop><RecICBCCode></RecICBCCode><RecCityName>工行系统内无需注明</RecCityName><RecBankNo></RecBankNo>"+
"<RecBankName>工行系统内无需注明</RecBankName><CurrType>001</CurrType><PayAmt>6789</PayAmt><UseCode></UseCode>"+
"<UseCN>上线测试</UseCN><EnSummary></EnSummary><PostScript></PostScript><Summary></Summary><Ref></Ref><Oref></Oref>"+
"<ERPSqn></ERPSqn><BusCode></BusCode><ERPcheckno></ERPcheckno><CrvouhType></CrvouhType><CrvouhName></CrvouhName>"+
"<CrvouhNo></CrvouhNo><BankType></BankType><FileNames></FileNames><Indexs></Indexs><PaySubNo></PaySubNo>"+
"<RecSubNo></RecSubNo><MCardNo></MCardNo><MCardName></MCardName></rd></in></eb></CMS>";byte[] textByte = xml_text.getBytes("GBK");String encodedText = encoder.encodeToString(textByte);
//System.out.println(encodedText);//System.out.println(new String(decoder.decode(encodedText), "GBK"));
System.out.println(sendPost("http://10.0.0.5:1398/WebService.asmx/QACCBAL","acct=1102020109000203242"));
System.out.println(sendPost("http://10.0.0.5:1398/WebService.asmx/ICBC_YQHL","ver=0.0.1.0&b64_xml="+encodedText));
System.out.println(sendPost("http://10.0.0.5:1398/WebService.asmx/QACCBAL","acct=1102020109000203242"));}public static String sendPost(String url, String param) {PrintWriter out = null;BufferedReader in = null;String result = "";try {URL realUrl = new URL(url);// 打开和URL之间的连接URLConnection conn = realUrl.openConnection();// 设置通用的请求属性conn.setRequestProperty("accept", "*/*");conn.setRequestProperty("connection", "Keep-Alive");conn.setRequestProperty("user-agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");conn.setRequestProperty("Accept-Charset", "utf-8");conn.setRequestProperty("contentType", "utf-8");// 发送POST请求必须设置如下两行conn.setDoOutput(true);conn.setDoInput(true);// 获取URLConnection对象对应的输出流out = new PrintWriter(conn.getOutputStream());// 发送请求参数out.print(param);// flush输出流的缓冲out.flush();// 定义BufferedReader输入流来读取URL的响应in = new BufferedReader(new InputStreamReader(conn.getInputStream(),"UTF-8"));String line;while ((line = in.readLine()) != null) {result += line;}} catch (Exception e) {System.out.println("发送 POST 请求出现异常!"+e);e.printStackTrace();}//使用finally块来关闭输出流、输入流finally{try{if(out!=null){out.close();}if(in!=null){in.close();}}catch(IOException ex){ex.printStackTrace();}}return result;}
}

运行:

使用.NET:介绍一下大批量支付(一次性提交1000笔指令)。使用本项目会非常简单,在最外层的rd循环节点套一个<zip>即可。

using System;
using System.Text;
using System.Net;
using System.IO;class Program{static void Main(string[] args){string rd_text="<rd><iSeqno>_X_</iSeqno><ReimburseNo></ReimburseNo><ReimburseNum></ReimburseNum><StartDate></StartDate><StartTime></StartTime>"+
"<PayType>1</PayType><PayAccNo>1102020109000009078</PayAccNo><PayAccNameCN>剥滥火判酬</PayAccNameCN><PayAccNameEN></PayAccNameEN>"+
"<RecAccNo>1102020109000203242</RecAccNo><RecAccNameCN>剥滥判幕耕婚舒憾</RecAccNameCN><RecAccNameEN></RecAccNameEN>"+
"<SysIOFlg>1</SysIOFlg><IsSameCity></IsSameCity><Prop></Prop><RecICBCCode></RecICBCCode><RecCityName>工行系统内无需注明"+
"</RecCityName><RecBankNo></RecBankNo><RecBankName>工行系统内无需注明</RecBankName><CurrType>001</CurrType><PayAmt>_X_</PayAmt>"+
"<UseCode></UseCode><UseCN>上线测试</UseCN><EnSummary></EnSummary><PostScript></PostScript><Summary></Summary><Ref></Ref><Oref></Oref>"+
"<ERPSqn></ERPSqn><BusCode></BusCode><ERPcheckno></ERPcheckno><CrvouhType></CrvouhType><CrvouhName></CrvouhName>"+
"<CrvouhNo></CrvouhNo><BankType></BankType><FileNames></FileNames><Indexs></Indexs><PaySubNo></PaySubNo>"+
"<RecSubNo></RecSubNo><MCardNo></MCardNo><MCardName></MCardName></rd>";string rd_total="";int money=0;for(int i=1;i<=1000;++i){rd_total+=rd_text.Replace("_X_",i.ToString());money+=i;}string xml_text = "<?xml version=\"1.0\"encoding=\"GBK\"?><CMS><eb><pub><TransCode>PAYENT</TransCode><CIS>46694306-XAAAAA</CIS>"+
"<BankCode>102</BankCode><ID>suzhouTest.y.1102</ID><TranDate></TranDate>NotCare<TranTime>NotCare</TranTime><fSeqno>X</fSeqno>"+
"</pub><in><OnlBatF>1</OnlBatF><SettleMode>0</SettleMode><TotalNum>1000</TotalNum><TotalAmt>"+money.ToString()+"</TotalAmt><SignTime></SignTime>"+
"<ReqReserved1>大批量压缩测试</ReqReserved1><ReqReserved2></ReqReserved2><zip>"+rd_total+"</zip></in></eb></CMS>";string b64_xml=Convert.ToBase64String(Encoding.GetEncoding("GBK").GetBytes(xml_text));Console.WriteLine(HttpPostTest("http://127.0.0.1:1398/WebService.asmx/QACCBAL","acct=1102020109000009078"));Console.WriteLine();Console.WriteLine(HttpPostTest("http://127.0.0.1:1398/WebService.asmx/ICBC_YQHL","ver=0.0.1.0&b64_xml="+b64_xml));Console.WriteLine();Console.WriteLine(HttpPostTest("http://127.0.0.1:1398/WebService.asmx/QACCBAL","acct=1102020109000009078"));}
static string HttpPostTest(string url, string content){byte[] bytesToPost = Encoding.GetEncoding("UTF-8").GetBytes(content);string cookieheader = string.Empty;CookieContainer cookieCon = new CookieContainer();#region 创建HttpWebRequest对象HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(url);#endregion#region 初始化HtppWebRequest对象httpRequest.CookieContainer = cookieCon;httpRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0;)";httpRequest.ContentType = "application/x-www-form-urlencoded";httpRequest.Method = "POST";httpRequest.Timeout = 15 * 1000;if (cookieheader.Equals(string.Empty)){cookieheader = httpRequest.CookieContainer.GetCookieHeader(new Uri(url));}else{httpRequest.CookieContainer.SetCookies(new Uri(url), cookieheader);}#endregionstring stringResponse = "";try{#region 附加Post给服务器的数据到HttpWebRequest对象httpRequest.ContentLength = bytesToPost.Length;System.IO.Stream requestStream = httpRequest.GetRequestStream();requestStream.Write(bytesToPost, 0, bytesToPost.Length);requestStream.Close();#endregion#region 读取服务器返回信息System.IO.Stream responseStream = httpRequest.GetResponse().GetResponseStream();using (System.IO.StreamReader responseReader = new System.IO.StreamReader(responseStream, Encoding.GetEncoding("UTF-8"))){stringResponse = responseReader.ReadToEnd();}responseStream.Close();#endregion}catch (Exception){;}return  stringResponse;}}

运行截图:

大批量支付,一般需要10分钟左右来处理,因此马上查询余额没有变化。

对应查询指令,只要把支付时候的请求编号放到查询报文的对应字段QryfSeqno即可。

查询指令状态代码:BASE64解码,直接输出明文:

using System;
using System.Text;
using System.Net;
using System.IO;class Program{static void Main(string[] args){string xml_text = "<?xml version=\"1.0\" encoding = \"GBK\"?><CMS><eb><pub><TransCode>QPAYENT</TransCode>"+
"<CIS>46694306-XAAAAA</CIS><BankCode>102</BankCode><ID>suzhouTest.y.1102</ID><TranDate></TranDate><TranTime></TranTime><fSeqno></fSeqno></pub>"+
"<in><QryfSeqno>1810281121DHCV@PE</QryfSeqno><QrySerialNo></QrySerialNo></in></eb></CMS>";string b64_xml=Convert.ToBase64String(Encoding.GetEncoding("GBK").GetBytes(xml_text));   string result=HttpPostTest("http://127.0.0.1:1398/WebService.asmx/ICBC_YQHL","ver=0.0.1.0&b64_xml="+b64_xml);int p1=result.IndexOf("ReqResult=");int p2=result.LastIndexOf("</string>");string result2= Encoding.GetEncoding("GBK").GetString(Convert.FromBase64String(result.Substring(p1+10,p2-p1-10)));Console.WriteLine(result2);}
static string HttpPostTest(string url, string content){byte[] bytesToPost = Encoding.GetEncoding("UTF-8").GetBytes(content);string cookieheader = string.Empty;CookieContainer cookieCon = new CookieContainer();#region 创建HttpWebRequest对象HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(url);#endregion#region 初始化HtppWebRequest对象httpRequest.CookieContainer = cookieCon;httpRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0;)";httpRequest.ContentType = "application/x-www-form-urlencoded";httpRequest.Method = "POST";httpRequest.Timeout = 15 * 1000;if (cookieheader.Equals(string.Empty)){cookieheader = httpRequest.CookieContainer.GetCookieHeader(new Uri(url));}else{httpRequest.CookieContainer.SetCookies(new Uri(url), cookieheader);}#endregionstring stringResponse = "";try{#region 附加Post给服务器的数据到HttpWebRequest对象httpRequest.ContentLength = bytesToPost.Length;System.IO.Stream requestStream = httpRequest.GetRequestStream();requestStream.Write(bytesToPost, 0, bytesToPost.Length);requestStream.Close();#endregion#region 读取服务器返回信息System.IO.Stream responseStream = httpRequest.GetResponse().GetResponseStream();using (System.IO.StreamReader responseReader = new System.IO.StreamReader(responseStream, Encoding.GetEncoding("UTF-8"))){stringResponse = responseReader.ReadToEnd();}responseStream.Close();#endregion}catch (Exception){;}return  stringResponse;}}

运行:

可用输出重定向的方式保存到文件查看,比如:QPAYENT > show.txt

对于回单,经过自己的技术积累,目前提供PDF回单服务,回单格式是自己设计的,比官方的似乎要多一些内容。使用的是破解版商业控件,本人在PDF文档中做了说明,仅用于学习和研究之用。另外,PDF回单文件中文字、图片等都是可以自己修改,所以回单的意义在于存档,生产环境数据得到的回单可以去官方网站验证。如果涉及法律纠纷,需要向银行索要纸质有盖章的凭据。

讲一下PDF回单的获取方法:

输入参数:账号、日期(年月日yyyyMMdd格式)、时间戳,金额(整数,无小数点)。

输出:0|错误消息;1|经过BASE64编码的二进制文件流

输入参数,可以通过查询明细的方式获取,建议使用0.0.1.0版本的QHISD接口。

我用.NET代码演示一下:

using System;
using System.Text;
using System.Net;
using System.IO;class Program{static void Main(string[] args){Console.WriteLine(DateTime.Now.ToString());string result=HttpPostTest("http://127.0.0.1:1398/WebService.asmx/PDF_Receipt","acct=1102020109000009078&date=20181003&time=2018-10-04-15.00.23.937835&amount=582");int p1=result.IndexOf("|");int p2=result.IndexOf("</string>");if(p1>0 && p2>0 && result[p1-1]=='1'){B64String2File(result.Substring(p1+1,p2-p1-1),"csc_code.pdf");Console.WriteLine("csc_code.pdf saved!");}else Console.WriteLine(result);Console.WriteLine(DateTime.Now.ToString());}
static void B64String2File(string B64Str,string fileName){byte[] bb= Convert.FromBase64String(B64Str);FileStream fs = new FileStream(fileName, FileMode.Create);fs.Write(bb, 0, bb.Length);fs.Close();}
static string HttpPostTest(string url, string content){byte[] bytesToPost = Encoding.GetEncoding("UTF-8").GetBytes(content);string cookieheader = string.Empty;CookieContainer cookieCon = new CookieContainer();#region 创建HttpWebRequest对象HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(url);#endregion#region 初始化HtppWebRequest对象httpRequest.CookieContainer = cookieCon;httpRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0;)";httpRequest.ContentType = "application/x-www-form-urlencoded";httpRequest.Method = "POST";httpRequest.Timeout = 30 * 1000;if (cookieheader.Equals(string.Empty)){cookieheader = httpRequest.CookieContainer.GetCookieHeader(new Uri(url));}else{httpRequest.CookieContainer.SetCookies(new Uri(url), cookieheader);}#endregionstring stringResponse = "";try{#region 附加Post给服务器的数据到HttpWebRequest对象httpRequest.ContentLength = bytesToPost.Length;System.IO.Stream requestStream = httpRequest.GetRequestStream();requestStream.Write(bytesToPost, 0, bytesToPost.Length);requestStream.Close();#endregion#region 读取服务器返回信息System.IO.Stream responseStream = httpRequest.GetResponse().GetResponseStream();using (System.IO.StreamReader responseReader = new System.IO.StreamReader(responseStream, Encoding.GetEncoding("UTF-8"))){stringResponse = responseReader.ReadToEnd();}responseStream.Close();#endregion}catch (Exception){;}return  stringResponse;}}

运行截图:

打开看看pdf文档:

PHP代码用于获取PDF回单:

<?php
header('charset: GBK');
function send_post($url, $post_data) {$postdata = http_build_query($post_data);$options = array('http' => array('method' => 'POST','header' => 'Content-type:application/x-www-form-urlencoded','content' => $postdata,'timeout' => 30 // 超时时间(单位:s)));$context = stream_context_create($options);$result = file_get_contents($url, false, $context);return $result;
}
$post_data = array('acct'=>'1102020109000043166','date'=>'20181003','time'=>'2018-10-04-15.07.05.677042','amount'=>'166'
);
$bank_ret=send_post('http://127.0.0.1:1398/WebService.asmx/PDF_Receipt', $post_data);
$p1= strpos($bank_ret,'|');
$p2= strpos($bank_ret,'</string>');
$b64ed=substr($bank_ret,$p1+1,$p2-$p1-1);
$content = base64_decode($b64ed); // 解码
$saveFile = 'php_test.pdf';
file_put_contents($saveFile, $content, true);?>

回单截图:

其他编程语言用于获取PDF回单的代码,读者可自行尝试。

后记:

银企互联业务绝大部分客户都是使用NC模式。非NC模式需要使用官方的jar控件包来实现NetSafeClient的两个服务功能,这增加了开发工作量,也限制了开发语言,好处是能得到银行主动通知。我对非NC模式没有研究。

在十多年的工作中,本人多次协助、指导客户开发,感觉客户自身在银企互联对接方面做的都不够完善和健壮。可能职业程序员都是赶任务,完成目标即可,根本没时间也没动力去对项目优化。本人技术水平一般,甚至可以说是落后的(毕竟工作岗位不是软件开发),但有比较充裕的时间专攻一面。本程序是我十多年银企互联技术支持的经验积累成果,功能完备,能大幅缩减企业开发周期和成本,方便跟踪、调试。

该WebService是鉴于某个SAP客户的需求而生,因客户的SAP不支持socket协议,要求银行提供中间服务层。先是咨询了本地一个软件服务商(坐标江苏),开发这么一个中间层大约需要10天,友情报价4万元。后来又听客户说某个银行开发这么个东西花了10万。这个价格本人觉得合理的,每天4K-5K,花上十天、大半月能搞定已经很不错了。但普通的软件开发商对这项业务以及技术的了解,应该不会比我更加深入。日常接触到使用一些颇有名气的软件的客户,还会打电话问“这笔指令你们银行接收到了吗?”——财务系统居然连一个查询指令状态的接口都没做。思来想去,决定还是自己动手开发一套程序试试。之后发现该方案适用面很大,可以让更多的客户受益,也算是为工行银企互联业务的推广出一把力吧。

Bug Report & Advise:fangrk_sz@qq.com

百度网盘:

https://pan.baidu.com/s/1lrR9qyJcDQdZTO0XtB3lyA

工行银企互联(NC模式)中间件开发模式相关推荐

  1. java调银企互联接口_银企互联NC中间件(演示:.NET、Java、Php、Python)

    *本程序非官方,作为企业客户的财务系统或客户端的一部分,由企业客户做好安全保护和维护* * 使用本程序,视作已知晓且同意* Java.Php.Python我都没学过,网上摘了点代码,自己修改调试,完成 ...

  2. 工行银企互联经验点滴

    工行银企互联经验点滴 现在越来越多企业为了提高工作效率.加快资金周转速率.节约资金成本.提升企业综合竞争力,直接使用银企互联来代替原有的网银等工作,完成原有网银能胜任的所有工作及大量原有网银不能完成的 ...

  3. 工行银企互联接入详解(4)--NC中间件部署

    点此下载源码及配套资源 本文目录 1. 背景 2. 原理 3. 声明 4. 部署流程 4.1 下载并解压NC中间件 4.2 修改Web.Config文件,如下图: 4.3 启用IIS功能 4.4 在I ...

  4. 工行银企互联接入详解(3)--启动NC

    点此下载源码及配套资源 本文目录 1. 背景 2. 打开NC 3. 配置Https客户端 4. 配置签名服务 5. 启动NC 1. 背景 我们的程序通过NC与工行服务进行交互,所以需要配置NC的参数, ...

  5. 工行银企互联接入详解(5)--使用Java调用银企互联接口

    点此下载源码及配套资源 本文目录 1. 准备工作 2. 创建项目 3. 查看接口文档 4. 编写程序 5. 查看结果 1. 准备工作 需要启动NC,然后启动NC里面的Https客户端服务和签名服务. ...

  6. java中mvc开发模式_MVC开发模式

    1.1  MVC模式 1.1.1  MVC模式简介 MVC是一种架构型模式,它本身并不引入新的功能,只是用来指导我们改善应用程序的架构,使得应用的模型和视图相分离,从而得到更好的开发和维护效率. 在M ...

  7. webpack4.x实战七,生产模式和开发模式分开打包

    生产模式中,要求css和js文件尽可能小,因此需要把css和js文件压缩:开发模式中,要便于代码的调试,不能压缩css和js文件. 使用webpack-merge 安装插件webpack-merge ...

  8. Elasticsearch 中的“生产模式”和“开发模式”是什么

    1.开发模式 开发模式是默认配置(未配置集群发现设置),如果用户只是出于学习目的,而引导检查会把很多用户挡在门外,所以ES提供了一个设置项discovery.type=single-node.此项配置 ...

  9. java mvp模式_mvp开发模式是什么意思(半分钟了解mvp模式)

    MVC 模式 Model-View-Controller ,模型-视图-控制器模式 Model:针对业务模型,建立的数据结构,Model 与 View 无关,而与业务有关. View:Android ...

最新文章

  1. 「THUPC2018」赛艇 / Citing
  2. 剑指offer解题思路锦集11-20题
  3. JVM — 类加载机制
  4. CentOS 6.4配置TL-WN823N外置无线网卡
  5. idea修改html不能立刻生效,解决idea debug模式下修改代码却不能生效
  6. TensorFlow学习笔记(十三)TensorFLow 常用Optimizer 总结
  7. python删除符合条件的行_这十道经典Python笔试题,全做对算我输
  8. BugkuCTF-MISC题普通的二维码
  9. 容器编排技术 -- Kubernetes kubectl rollout pause 命令详解
  10. 【PHP面向对象(OOP)编程入门教程】10.__set(),__get(),__isset(),__unset()四个方法的应用...
  11. HTML5 WebRTC API无需网络获取本地IP
  12. SpringBoot系列(10):SpringBoot中的全局异常处理
  13. C - The Little Girl who Picks Mushrooms HDU - 4422
  14. Java 获取服务器ip地址
  15. mysql源码分析——InnoDB的内存应用整体架构源码
  16. MOS管寄生电容是如何形成的?
  17. 【前端知识之webpack】Loader和Plugin都是什么,有什么区别
  18. ArrayList 类 的简单应用
  19. 原子性 可见性 有序性_极简主义的内容可见性
  20. Android dex修复工具,安卓热修复----手动加载dex文件到设备并执行

热门文章

  1. 服务中的mysql叫什么名_mysql 服务器名是什么意思
  2. 如何获取抖音和快手直播间的直播流地址
  3. Vulnhub-medium_socnet
  4. 抖音取名,抖音打造人设(取名,头像,简介...):国仁楠哥
  5. 超级账本Fabric的架构与设计
  6. 超级账本Fabric 2.x 详细安装步骤及可能问题解决方式
  7. 邮箱无法登陆的解决方法
  8. takken android apk,银钢梦想之旅|TAKKEN 出击秘境探索第二季
  9. 密歇根安娜堡大学计算机专业硕士,2020年密歇根大学安娜堡分校硕士专业设置...
  10. 永恒之蓝漏洞复现MS17-010