银企直连|平安银行与招商银行的银企直连有种神似 还是熟悉的玩法 前置机、报文、加密解密、古老的报文 高门槛高服务费 对接传统银行

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!
关注公众号,进入学海无涯,高人带路模式!!银企直连再难,有人带路,轻松搞定

招商银行银企直连怎么收费

您好,银企直联主要是可以将企业网银与企业财务软件连接的方式,一般企业申请银企“直联”会收取服务费:开通10000元,每月基本服务费200元,或按协议价格收取。通过银企直联转账手续费同企业网银支付功能收费。

这个收费标准是否已经让一波小伙伴放弃了银企直连,如果每家银行都这个标准来算的话,全部接下来,成本也不少。这就是第三方支付的生存意义了。结合之前写的平安银行的银企直连,再来看招商银行的,会发现,基本是一相通的,莫非出自一个外包公司之手?

温故而知新

平安银行对接|银企对接扫雷排坑实战经验分享 感受一下财大气粗的银行对接 感受一下等长报文的痛苦 未曾经历无以感受他人在坑中崩溃状

熟悉的前置机

对比平安银行的对接玩法,再来看招商银行的银企直连,又看到了这么一个熟悉的前置机”神兽”,这跟支付宝对接、微信对接的玩法,出入相当的大。

demo

java版demo就4个文件HttpRequest.java、SaxHandler.java、SocketRequest.java、XmlPacket.java

