存在许多问题,在使用测试环境账号、参数时无法发现,直到上了生产环境才出现。在此将遇到的一个个问题汇集如下,以期对后来开发人员提供借鉴。
关于调用查询接口,查询支付结果,使用的时间段的问题:
(1).测试时使用本地的前置机,且一直使用招行商务人员提供的测试账号,发现支付时能够即时返回请求被受理的结果,却查询不到支付记录。经过询问银行技术人员,得知银行测试服务器时间不是当前时间,需要用一个工具软件查询测试服务器日期。使用此日期后查询到了支付结果。
(2).查询交易记录时需要注意,”起始日期和结束日期的间隔不能超过100天”。并且查询时间段应该至少包含支付日期的前一天及后一天,因为在0点附近的交易可能会处于前一天,也可能属于后一天。
另外,使用前置机,不需要报文格式转换插件,因为采用demo中所示的程序,我们使用的已经是格式三的报文了。
使用HTTP方式向前置机发送请求,在处理支付及查询的返回报文时,对示例程序作出少许修改,对多条结果处理的方式如下(以直接支付为例):
/**
* 处理返回的报文
* @param result 前置机返回的报文
*/
private List<NTQPAYRQZ> processResult(String result) {
List<NTQPAYRQZ> ntqpayrqzs = null;
if (result != null && result.length() > 0) {
XmlPacket pktRsp = XmlPacket.valueOf(result);
if (pktRsp != null) {
String sRetCod = pktRsp.getRETCOD();
if (sRetCod.equals("0")) {
/*start 批量支付封装返回的报文开始*/
Vector<Map> vector = pktRsp.getProperty("NTQPAYRQZ");
ntqpayrqzs = new ArrayList<NTQPAYRQZ>();
if(vector!=null){
for (Map map : vector) {
NTQPAYRQZ ntqpayrqz = new NTQPAYRQZ();
ntqpayrqz=(NTQPAYRQZ) DataUtil.transMap2Bean(map,ntqpayrqz);
ntqpayrqzs.add(ntqpayrqz);
}
}
/*end 批量支付封装返回的报文结束*/
/* 此处应为对多比交易返回结果的处理 */
//下面为单笔时的处理
int sectionSize = pktRsp.getSectionSize("NTQPAYRQZ");
Map propPayResult = pktRsp.getProperty("NTQPAYRQZ", 0);//由于本人在项目中是单笔支付的,所以最多会返回一条记录,故此处取0。
String sREQSTS = (String) propPayResult.get("REQSTS");
String sRTNFLG = (String) propPayResult.get("RTNFLG");
if (sREQSTS.equals("FIN") && sRTNFLG.equals("F")) {
logger.info("支付失败:"
+ propPayResult.get("ERRTXT"));
} else {
logger.info("支付已被银行受理(支付状态:" + sREQSTS + ")");
}
} else if (sRetCod.equals("-9")) {
logger.info("支付未知异常,请查询支付结果确认支付状态,错误信息:"
+ pktRsp.getERRMSG());
} else {
logger.info("支付失败:" + pktRsp.getERRMSG());
}
} else {
logger.info("响应报文解析失败");
}
}
return ntqpayrqzs;
}
备注:NTQPAYRQZ 为以返回报文的节点构造的类,以节点NTQPAYRQZ下的各个字段作为类的属性。
其中,用到的工具方法如下:
/**
* 取得指定接口的数据记录
* @param sSectionName
* @param index 索引,从0开始
* @return Map
*/
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 Map
*/
public Vector getProperty(String sSectionName){
if(data.containsKey(sSectionName)){
return (Vector)data.get(sSectionName);
}else{
return null;
}
}
// Map --> Bean 1: 利用Introspector,PropertyDescriptor实现 Map --> Bean
public static Object transMap2Bean(Map map, Object obj) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (map.containsKey(key)) {
Object value = map.get(key);
// 得到property对应的setter方法
Method setter = property.getWriteMethod();
setter.invoke(obj, value);
}
}
} catch (Exception e) {
logger.error("transMap2Bean Error:",e);
}
return obj;
}
(1).生产环境应用服务器连接不上前置机服务器
运维人员对于连接前置机的服务器IP进行了限制;另外,还需要财务人员远程登录前置机服务器,开启前置机;
(2).返回错误信息:有审批业务,不可直接经办
编写程序时忽略了接口文档中的BUSMOD字段值,采用了文档中的示例BUSMOD= 00001。对比参照之前生产环境前置机的报文,发现支付成功的报文中此字段值为00002,修改程序后成功。此字段表示“支付结算业务所采用的业务审批模式”。
而报文中的另外一个值:“业务类型”字段BUSCOD,指的是“支付、集团支付或内部转帐等”,如BUSCOD=N02031。这两个字段容易混淆,需特别注意,并且它们的长度也不一样。
(3).行内支付使用直接支付接口,返回错误信息:收方必须为商户账户
咨询招行技术人员得知是因为我们公司在招行的账户受到限制,收款方必须加入白名单才能打款。询问财务人员,确实如此,经加入白名单后,打款成功。
跨行未受到限制,估计是因为走的是央行的超级网银渠道,所以没有限制。
(4).跨行支付收款方行号的问题
跨行支付时需要输入收款行行号(网银贷记接口中的CDTBRD字段),只能输入总行的行号,输入支行的行号报错。咨询招行技术人员,回复是系统中没有支行的行号。与招行对接时商务人员会提供“人行网银互联(跨行清算系统)联行号信息.txt”,其中有各银行及对应的联行号。
在文档《招商银行直联系统开发指南version 3.9.0》的3.4及3.5节,有调用频率及并发性能的说明。
摘录如下:
3.4调用频率
请求调用间隔:本次请求调用与上次请求调用之间的时间差;
调用频率控制按接口种类区别控制:
经办类请求:两次经办类请求最小调用间隔为1秒;
查询类请求:两次查询类请求最小调用间隔为2秒;
通知类请求:两次通知类请求最小调用间隔为5秒。
当调用频率没有满足以上条件时(实际调用间隔小于最小调用间隔),系统默认会内部等待,等待时间为:最小调用间隔 — 实际调用间隔,等待完成后再处理该请求。
当调用频率满足以上条件时(实际调用间隔大于或等于最小调用间隔),系统立即处理该请求并转发到银行后台系统。
调用频率控制以登陆用户为单位,不同登陆用户之间不受影响。
3.5 并发性能
本系统支持多用户并发请求,同一用户的多个请求按照优先队列排队处理,为了避免请求等待时间过长,建议同一用户并发请求不要超过50个,登陆用户数没有限制。
备注:一台电脑只能安装一个招行前置机,多台电脑允许安装多个招行前置机。
开发、测试、上线此通道,前后经历约一个月,与银行商务及技术人员保持了电话及QQ沟通。感谢银行技术人员耐心的讲解及热情的帮助!