✨ blade-bfi-api 新增发票云全票池发票查询能力(A801-1 rim契约,queryAllPages全量翻页)

This commit is contained in:
刘泉佳
2026-09-20 19:41:07 +08:00
parent 44a34f57f0
commit 31a039245c
7 changed files with 269 additions and 20 deletions
@@ -25,6 +25,8 @@
*/
package org.springblade.thirdparty.bfi.constant;
import java.util.Map;
/**
* 业财融合(BFI)常量类
*
@@ -37,6 +39,16 @@ public class BfiConstant {
*/
public static final String SUCCESS_CODE = "0";
/**
* 收票 rim/message 契约调用成功错误码
*/
public static final String RIM_SUCCESS_CODE = "0000";
/**
* A801-1 查询全票池发票 消息类型,固定值
*/
public static final String MESSAGE_TYPE_INVOICE_QUERY = "invoiceQuery";
/**
* 网关认证请求头名称
*/
@@ -376,4 +388,42 @@ public class BfiConstant {
* 预付信息单据类型:期初预付单
*/
public static final String PREPAY_BILL_TYPE_INIT_PAID = "ap_paidbill";
/**
* 发票类型编码 -> 名称,编码明细参考票易通文档《发票数据结构》
*/
public static final Map<String, String> INVOICE_TYPE_NAMES = Map.ofEntries(
Map.entry("1", "增值税电子普通发票"),
Map.entry("2", "增值税电子专用发票"),
Map.entry("3", "增值税普通发票"),
Map.entry("4", "增值税专用发票"),
Map.entry("5", "增值税普通发票(卷票)"),
Map.entry("7", "通用机打发票"),
Map.entry("8", "出租车票"),
Map.entry("9", "火车票"),
Map.entry("10", "飞机票"),
Map.entry("11", "其他票"),
Map.entry("12", "机动车销售统一发票"),
Map.entry("13", "二手车销售统一发票"),
Map.entry("14", "定额发票"),
Map.entry("15", "增值税电子普通发票(通行费)"),
Map.entry("16", "汽车票"),
Map.entry("17", "过路(过桥)费发票"),
Map.entry("19", "税收完税证明"),
Map.entry("20", "轮船票"),
Map.entry("21", "海关缴款书"),
Map.entry("23", "通用机打电子发票"),
Map.entry("24", "火车票退票凭证"),
Map.entry("25", "财政电子票据"),
Map.entry("26", "数电发票(普通发票)"),
Map.entry("27", "数电发票(增值税专用发票)"),
Map.entry("28", "数电发票(航空运输电子客票行程单)"),
Map.entry("29", "数电发票(铁路电子客票)"),
Map.entry("30", "海外发票"),
Map.entry("31", "海外收据"),
Map.entry("47", "增值税代扣代缴完税凭证"),
Map.entry("72", "数电发票(通行费发票)"),
Map.entry("83", "数电发票(机动车销售统一发票)"),
Map.entry("84", "数电发票(二手车销售统一发票)")
);
}
@@ -30,20 +30,11 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
/**
* 业财融合(BFI)A801-1 查询全票池发票数据参数
* <p>
* 请求示例:
* <pre>{@code
* {
* "queryParam": {
* "invoiceNo": "25532000000044538098"
* }
* }
* }</pre>
* queryParam 字段明细参考票易通文档 https://open.piaozone.com/api-145421056
* 收票 rim/message 契约请求体,字段明细参考票易通文档 https://open.piaozone.com/api-145421056
*
* @author Chill
*/
@@ -53,7 +44,15 @@ public class BfiInvoicePoolQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 【必填】查询参数,键值对形式,如 invoiceNo(发票号码),字段明细参考 https://open.piaozone.com/api-145421056
* 请求类型,固定值 invoiceQuery,由服务端填充
*/
private Map<String, Object> queryParam;
private String messageType;
/**
* 请求id,唯一标识,由服务端填充
*/
private String messageId;
/**
* 【必填】查询条件主体
*/
private BfiInvoicePoolQueryDataDTO data;
}
@@ -0,0 +1,139 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.thirdparty.bfi.pojo.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 业财融合(BFI)A801-1 查询全票池发票数据 查询条件主体
* <p>
* 字段明细参考票易通文档 https://open.piaozone.com/api-145421056
*
* @author Chill
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BfiInvoicePoolQueryDataDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 页码,从1开始
*/
private Integer pageNo;
/**
* 每页数量,最大500
*/
private Integer pageSize;
/**
* 开票日期起 yyyy-MM-dd
*/
private String invoiceDateStart;
/**
* 开票日期止 yyyy-MM-dd
*/
private String invoiceDateEnd;
/**
* 发票类型编码
*/
private String invoiceType;
/**
* 增值税发票状态
*/
private String invoiceStatus;
/**
* 发票代码
*/
private String invoiceCode;
/**
* 发票号码
*/
private String invoiceNo;
/**
* 增值税用途勾选状态[0未勾选,1已勾选,2勾选认证,3扫描认证,4预勾选]
*/
private String authenticateFlag;
/**
* 单据使用状态,多个用英文逗号分隔[1未用,30在用,60已用,65已入账]
*/
private String expenseStatus;
/**
* 核算组织编码数组
*/
private List<String> orgNumber;
/**
* 采集时间起 yyyy-MM-dd
*/
private String createTimeStart;
/**
* 采集时间止 yyyy-MM-dd(票开放平台原始拼写 creatTimeEnd)
*/
private String creatTimeEnd;
/**
* 采集用户编码
*/
private String creatorNo;
/**
* 购方税号
*/
private String buyerTaxNo;
/**
* 销方税号
*/
private String salerTaxNo;
/**
* 发票流水号数组
*/
private List<String> serialNos;
/**
* 勾选时间起 yyyy-MM-dd
*/
private String selectTimeStart;
/**
* 勾选时间止 yyyy-MM-dd
*/
private String selectTimeEnd;
/**
* 抵扣税期 YYYY-MM
*/
private String taxPeriod;
/**
* 更新时间起 yyyy-MM-dd HH:mm:ss
*/
private String updateTime;
/**
* 更新时间止 yyyy-MM-dd HH:mm:ss
*/
private String updateTimeEnd;
/**
* 是否查询发票附件[1是,0否]
*/
private String queryAttach;
}
@@ -104,6 +104,11 @@ public class BfiInvoicePoolVO implements Serializable {
*/
@JsonProperty("totalTaxAmount")
private String totalTaxAmount;
/**
* 税率(票开放平台顶层税率字段)
*/
@JsonProperty("taxRate")
private String taxRate;
/**
* 可用税额
*/
@@ -114,6 +119,11 @@ public class BfiInvoicePoolVO implements Serializable {
*/
@JsonProperty("buyerTaxNo")
private String buyerTaxNo;
/**
* 购方名称
*/
@JsonProperty("buyerName")
private String buyerName;
/**
* 销方税号
*/
@@ -278,10 +278,10 @@ public interface IBfiService {
BfiBillSaveResultVO pushClaimResult(BfiClaimPushDTO param);
/**
* A801-1 查询全票池发票数据
* A801-1 查询全票池发票数据(自动翻页汇总全量)
*
* @param param 查询参数,queryParam 键值对参考票易通文档
* @return 票池发票数据列表
* @param param 查询参数,messageType/messageId 由服务端填充;data.pageSize 必填,作为单页大小与翻页终止依据
* @return 票池发票数据全量列表
*/
List<BfiInvoicePoolVO> queryInvoicePool(BfiInvoicePoolQueryDTO param);
@@ -56,6 +56,7 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignBodyDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignItemDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceDownloadDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoicePoolQueryDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoicePoolQueryDataDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyPushBodyDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyPushDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyQueryDTO;
@@ -98,6 +99,7 @@ import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
/**
@@ -483,12 +485,29 @@ public class BfiServiceImpl implements IBfiService {
@Override
public List<BfiInvoicePoolVO> queryInvoicePool(BfiInvoicePoolQueryDTO param) {
if (param == null || param.getQueryParam() == null || param.getQueryParam().isEmpty()) {
if (param == null || param.getData() == null) {
throw new BfiException("查询BFI全票池发票失败,查询参数不能为空");
}
String accessToken = getAccessToken();
BfiResultVO<List<BfiInvoicePoolVO>> result = commonCall("全票池发票查询", () -> client.invoicePoolQuery(accessToken, param));
return result.getData() != null ? result.getData() : List.of();
param.setMessageType(BfiConstant.MESSAGE_TYPE_INVOICE_QUERY);
// A801-1 rim 契约仅返回当前页数据且无总数,统一通过通用翻页工具取全量,由调用方自行分页
BfiInvoicePoolQueryDataDTO data = param.getData();
long start = System.currentTimeMillis();
log.info("调用BFI接口 全票池发票查询开始 pageSize:{}", data.getPageSize());
List<BfiInvoicePoolVO> invoices = BfiUtil.queryAllPages(pageNo -> {
// 翻页工具逐页注入页码;rim 契约要求每次请求携带唯一 messageId
data.setPageNo(pageNo);
param.setMessageId(UUID.randomUUID().toString());
BfiResultVO<List<BfiInvoicePoolVO>> result = commonRimCall("全票池发票查询",
() -> client.invoicePoolQuery(getAccessToken(), param));
List<BfiInvoicePoolVO> rows = result.getData() != null ? result.getData() : List.of();
// rim 契约无 lastPage,包装为 BfiPageVO 借助"数据不满一页即末页"规则终止翻页
BfiPageVO<BfiInvoicePoolVO> page = new BfiPageVO<>();
page.setRows(rows);
page.setPageSize(data.getPageSize());
return page;
});
log.info("调用BFI接口 全票池发票查询结束 耗时:{}ms,全量 {} 条", System.currentTimeMillis() - start, invoices.size());
return invoices;
}
@Override
@@ -542,6 +561,34 @@ public class BfiServiceImpl implements IBfiService {
}
}
/**
* 收票 rim/message 契约公共调用方法,成功错误码兼容 0 与 0000
*
* @param type 调用类型描述
* @param supplier 实际调用
* @param <T> 响应数据类型
* @return 响应结果
*/
private <T> BfiResultVO<T> commonRimCall(String type, Supplier<BfiResultVO<T>> supplier) {
try {
BfiResultVO<T> result = supplier.get();
if (result != null && Boolean.TRUE.equals(result.getStatus())
&& (BfiConstant.SUCCESS_CODE.equals(result.getErrorCode()) || BfiConstant.RIM_SUCCESS_CODE.equals(result.getErrorCode()))) {
return result;
}
log.error("调用BFI接口 {}异常响应参数:{}", type, JSONUtil.toJsonStr(result));
String message = Optional.ofNullable(result).map(BfiResultVO::getMessage).filter(StringUtil::isNotBlank)
.orElseGet(() -> Optional.ofNullable(result).map(BfiResultVO::getErrorCode)
.map(errorCode -> "错误码 " + errorCode).orElse("响应为空或解析失败"));
throw new BfiException("调用BFI接口" + type + "异常 " + message);
} catch (BfiException e) {
throw e;
} catch (Exception e) {
log.error("调用BFI接口 {}异常", type, e);
throw new BfiException("调用BFI接口" + type + "异常", e);
}
}
/**
* 开放平台公共调用方法,校验 success 标识
*
@@ -60,6 +60,7 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignBodyDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignItemDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignRefDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoicePoolQueryDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoicePoolQueryDataDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceQueryDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveEntryDTO;
@@ -528,8 +529,11 @@ class BfiServiceImplTest {
BfiInvoicePoolVO invoice = new BfiInvoicePoolVO();
when(client.invoicePoolQuery(anyString(), any(BfiInvoicePoolQueryDTO.class)))
.thenReturn(success(List.of(invoice)));
BfiInvoicePoolQueryDataDTO data = new BfiInvoicePoolQueryDataDTO();
data.setInvoiceNo("25532000000044538098");
data.setPageSize(500);
BfiInvoicePoolQueryDTO param = new BfiInvoicePoolQueryDTO();
param.setQueryParam(Map.of("invoiceNo", "25532000000044538098"));
param.setData(data);
List<BfiInvoicePoolVO> rows = service.queryInvoicePool(param);
assertEquals(1, rows.size());
}