没看我之前的文章的先看一下《前期准备》,再来看这篇文章java农业银行-企业银行ERP接口开发(1-前期准备)https://blog.csdn.net/new_public/article/details/133882741
这篇文章我们主要讲:
批平台处理结果查询(IBAQ06)
最近一直想出这篇文章,奈何被一条sql搞了半个月,sql极其复杂,条件无敌多,最让人难受的是,开发环境没有数据,只有测试环境有,公司又不给我测试环境数据库的链接,搞得我看不了sql解释计划,不知道是走没走索引,只能调一下又上测试环境,结果出问题,又继续调.....
回归正题,这篇文章主要讲调用农行批量代发接口后,对代发结果进行查询。
先看请求报文,没有什么太多的特有字段,只有一个交易请求流水号。
这个流水号就是调用批量代发接口的时候,传的流水号<ReqSeqNo>请求方流水号</ReqSeqNo>,所以我在批量汇总代发接口文章里面强调把这个流水号记录到库表,后期用来查询交易结果的。
根据请求报文创建一个实体对象,继承公共请求对象(公共请求对象去第一篇文章拿)。
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* IBAQ06(批平台处理结果查询)请求报文字段
* */
@XmlRootElement(name = "ap")
public class IBAQ06RequestDTO extends RequestBaseEntity {
/**
* ???
* */
@XmlElement(name = "Corp")
private Corp corp;
@XmlAccessorType(XmlAccessType.FIELD)
public static class Corp {
/**
* 原批交易请求流水号
* */
@XmlElement(name = "CustomNo")
private String customNo;
public String getCustomNo() {
return customNo;
}
public void setCustomNo(String customNo) {
this.customNo = customNo;
}
}
public Corp getCorp() {
return corp;
}
public void setCorp(Corp corp) {
this.corp = corp;
}
}
然后看接口的应答报文
有没有注意到,标签名竟然是中文,好家伙,我都震惊了。
根据应答报文建立应答报文实体对象(里面有个文件对象,是用来解析应答报文文件的时候用的。由于查询IBAF04交易结果和查询IBBF23交易结果的返回文件里面的字段不一致,所以定义了两个文件对象,一个是IBAF04FileObject,一个是IBBF23FileObject,后面处理查询结果的时候,要区别一下是发起哪个接口进行交易的,从而选择不同的文件对象进行解析),并继承公共应答报文对象。get set 方法自己加,或者打个Lombok注解。
import javax.xml.bind.annotation.*;
import java.util.List;
/**
* IBAQ06(批平台处理结果查询)应答报文字段
* */
@XmlRootElement(name = "ap")
@XmlAccessorType(XmlAccessType.FIELD)
public class IBAQ06ResponseDTO extends ResponseBaseEntity {
/**
* 原批交易类别(批交易码)
* */
@XmlElement(name = "TransType")
private String transType;
/**
* ????
* */
@XmlElement(name = "BatchPlat")
private BatchPlat batchPlat;
/**
* IBAF04本行应答报文返回的文件对象
* */
@XmlTransient
private List<IBAF04FileObject> IBAF04FileObjectList;
/**
* IBBF23他行应答报文返回的文件对象
* */
@XmlTransient
private List<IBBF23FileObject> IBBF23FileObjectList;
@XmlAccessorType(XmlAccessType.FIELD)
public static class BatchPlat {
/**
* 批处理
* */
@XmlElement(name = "批处理")
private BatchHandler batchHandler;
/**
* 批处理信息类
* */
@XmlAccessorType(XmlAccessType.FIELD)
public static class BatchHandler {
/**
* 状态
* */
@XmlElement(name = "状态")
private String status;
/**
* 子状态
* */
@XmlElement(name = "子状态")
private String substate;
/**
* 描述
* */
@XmlElement(name = "描述")
private String describe;
/**
* 成功金额
* */
@XmlElement(name = "成功金额")
private String successAmt;
/**
* 成功笔数
* */
@XmlElement(name = "成功笔数")
private String successNum;
/**
* 失败金额
* */
@XmlElement(name = "失败金额")
private String failAmt;
/**
* 失败笔数
* */
@XmlElement(name = "失败笔数")
private String failNum;
/**
* 异常金额
* */
@XmlElement(name = "异常金额")
private String exceptionAmt;
/**
* 异常笔数
* */
@XmlElement(name = "异常笔数")
private String exceptionNum;
/**
* 落地金额
* */
@XmlElement(name = "落地金额")
private String groundAmt;
/**
* 落地笔数
* */
@XmlElement(name = "落地笔数")
private String groundNum;
}
}
// 文件对象
public static class IBAF04FileObject {
/**
* 客户编号
* */
private String customerCode;
/**
* 收款人姓名
* */
private String crAccName;
/**
* 收款人账号
* */
private String crBankNo;
/**
* 币种
* */
private String cur;
/**
* 账簿号
* */
private String accountBookCode;
/**
* 交易金额
* */
private Double amt;
/**
* 摘要
* */
private String excerpt;
/**
* 合约校验标志 01-强制校验 02-均不校验
* */
private String contractFlag;
/**
* 户名校验标志 0-否 1-是
* */
private String accountFlag;
/**
* 客户移动电话校验标识 0-否 1-是
* */
private String phoneFlag;
/**
* 移动电话
* */
private String phone;
/**
* 注册证件类型代码
* */
private String idCardType;
/**
* 证件号码
* */
private String idCardCode;
/**
* 处理结果
* */
private String handlerResult;
/**
* 返回码
* */
private String responseCode;
/**
* 返回码中文描述
* */
private String responseCodeDesc;
}
// 文件对象
public static class IBBF23FileObject {
/**
* 交易序号
* */
private String tradeNo;
/**
* 客户请求序号
* */
private String customerCode;
/**
* 系统流水号
* */
private String systemSerialNo;
/**
* 交易日期
* */
private String tradeDate;
/**
* 交易类型
* */
private String tradeType;
/**
* 交易金额
* */
private Double amt;
/**
* 处理结果
* */
private String handlerResult;
/**
* 结果描述
* */
private String resultDesc;
/**
* 对方户名
* */
private String crAccName;
/**
* 对方账号
* */
private String crBankNo;
/**
* 用途
* */
private String useTo;
}
}
来到业务场景,调用批量代发后,记录请求流水号,后面定时去刷交易结果就行了,大概发起后十分钟就可以去刷了,具体看批量付款的收款对象有多少个吧,太多就要等得久一些,前期测试一个收款账户,失败的话,两三分钟内出结果,成功的话可能得四五分钟这样。
AbcErpToIctSocket农行请求工具类(去第二篇文章,有专门解释,这里不解释这个类了)增加一个方法,专门用来查询批量代发结果的。
/**
* IBAQ06 批平台处理结果查询
* @transCode 批量代发交易接口码 IBAF04 | IBBF23
* */
public Map<String, Object> ibaq06(IBAQ06RequestDTO requestEntity, String transCode) {
if (requestEntity == null) {
log.error("IBAQ06报文请求对象不能为空!");
return null;
}
requestEntity.setCctransCode(CctransCodeEnum.IBAQ06.code());
requestEntity.setIsEncryption("0");
// 开始socket请求
Map<String, Object> socketResponseMap = socket(requestEntity, IBAQ06ResponseDTO.class);
// 解析报文返回的文件对象
Object responseObject = socketResponseMap.get("responseEntity");
if (responseObject != null) {
IBAQ06ResponseDTO ibaq06Response = (IBAQ06ResponseDTO) responseObject;
// 本行
if (StringUtils.equals(transCode, "IBAF04")) {
List<IBAQ06ResponseDTO.IBAF04FileObject> fileObjectList = new ArrayList<>();
this.analysisFileObject(ibaq06Response, fileObjectList, IBAQ06ResponseDTO.IBAF04FileObject.class);
if (!CollectionUtils.isEmpty(fileObjectList)) {
ibaq06Response.setIBAF04FileObjectList(fileObjectList);
}
} else if (StringUtils.equals(transCode, "IBBF23")) {
// 他行
List<IBAQ06ResponseDTO.IBBF23FileObject> fileObjectList = new ArrayList<>();
this.analysisFileObject(ibaq06Response, fileObjectList, IBAQ06ResponseDTO.IBBF23FileObject.class);
if (!CollectionUtils.isEmpty(fileObjectList)) {
ibaq06Response.setIBBF23FileObjectList(fileObjectList);
}
}
}
return socketResponseMap;
}
上面的socket方法去第二篇文章看,这里不说了,这里重点说analysisFileObject方法,这个是用来解析应答文件的。
/**
* 解析应答报文返回的文件
* @responseEntity 应答报文对象
* @fileObjectList 文件对象List,用来装载已经解析好的对象
* @fileObjectClass 文件对象class
* */
private <R extends ResponseBaseEntity> void analysisFileObject(R responseEntity, List fileObjectList, Class fileObjectClass) {
// 如果有文件标识,解析文件
if (responseEntity != null && responseEntity.getFileFlag() != null && responseEntity.getFileFlag() == 1) {
// 文件名
String batchFileName = responseEntity.getCmp().getBatchFileName();
// 取出文件类所有字段
Field[] fields = fileObjectClass.getDeclaredFields();
// 字段转成数组, 值为字段名
String[] respHeadArray = new String[fields.length];
for (int index = 0; index < fields.length; index++) {
respHeadArray[index] = fields[index].getName();
}
if (StringUtils.isNotBlank(batchFileName)) {
// 组合文件全路径
String fileFullPath = new StringBuffer(ictFileDownloadPath).append("/").append(batchFileName).toString();
File file = new File(fileFullPath);
// 如果文件存在
if (file.exists()) {
try {
// 读取文件 FileUtils 是 org.apache.commons.io 包下的
String fileContent = FileUtils.readFileToString(file, "GBK");
// 如果文件内容有值
if (StringUtils.isNotBlank(fileContent)) {
// 文件内容转成数组
String[] contentArray = fileContent.split("\\n");
ObjectMapper objectMapper = new ObjectMapper();
// 文件里面每一行各个文件的分隔符
String fieldSeparator = "\\|\\_\\|";
// 遍历文件内容
for (int contentIndex = 0; contentIndex < contentArray.length; contentIndex++) {
Map<String, String> map = new HashMap<>();
// 把每一行的值转成数组,后面拼一个空格,避免等会转换成数组的时候,如果后面都是|_||_|,会丢失后面的几个空字符串
String[] contentValueArray = new StringBuffer(contentArray[contentIndex]).append(" ").toString().split(fieldSeparator);
// 循环设置字段值
for (int i = 0; i < respHeadArray.length; i++) {
// uncapitalize 首字母小写 StringUtils 是org.apache.commons.lang3 包下的
map.put(StringUtils.uncapitalize(respHeadArray[i]), contentValueArray[i]);
}
// map转成实体
fileObjectList.add(objectMapper.convertValue(map, fileObjectClass));
}
}
} catch (IOException e) {
log.error("应答报文MFS文本文件解析失败", e);
}
}
}
}
}
接下来就是测试了,new一个请求报文对象,并设置交易流水号即可。
// 记得注入 AbcErpToIctSocket 工具类
// @Autowired
// private AbcErpToIctSocket abcErpToIctSocket;
IBAQ06RequestDTO ibaq06Request = new IBAQ06RequestDTO();
IBAQ06RequestDTO.Corp corp = new IBAQ06RequestDTO.Corp();
corp.setCustomNo("PO23111316564908xxxx");
ibaq06Request.setCorp(corp);
Map<String, Object> stringObjectMap = abcErpToIctSocket.ibaq06(ibaq06Request);
请求报文是这样子的
0285
<ap>
<CCTransCode>IBAQ06</CCTransCode>
<ProductID>ICC</ProductID>
<ChannelType>ERP</ChannelType>
<CorpNo></CorpNo>
<OpNo></OpNo>
<AuthNo></AuthNo>
<ReqSeqNo></ReqSeqNo>
<ReqDate>20231122</ReqDate>
<ReqTime>134006</ReqTime>
<Sign></Sign>
<Corp>
<CustomNo>PO23111316564908xxxx</CustomNo>
</Corp>
</ap>
应答报文是这样子的,由于填写信息不正确,所以失败了。
0695 <ap>
<Cmp>
<RespPrvData></RespPrvData>
<BatchFileName></BatchFileName>
<CmeSeqNo>SS20231122134000xxxx</CmeSeqNo>
</Cmp>
<BatchPlat>
<批处理>
<状态>1</状态>
<描述>CFRC48失败[EAPAYS1003-调用boe失败!-[rtbasic]输入过渡账户【273201*****0786】有误!]</描述>
</批处理>
</BatchPlat>
<CCTransCode>IBAQ06</CCTransCode>
<RespDate>20231122</RespDate>
<RespTime>134008</RespTime>
<RespSeqNo></RespSeqNo>
<RespSource>0</RespSource>
<RespCode>0000</RespCode>
<RespInfo>交易成功</RespInfo>
<RxtInfo>批量交易处理失败!</RxtInfo>
<Cme>
<RecordNum>0</RecordNum>
<FieldNum>0</FieldNum>
</Cme>
<FileFlag>0</FileFlag>
</ap>
对于应答报文的处理,我是这么写的,对应业务代码自己补齐。
// 记得注入 AbcErpToIctSocket 工具类
// @Autowired
// private AbcErpToIctSocket abcErpToIctSocket;
IBAQ06RequestDTO ibaq06Request = new IBAQ06RequestDTO();
IBAQ06RequestDTO.Corp corp = new IBAQ06RequestDTO.Corp();
corp.setCustomNo("PO23111316564908xxxx");
ibaq06Request.setCorp(corp);
Map<String, Object> responseObjectMap = abcErpToIctSocket.ibaq06(ibaq06Request);
// 查询结果前期判断
if (this.socketRequestAfterDetermine(responseObjectMap)) {
// 如果应答对象有值
if (responseObjectMap.get("responseEntity") != null) {
IBAQ06ResponseDTO ibaq06Response = (IBAQ06ResponseDTO) responseObjectMap.get("responseEntity");
IBAQ06ResponseDTO.BatchPlat.BatchHandler batchHandler = ibaq06Response.getBatchPlat().getBatchHandler();
if (StringUtils.equals(batchHandler.getStatus(), "H")) {
// 批处理状态为H,则说明正在处理中,稍后再查询
} else if (StringUtils.equals(batchHandler.getStatus(), "1")) {
// 批处理状态为1,则说明整批失败(全部失败)
} else if (StringUtils.equals(batchHandler.getStatus(), "5")) {
// 批处理状态为5,则说明交易处理完成
// 如果落地笔数或异常笔数都没有,则说明整批都交易完成,下次不用再定时查询了
if (StringUtils.equals(batchHandler.getExceptionNum(), "0") &&
StringUtils.equals(batchHandler.getGroundNum(), "0")) {
}
// 设置成功笔数,成功金额
Double.valueOf(batchHandler.getSuccessAmt());
Integer.parseInt(batchHandler.getSuccessNum());
// 失败金额,失败笔数
Double.valueOf(batchHandler.getFailAmt());
Integer.parseInt(batchHandler.getFailNum());
// 处理每条收款信息的结果,fileObjectList 是前面根据应答文件名获取文件解析后获得的,里面是每一个收款账户的收款信息以及交易状态
// 这里必须要确定前面是哪个接口发起的批量交易,这就要在发起批量交易时,记录交易码到数据库里面了
List fileObjectList = null;
// 本行
if (StringUtils.equals(批量代发接口交易码, "IBAF04")) {
fileObjectList = ibaq06Response.getIBAF04FileObjectList();
} else {
// 他行
fileObjectList = ibaq06Response.getIBBF23FileObjectList();
}
if (!CollectionUtils.isEmpty(fileObjectList)) {
fileObjectList.forEach(fileObject -> {
IBAQ06ResponseDTO.IBAF04FileObject ibaf04FileObject = null;
IBAQ06ResponseDTO.IBBF23FileObject ibbf23FileObject = null;
// 本行
if (StringUtils.equals(批量代发接口交易码, "IBAF04")) {
ibaf04FileObject = (IBAQ06ResponseDTO.IBAF04FileObject) fileObject;
} else {
// 他行
ibbf23FileObject = (IBAQ06ResponseDTO.IBBF23FileObject) fileObject;
}
// 客户编号 这里说一下,这个客户编号是我们批量代发的时候设置上的,这时候会原样返回,方便我们用于回写业务数据
String sourceId = ibaf04FileObject != null ? ibaf04FileObject.getCustomerCode() : ibbf23FileObject.getCustomerCode();
// 处理结果
String handlerResult = ibaf04FileObject != null ? ibaf04FileObject.getHandlerResult() : ibbf23FileObject.getHandlerResult();
// 判断这条记录是成功了还是失败 5-失败、E-不合法 这两种状态为明确失败;4-成功 为明确成功;其他状态为状态不确定,等下次查询
// 只有明确是成功还是失败了,再根据sourceId更新业务数据
if (IBAQ06HandlerStatusEnum.Fail.code().equals(handlerResult) ||
IBAQ06HandlerStatusEnum.FailE.code().equals(handlerResult) ||
IBAQ06HandlerStatusEnum.Success.code().equals(handlerResult)) {
// 每条收款人信息的交易返回码,以及返回码描述,可能是成功信息,也可能是失败原因
if (ibaf04FileObject != null) {
ibaf04FileObject.getResponseCode();
ibaf04FileObject.getResponseCodeDesc();
} else {
ibbf23FileObject.getResultDesc();
}
}
});
}
}
}
}
socketRequestAfterDetermine 请求后处理方法
/**
* 交易结果查询后,前期通用判断
* @resMap socket请求结果
* @return 是否请求成功
* */
// 有些代码我删掉了,自己完善
private boolean socketRequestAfterDetermine(Map<String, Object> resMap) {
if (resMap == null) {
// 查询出错
return false;
} else if (resMap.get("errorMessage") != null) { // 有错误信息
resMap.get("errorMessage").toString();
return false;
} else if (resMap.get("isAlreadyResq") == null) { // 没有发送socket请求
return false;
} else if (resMap.get("responseEntity") == null) { // 应答报文对象
if (resMap.get("responseMessage") != null) {
resMap.get("responseMessage").toString();
}
return false;
}
return true;
}
这里啰嗦一下,事务最好还是手动开启,每查一次交易结果,就开启一次事务,最后处理完业务数据后就提交事务,然后再开启事务查下一个批量代发交易结果,把它们都分开成独立的一样,因为下一次的查询跟前面的没有任何关系。
枚举类
/**
* IBAQ06 批量支付结果查询响应文件里面每条记录的具体支付状态
* */
public enum IBAQ06HandlerStatusEnum {
Success("4", "成功"),
Fail("5", "失败"),
FailE("E", "失败");
// 其他状态,为不确定状态
private String code;
private String title;
IBAQ06HandlerStatusEnum(String code, String title) {
this.code = code;
this.title = title;
}
public String code() {
return this.code;
}
public String title() {
return this.title;
}
}
好了,又是上班时间写文章,得抓紧干活了,不然明天就被骂了。
码字不易,于你有利,勿忘点赞。
一点飞鸿影下,青山绿水,白草红叶黄花