相关介绍文章,放入本人的公众号(“开发谈”系列),方便读者查阅。

本开发者版本特点:

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

说明:与第一篇文章类似,调整一下格式,补充一些内容。

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

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

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

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

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

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

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

部署本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:ICBC_YQHL方法是否产生日志文件,是否保留pdf回单文档。日志文件是跟踪错误,向银行陈述错误的重要依据。虽然NetSafeClient本身可以产生日志文件,但NC2.0版本不建议长期开通-debugmax调试模式,很容易因日志文件过大而宕机;NC3.0属于循环存放日志,但发生错误后,要在10个日志文件中(每个可能10M大小)中查找具体信息,也是费时费力的。本软件的日志是每次调用接口产生一个日志,需要时能迅速定位。

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

添加端口后,访问正常:

提供的方法以及需要的参数,可以自行点击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接口。

如果回单文件是第一次制作,需要一个过程来校验获取数据制作。如果已经存在了,则直接返回该文件内容。

经测试,单个pdf回单文件在70K左右,经过编码后传输量大约100K。在Windows10的系统中,测试传输单个6M的二进制文件通过。

我用.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模式没有研究。

在十年的工作中,本人多次协助、指导客户开发,感觉客户自身在银企互联对接方面做的都不够完善和健壮。可能职业程序员都是赶任务,完成目标即可,基本没时间也没动力去回顾。本人技术水平一般,甚至可以说是落后的(毕竟工作岗位不是软件开发),但有比较充裕的时间专攻一面,挖空心思优化、提升自己的作品。这个开发者版,算是穷尽之力了。

如果自己没有开发网银转换工具:完美转换.NET,那就不会接触到那个电子表格商业控件。这个控件能脱离office环境快速读写excel文件,对于制作pdf文件大有帮助,说实话很想买一个版权。

如果自己没有参加2016年年底总行电子银行召开的银企互联研讨会,我就没有想法开发银企互联封装模块,也不会有大量的诸如下载电子回单信息的应用。封装模块能大量减轻重复性开发量,当然仅限于我自己的.NET程序,因为别人要理解、使用我的封装模块也是需要一定时间和精力的。这个模块是核心。

如果自己没遇到那个使用sap的客户,那就不会产生开发WebService中间件的想法。这个中间件是从.NET扩展到多语言的通道。

如果上述任意缺少一个,那么我也无缘制作包含PDF电子回单的开发者版本——实乃一大幸事。

做好事情在于自己,得到认可在于他人。工作中能有创新和突破,是件美妙的事情,至少精神上如此。

在这十年银企互联业务、技术支持中,接受过很多人帮助,也帮助过很多人。助人者助己,皆为有缘人。

十年,一剑,于予足矣。

Bug Report & Advise:fangrk_sz@qq.com

百度网盘:

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

Mr.Fang出品:银企互联(NC模式)开发者版本(.NET WebService中间件,Java、PHP、Python等跨语言测试通过)相关推荐

  1. 工行银企互联(NC模式)中间件开发模式

    相关NC中间件的文章,已经放到个人公众号("开发谈"栏目),扫码可以进入. 适用对象:自己动手开发银企互联的企业客户. 非官方产品,客户应优先根据官方开发手册自行开发. 开发者免责 ...

  2. Spark - OnYARN 模式搭建,并使用 Scala、Java、Python 三种语言测试

    一.SparkOnYarn搭建 安装前需要提前安装好 hadoop 环境,关于 HDFS 和 Yarn 集群的搭建可以参考下面我的博客: https://blog.csdn.net/qq_436929 ...

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

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

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

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

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

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

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

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

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

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

  8. 工行网银银企互联业务简介

    一.业务简述 银企互联是指我行系统和企业的财务系统相联接,企业直接通过财务系统办理账户管理.转账支付等银行服务,并可根据需要自行在其财务系统中定制更多个性化功能的网上银行业务,是目前工行提供给高端客户 ...

  9. 工商银行银企直联开发java,工商银行银企互联WebService中间件

    [实例简介]工商银行银企互联WebService中间件,可直接部署使用 [实例截图] NC中间件(个人自主开发) ├── ICBC.WebService.CommFriend.rar ├── WebS ...

最新文章

  1. 前期优达无人驾驶对课程评价
  2. ESLint 配置说明
  3. java学习笔记11--集合总结
  4. 事务注解 @Transactional
  5. flume从指定网络端口采集数据输出到控制台
  6. IaaS, PaaS和SaaS公司都做些什么
  7. Spring:Spring-过滤器Filter原理
  8. 电磁波传播相位是否会变化_电磁波相位关系释疑
  9. 计算机信息管理是学什么课程设计,毕业论文计算机专业学生信息管理系统(数据库课程设计)...
  10. 脱离 Windows 完全使用 Linux你花了多少时间适应?
  11. 一个实用的小工具——键盘映射KeyTweak
  12. 神经网络——基础思想
  13. java中的线程模型_Java 线程模型
  14. html导航栏可以展开的下拉菜单,html导航栏下拉菜单如何制作
  15. 椭圆曲线上两种基本的运算:点集运算、P+Q详解
  16. 洛谷-P1883-函数最小值
  17. 如何获取android系统版本号
  18. 计算机多媒体培训总结,多媒体培训心得体会
  19. 机器学习中的分类算法
  20. @Transactional的七种事务传播行为

热门文章

  1. 考进中科院计算所:我的经历和体会
  2. JavaScript:从内存的角度图解 函数作用域及作用域链
  3. 微信小程序和用网易新闻api实现自己的微信小程序
  4. Android中相机(Camera)画面旋转角度分析:手机摄像头的“正向”、手机画面自然方向、相机画面的偏转角度
  5. java applet类开始博饼_中秋博饼demo
  6. repo代码减少git数据库下载
  7. 2012年终全球IT企业市值TOP25排行榜
  8. 2021年三季度中国医疗器械行业A股上市企业营收排行榜:东方生物每股收益高达32.66元(附热榜TOP100详单)
  9. 奇异的水果世界——水果篇
  10. 人工智能的伦理和价值观——阿西洛马人工智能原则(Asilomar AI Principles )