import java.io.*;import java.net.*;import java.util.Map;import java.util.Properties;/** *  * HTTP通讯范例: 直接支付 *  */public class HttpRequest {  /**   * 生成请求报文   *    * @return   */  private String getRequestStr() {    // 构造支付的请求报文    XmlPacket xmlPkt = new XmlPacket("Payment", "USRA01");    Map mpPodInfo = new Properties();    mpPodInfo.put("BUSCOD", "N02031");    xmlPkt.putProperty("SDKPAYRQX", mpPodInfo);    Map mpPayInfo = new Properties();    mpPayInfo.put("YURREF", "201009270001");    mpPayInfo.put("DBTACC", "571905400910411");    mpPayInfo.put("DBTBBK", "57");    mpPayInfo.put("DBTBNK", "招商银行杭州分行营业部");    mpPayInfo.put("DBTNAM", "NEXT TEST");    mpPayInfo.put("DBTREL", "0000007715");    mpPayInfo.put("TRSAMT", "1.01");    mpPayInfo.put("CCYNBR", "10");    mpPayInfo.put("STLCHN", "N");    mpPayInfo.put("NUSAGE", "费用报销款");    mpPayInfo.put("CRTACC", "571905400810812");    mpPayInfo.put("CRTNAM", "测试收款户");    mpPayInfo.put("CRTBNK", "招商银行");    mpPayInfo.put("CTYCOD", "ZJHZ");    mpPayInfo.put("CRTSQN", "摘要信息:[1.01]");    xmlPkt.putProperty("SDKPAYDTX", mpPayInfo);    return xmlPkt.toXmlString();  }  /**   * 连接前置机,发送请求报文,获得返回报文   *    * @param data   * @return   * @throws MalformedURLException   */  private String sendRequest(String data) {    String result = "";    try {      URL url;      url = new URL("http://localhost:8080");      HttpURLConnection conn;      conn = (HttpURLConnection) url.openConnection();      conn.setRequestMethod("POST");      conn.setDoInput(true);      conn.setDoOutput(true);      OutputStream os;      os = conn.getOutputStream();      os.write(data.toString().getBytes("gbk"));      os.close();      BufferedReader br = new BufferedReader(new InputStreamReader(conn          .getInputStream()));      String line;      while ((line = br.readLine()) != null) {        result += line;      }      System.out.println(result);      br.close();    } catch (MalformedURLException e) {      e.printStackTrace();    } catch (UnsupportedEncodingException e) {      e.printStackTrace();    } catch (ProtocolException e) {      e.printStackTrace();    } catch (IOException e) {      e.printStackTrace();    }    return result;  }  /**   * 处理返回的结果   *    * @param result   */  private void processResult(String result) {    if (result != null && result.length() > 0) {      XmlPacket pktRsp = XmlPacket.valueOf(result);      if (pktRsp != null) {        String sRetCod = pktRsp.getRETCOD();        if (sRetCod.equals("0")) {          Map propPayResult = pktRsp.getProperty("NTQPAYRQZ", 0);          String sREQSTS = (String) propPayResult.get("REQSTS");          String sRTNFLG = (String) propPayResult.get("RTNFLG");          if (sREQSTS.equals("FIN") && sRTNFLG.equals("F")) {            System.out.println("支付失败:"                + propPayResult.get("ERRTXT"));          } else {            System.out.println("支付已被银行受理(支付状态:" + sREQSTS + ")");          }        } else if (sRetCod.equals("-9")) {          System.out.println("支付未知异常,请查询支付结果确认支付状态,错误信息:"              + pktRsp.getERRMSG());        } else {          System.out.println("支付失败:" + pktRsp.getERRMSG());        }      } else {        System.out.println("响应报文解析失败");      }    }  }  public static void main(String[] args) {    try {      HttpRequest request = new HttpRequest();      // 生成请求报文      String data = request.getRequestStr();      // 连接前置机,发送请求报文,获得返回报文      String result = request.sendRequest(data);      // 处理返回的结果      request.processResult(result);    } catch (Exception e) {      System.out.println(e.getMessage());    }  }}
import org.xml.sax.Attributes;import org.xml.sax.SAXException;import org.xml.sax.helpers.DefaultHandler;import java.util.*;/** *  * 招行XML报文解析类 * */public class SaxHandler extends DefaultHandler {  int layer=0;  String curSectionName;  String curKey;  String curValue;  XmlPacket pktData;  Map mpRecord;    public SaxHandler(XmlPacket data){    curSectionName = "";    curKey = "";    curValue = "";    pktData = data;    mpRecord = new Properties();  }  public void startElement(String uri, String localName, String qName,      Attributes attributes) throws SAXException {    layer++;    if(layer==2){      curSectionName = qName;    }else if(layer==3){      curKey = qName;    }  }  public void endElement(String uri, String localName, String qName)      throws SAXException {    if(layer==2){      pktData.putProperty(curSectionName, mpRecord);      mpRecord = new Properties();    }else if(layer==3){      mpRecord.put(curKey, curValue);      if(curSectionName.equals("INFO")){        if(curKey.equals("FUNNAM")){          pktData.setFUNNAM(curValue);        }else if(curKey.equals("LGNNAM")){          pktData.setLGNNAM(curValue);        }else if(curKey.equals("RETCOD")){          pktData.setRETCOD(curValue);        }else if(curKey.equals("ERRMSG")){          pktData.setERRMSG(curValue);        }      }    }    curValue = "";    layer--;  }  public void characters(char[] ch, int start, int length)      throws SAXException {    if(layer==3){      String value = new String(ch, start, length);      if(ch.equals("\n")){        curValue += "\r\n";      }else{        curValue += value;      }    }  }}
import java.io.*;import java.net.*;import java.util.*;/** * SOCKET通讯范例:查询账户信息 */public class SocketRequest {  /**   * 生成请求报文   *    * @return   */  private String getRequestStr() {    // 构造查询余额的请求报文    XmlPacket xmlPkt = new XmlPacket("GetAccInfo", "USRA01");    Map mpAccInfo = new Properties();    mpAccInfo.put("BBKNBR", "57");    mpAccInfo.put("ACCNBR", "571905400610301");    xmlPkt.putProperty("SDKACINFX", mpAccInfo);    return xmlPkt.toXmlString();  }  /**   * 连接前置机,发送请求报文,获得返回报文   *    * @param data   * @return   */  private String sendRequest(String data) {    // 连接前置机:Ip + port    String hostname = "localhost";    int port = 8080;    String result = null;    try {      InetAddress addr = InetAddress.getByName(hostname);      Socket socket = new Socket(addr, port);      // 设置2分钟通讯超时时间      socket.setSoTimeout(120 * 1000);      DataOutputStream wr = new DataOutputStream(socket.getOutputStream());      // 通讯头为8位长度,右补空格:先补充8位空格,再取前8位作为报文头      String strLen = String.valueOf(data.getBytes().length) + "        ";      wr.write((strLen.substring(0, 8) + data).getBytes());      wr.flush();      DataInputStream rd = new DataInputStream(socket.getInputStream());      // 接收返回报文的长度      byte rcvLen[] = new byte[8];      rd.read(rcvLen);      String sLen = new String(rcvLen);      int iSum = 0;      try {        iSum = Integer.valueOf(sLen.trim());      } catch (NumberFormatException e) {        System.out.println("报文头格式错误:" + sLen);      }      if (iSum > 0) {        System.out.println("响应报文长度:" + iSum);        // 接收返回报文的内容          int nRecv = 0, nOffset = 0;        byte[] rcvData = new byte[iSum];// data        while (iSum > 0) {          nRecv = rd.read(rcvData, nOffset, iSum);          if (nRecv < 0)            break;          nOffset += nRecv;          iSum -= nRecv;        }        result = new String(rcvData);        System.out.println("响应报文内容:" + result);      }      wr.close();      rd.close();    } catch (java.net.SocketTimeoutException e) {      System.out.println("通讯超时:" + e.getMessage());    } catch (IOException e) {      System.out.println(e.getMessage());    }    return result;  }  /**   * 处理返回的结果   *    * @param result   */  private void processResult(String result) {    if (result != null && result.length() > 0) {      XmlPacket pktRsp = XmlPacket.valueOf(result);      if (pktRsp != null) {        if (pktRsp.isError()) {          System.out.println("取账户信息失败:" + pktRsp.getERRMSG());        } else {          Map propAcc = pktRsp.getProperty("NTQACINFZ", 0);          System.out.println("账户" + propAcc.get("ACCNBR") + "的联机余额:"              + propAcc.get("ONLBLV"));        }      } else {        System.out.println("响应报文解析失败");      }    }  }  public static void main(String[] args) {    try {      SocketRequest request = new SocketRequest();      // 生成请求报文      String data = request.getRequestStr();      // 连接前置机,发送请求报文,获得返回报文      String result = request.sendRequest(data);      // 处理返回的结果      request.processResult(result);    } catch (Exception e) {      System.out.println(e.getMessage());    }  }}
import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.util.*;import javax.xml.parsers.ParserConfigurationException;import javax.xml.parsers.SAXParser;import javax.xml.parsers.SAXParserFactory;import java.io.ByteArrayInputStream;import org.xml.sax.SAXException;/** *  * 招行XML通讯报文类 * */public class XmlPacket{  protected String FUNNAM;  protected final String DATTYP="2";//报文类型固定为2  protected String LGNNAM;  protected String RETCOD;  protected String ERRMSG;  protected Map data; //<String,Vector>    public XmlPacket(){    data = new Properties();  }    public XmlPacket(String sFUNNAM){    FUNNAM = sFUNNAM;    data = new Properties();  }    public XmlPacket(String sFUNNAM, String sLGNNAM){    FUNNAM = sFUNNAM;    LGNNAM = sLGNNAM;    data = new Properties();  }    public String getFUNNAM() {    return FUNNAM;  }  public void setFUNNAM(String fUNNAM) {    FUNNAM = fUNNAM;  }  public String getLGNNAM() {    return LGNNAM;  }  public void setLGNNAM(String lGNNAM) {    LGNNAM = lGNNAM;  }  public String getRETCOD() {    return RETCOD;  }  public void setRETCOD(String rETCOD) {    RETCOD = rETCOD;  }  public String getERRMSG() {    return ERRMSG;  }  public void setERRMSG(String eRRMSG) {    ERRMSG = eRRMSG;  }    /**   * XML报文返回头中内容是否表示成功   * @return   */  public boolean isError(){    if(RETCOD.equals("0")){      return false;    }else{      return true;    }  }    /**   * 插入数据记录   * @param sSectionName   * @param mpData <String, String>   */  public void putProperty(String sSectionName, Map mpData){    if(data.containsKey(sSectionName)){      Vector vt = (Vector)data.get(sSectionName);      vt.add(mpData);    }else{      Vector vt = new Vector();      vt.add(mpData);      data.put(sSectionName, vt);      }      }    /**   * 取得指定接口的数据记录   * @param sSectionName   * @param index 索引,从0开始   * @return Map<String,String>   */  public Map getProperty(String sSectionName, int index){    if(data.containsKey(sSectionName)){      return (Map)((Vector)data.get(sSectionName)).get(index);    }else{      return null;    }  }    /**   * 取得制定接口数据记录数   * @param sSectionName   * @return   */  public int getSectionSize(String sSectionName){    if(data.containsKey(sSectionName)){      Vector sec = (Vector)data.get(sSectionName);      return sec.size();    }    return 0;  }    /**   * 把报文转换成XML字符串   * @return   */  public String toXmlString(){    StringBuffer sfData = new StringBuffer(        "<?xml version='1.0' encoding = 'GBK'?>");    sfData.append("<CMBSDKPGK>");    sfData        .append("<INFO><FUNNAM>"+FUNNAM+"</FUNNAM><DATTYP>"+DATTYP+"</DATTYP><LGNNAM>"+LGNNAM+"</LGNNAM></INFO>");    int secSize = data.size();    Iterator itr = data.keySet().iterator();    while(itr.hasNext()){      String secName = (String)itr.next();      Vector vt = (Vector)data.get(secName);      for(int i=0; i<vt.size(); i++){        Map record = (Map)vt.get(i);        Iterator itr2 = record.keySet().iterator();        sfData.append("<"+secName+">");        while(itr2.hasNext()){          String datakey = (String)itr2.next();          String dataValue = (String)record.get(datakey);          sfData.append("<"+datakey+">");          sfData.append(dataValue);          sfData.append("</"+datakey+">");        }        sfData.append("</"+secName+">");      }    }    sfData.append("</CMBSDKPGK>");        return sfData.toString();  }    /**   * 解析xml字符串,并转换为报文对象   * @param message   */  public static XmlPacket valueOf(String message) {    SAXParserFactory saxfac = SAXParserFactory.newInstance();    try {      SAXParser saxparser = saxfac.newSAXParser();      ByteArrayInputStream is = new ByteArrayInputStream(message.getBytes());      XmlPacket xmlPkt= new XmlPacket();      saxparser.parse(is, new SaxHandler(xmlPkt));      is.close();      return xmlPkt;    } catch (ParserConfigurationException e) {      e.printStackTrace();    } catch (SAXException e) {      e.printStackTrace();    } catch (IOException e) {      e.printStackTrace();    }      return null;  }}

C#版demo

相对java版,C#版的demo好歹是一个工程,用vs可以直接打开。

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net;using System.Net.Sockets;using System.IO;namespace CSharpTest{    class Program    {        static void Main()        {            //报文内容,用户自己根据接口组装            string strSendData = "<?xml version=\"1.0\" encoding = \"GBK\"?><CMBSDKPGK><INFO><FUNNAM>SDKCSFDFBRTIMG</FUNNAM><DATTYP>2</DATTYP><LGNNAM>QGZ01</LGNNAM></INFO><CSRRCFDFY0><EACNBR>216082647110001</EACNBR><BEGDAT>20170426</BEGDAT><ENDDAT>20170526</ENDDAT><RRCFLG>1</RRCFLG><RRCCOD>HD002016</RRCCOD></CSRRCFDFY0></CMBSDKPGK>";            //前置机部署的IP地址            string strIP = "127.0.0.1";            //Socket传输方式前置机的监听端口            int iSocketPort = 1080;            //Http传输方式前置机的监听端口            int iHttpPort = 8080;            string strReciveData = string.Empty;            Console.WriteLine("请选择通讯方式:1.Socket     2.HTTP");            string strType = Console.ReadLine();            if (strType.Equals("1"))            {                strReciveData = Send_Socket(strSendData, strIP, iSocketPort);            }            else if (strType.Equals("2"))            {                strReciveData = Send_Http(strSendData, strIP, iHttpPort);            }            else            {                Console.WriteLine("输入有误!");            }            Console.WriteLine(strReciveData);            Console.WriteLine("按Enter键退出!");            Console.ReadLine();        }        static private string Send_Socket(string strSendData, string strIP, int iPort)        {            string strDataLen = Convert.ToString(Encoding.Default.GetBytes(strSendData).Length).PadLeft(8, '0');    //长度8字节左补0            string sReturn = string.Empty;            IPAddress ip = IPAddress.Parse(strIP);    //前置机IP地址            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 30 * 1000);            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 30 * 1000);            try            {                socket.Connect(new IPEndPoint(ip, iPort));                byte[] dataToBeSend = Encoding.Default.GetBytes(strDataLen + strSendData);                socket.Send(dataToBeSend);                byte[] receivedData = new byte[3000];                int recvLen = socket.Receive(receivedData);                if (recvLen == 0)                {                    throw new Exception("返回数据为空");                }                sReturn = Encoding.Default.GetString(receivedData, 0, recvLen);            }            catch (Exception ex)            {                sReturn = ex.Message;            }            if (socket != null)            {                socket.Close();            }            return sReturn;        }        static private string Send_Http(string strSendData, string strIP, int iPort)        {            string strUrl = "http://" + strIP + ":" + iPort.ToString();    //前置机的地址和监听端口            string sReturn = string.Empty;            try            {                byte[] byteArray = Encoding.Default.GetBytes(strSendData);                HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(new Uri(strUrl));                webReq.Method = "POST";                webReq.ContentType = "application/x-www-form-urlencoded";                webReq.Timeout = 30 * 1000;                webReq.ContentLength = byteArray.Length;                Stream newStream = webReq.GetRequestStream();                newStream.Write(byteArray, 0, byteArray.Length);                newStream.Close();                HttpWebResponse response = (HttpWebResponse)webReq.GetResponse();                StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.Default);                sReturn = sr.ReadToEnd();                sr.Close();                response.Close();                newStream.Close();            }            catch (Exception ex)            {                sReturn = ex.Message;            }            return sReturn;        }    }}

 

招商银行的网银直连文档可以直接在官方查到
https://u.ebank.cmbchina.com/CmbBank_GenShell/UI/Help/DCBank2/Main.aspx
 对比招商银行的直连文档、再看看平安银行的直连文档,再对比第三方支付提供的sdk,对接体验,嗯嗯,还是第三方好!

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处

发表评论

电子邮件地址不会被公开。 必填项已用*标注