fujian_water_biz_doc/docs/superpowers/specs/2026-07-22-invoice-platform-account-config-design.md

13 KiB
Raw Blame History

发票平台账号配置设计

背景

当前发票模块已经有三类配置和业务数据:

  • biz_invoice:开票业务配置,按水司账户维护供应商、开票限额、开票类型、自动开票、扩展参数。
  • biz_invoice_taxrate:税收分类编码、税率、规格单位等明细配置。
  • biz_cust_invoice:客户发票抬头、税号、开户行、地址电话等客户资料。

这几类配置可以支撑“能不能开票、怎么组成发票明细、给谁开票”但没有独立维护“对接哪个发票平台账号、平台地址、APPID、销方税号、回调地址、密钥引用”的能力。诺诺联调已经证明接口链路可通但当前实现仍偏向代码或环境变量配置不适合运营后台长期维护也不适合未来接入方欣、百望、航信、用友等其他供应商。

目标

  1. 把发票平台账号做成通用配置,不绑定诺诺专有表结构。
  2. 允许一个水司账户配置多个发票平台账号,并由 biz_invoice 业务配置选择默认账号。
  3. 发票申请、查询、作废、冲红都能回溯当时使用的平台账号,避免后续配置变更影响已开票记录。
  4. 密钥不落业务库,只在业务库保存 secret_ref实际密钥从环境变量、Nacos、KMS 或 Vault 解析。
  5. 保持现有 /business/invoice 配置语义,让业务开票规则和平台账号解耦。

不纳入本次范围

  • 不一次性实现所有外部供应商客户端。
  • 不重做发票税率、客户抬头、费用组成等既有配置。
  • 不迁移历史发票记录的平台账号归属;历史记录可以继续按已有字段展示和查询。
  • 不把真实 APPKEY、私钥、证书内容保存到数据库。

核心概念

概念 存储位置 说明
供应商模板 supplier biz_invoice.supplier 现有枚举 InvoiceTemplateEnum,描述业务采用哪套开票模板或供应商类型。
平台编码 platform_code 新平台账号表、biz_invoicebiz_invoice_record 稳定技术路由键,例如 NUONUOFANGXINBAIWANGHANGXIN
平台账号编码 platform_account_code 新平台账号表、biz_invoicebiz_invoice_record 同一平台下的账号标识,例如 NUONUO_STAGE_339901999999142
平台密钥引用 app_key_secret_ref 新平台账号表 指向外部密钥来源的引用值,不保存密钥明文。

供应商模板和平台编码不强制一一对应。比如 NUONUO_QUANDIANNUONUO 可以复用 platform_code=NUONUO,通过模板字段决定业务明细映射,通过平台编码决定调用哪个技术客户端。

数据模型

新增表 biz_invoice_platform_account

建议新增 PostgreSQL DDL

CREATE TABLE IF NOT EXISTS biz_invoice_platform_account (
    id                      BIGINT       NOT NULL,
    account_id              BIGINT       NOT NULL,
    platform_code           VARCHAR(32)  NOT NULL,
    platform_account_code   VARCHAR(64)  NOT NULL,
    account_name            VARCHAR(100) NOT NULL,
    base_url                VARCHAR(255) NOT NULL,
    version                 VARCHAR(32),
    app_id                  VARCHAR(128) NOT NULL,
    app_key_secret_ref      VARCHAR(255) NOT NULL,
    seller_taxnum           VARCHAR(50)  NOT NULL,
    company_code            VARCHAR(64),
    callback_url            VARCHAR(512),
    default_buyer_email     VARCHAR(100),
    default_buyer_mobile    VARCHAR(20),
    extra_properties        TEXT,
    status                  SMALLINT     NOT NULL DEFAULT 1,
    tenant_id               BIGINT,
    creator                 VARCHAR(64),
    create_time             TIMESTAMP    NOT NULL DEFAULT now(),
    updater                 VARCHAR(64),
    update_time             TIMESTAMP    NOT NULL DEFAULT now(),
    deleted                 SMALLINT     NOT NULL DEFAULT 0,
    PRIMARY KEY (id)
);

CREATE UNIQUE INDEX IF NOT EXISTS uk_invoice_platform_account_code
    ON biz_invoice_platform_account(account_id, platform_code, platform_account_code)
    WHERE deleted = 0;

CREATE INDEX IF NOT EXISTS idx_invoice_platform_account_account
    ON biz_invoice_platform_account(account_id, status)
    WHERE deleted = 0;

字段说明:

字段 用途
account_id 水司账户 ID和现有开票配置保持同一归属维度。
platform_code 技术平台编码,作为客户端注册和路由键。
platform_account_code 账号编码,业务配置和发票记录只引用这个编码。
base_url 平台环境地址,例如诺诺测试环境 https://cmp-stage.nntest.cn
version 平台接口版本,诺诺当前使用 1.0.0
app_id 平台分配的应用标识。
app_key_secret_ref 密钥引用,例如 env:NUONUO_STAGE_APP_KEYnacos:invoice.nuonuo.stage.app-key
seller_taxnum 销方税号。
company_code 平台侧企业编码,有的平台不需要时为空。
callback_url 平台回调地址。
extra_properties 平台差异化配置JSON 字符串,例如门店编码、开票员、收款人、复核人。
status 启停状态,1 启用,0 停用。

扩展 biz_invoice

在现有开票业务配置表增加默认平台账号绑定:

ALTER TABLE biz_invoice ADD COLUMN IF NOT EXISTS platform_code VARCHAR(32);
ALTER TABLE biz_invoice ADD COLUMN IF NOT EXISTS platform_account_code VARCHAR(64);

CREATE INDEX IF NOT EXISTS idx_biz_invoice_platform_route
    ON biz_invoice(account_id, supplier, invoice_type, platform_code, platform_account_code)
    WHERE deleted = 0;

biz_invoice 继续表示开票规则。新增字段只表示这条业务规则默认走哪个平台账号。

复用 biz_invoice_record

发票记录中已有或计划中的 platform_codeplatform_account_code 字段继续保留。申请成功或进入平台处理中时,记录本次使用的平台编码和账号编码。后续查询、作废、冲红优先使用发票记录上的平台字段,不再根据当前业务配置重新选择账号。

密钥方案

数据库只保存 app_key_secret_ref,不保存真实密钥。后端增加统一解析接口:

public interface InvoicePlatformSecretProvider {
    String resolve(String secretRef);
}

第一阶段支持两种引用:

引用格式 解析来源 用途
env:NUONUO_STAGE_APP_KEY 系统环境变量 本地联调、流水线、临时测试。
nacos:invoice.nuonuo.stage.app-key 应用配置中心属性 测试环境、生产环境。

后续接入 KMS 或 Vault 时扩展该接口,不影响业务表结构和平台账号表结构。日志中禁止打印 appKey、解密后的密钥、secret_ref 解析结果。

路由流程

开票申请

  1. 根据 chargeIds 校验费用归属,并解析水司账户 accountId
  2. 读取 biz_invoice 中启用的业务配置,匹配 accountIdinvoiceType,并优先使用请求中的 supplier
  3. 如果请求没有传 supplier,且同一 accountId + invoiceType 只有一条启用配置,则使用该配置。
  4. 如果请求没有传 supplier,且存在多条启用配置,返回明确业务异常,要求前端传供应商模板。
  5. 使用业务配置中的 platform_codeplatform_account_code 查询启用的平台账号。
  6. InvoicePlatformClientRegistryplatform_code 获取客户端。
  7. 调用平台客户端,写入 biz_invoice_record.platform_codebiz_invoice_record.platform_account_codesys_request_no 和平台返回状态。

查询、作废、冲红

  1. 根据申请单号或发票记录 ID 读取 biz_invoice_record
  2. 优先使用记录中的 platform_codeplatform_account_code 查询平台账号。
  3. 如果历史记录缺少平台字段,且系统中只有一个可用平台账号,可以走兼容兜底;如果存在多个候选账号,返回需要人工确认的业务异常。
  4. platform_code 取客户端并执行对应操作。
  5. 更新发票记录状态,不覆盖历史平台字段。

服务分层

新增后端结构:

controller/admin/invoiceplatform/
  InvoicePlatformAccountController.java
  vo/InvoicePlatformAccountCreateReqVO.java
  vo/InvoicePlatformAccountUpdateReqVO.java
  vo/InvoicePlatformAccountPageReqVO.java
  vo/InvoicePlatformAccountRespVO.java
  vo/InvoicePlatformAccountTestReqVO.java
  vo/InvoicePlatformAccountTestRespVO.java

dal/dataobject/invoiceplatform/
  InvoicePlatformAccountDO.java

dal/mysql/invoiceplatform/
  InvoicePlatformAccountMapper.java

service/invoice/platform/config/
  InvoicePlatformAccountConfig.java
  InvoicePlatformSecretProvider.java
  EnvAndPropertyInvoicePlatformSecretProvider.java

service/invoice/platform/routing/
  InvoicePlatformAccountResolver.java
  InvoicePlatformClientRegistry.java
  InvoiceRouteContext.java

管理后台 API

方法 路径 说明
POST /business/invoice-platform-account/create 创建平台账号。
PUT /business/invoice-platform-account/update 更新平台账号,密钥只更新引用。
DELETE /business/invoice-platform-account/delete?id= 删除平台账号,已有发票记录不变。
GET /business/invoice-platform-account/get?id= 获取详情,返回 secret_ref,不返回密钥。
GET /business/invoice-platform-account/page 分页查询。
POST /business/invoice-platform-account/test-connect 使用账号配置发起平台连通性测试。

客户端注册

每个供应商客户端声明自己的平台编码:

public interface InvoicePlatformClient {
    String platformCode();
    ApplyResult apply(ApplyContext ctx);
    QueryResult query(QueryContext ctx);
    InvalidateResult invalidate(PostProcessContext ctx);
    RedInkResult redInk(PostProcessContext ctx);
}

注册表在启动时收集所有客户端:

@Component
public class InvoicePlatformClientRegistry {
    private final Map<String, InvoicePlatformClient> clients;

    public InvoicePlatformClientRegistry(List<InvoicePlatformClient> candidates) {
        this.clients = candidates.stream()
                .collect(Collectors.toUnmodifiableMap(
                        client -> client.platformCode().toUpperCase(Locale.ROOT),
                        Function.identity()));
    }

    public InvoicePlatformClient getRequired(String platformCode) {
        InvoicePlatformClient client = clients.get(platformCode.toUpperCase(Locale.ROOT));
        if (client == null) {
            throw exception(INVOICE_PLATFORM_CLIENT_NOT_EXISTS);
        }
        return client;
    }
}

这样平台选择从“Spring 只注入一个全局 InvoicePlatformClient”升级为“按记录或业务配置路由到对应客户端”。

诺诺账号示例

联调账号在平台账号表中表现为:

字段
account_id 对应水司账户 ID
platform_code NUONUO
platform_account_code NUONUO_STAGE_339901999999142
account_name 诺诺测试账号
base_url https://cmp-stage.nntest.cn
version 1.0.0
app_id convert
app_key_secret_ref env:NUONUO_STAGE_APP_KEY
seller_taxnum 339901999999142

真实密钥通过运行环境设置 NUONUO_STAGE_APP_KEY,不写入仓库、不写入数据库迁移脚本。

兼容策略

  1. 现有 biz_invoice.extra_properties 保留,用于开票业务模板的扩展参数。
  2. 现有诺诺 YAML 配置可以保留为本地联调兜底,但生产调用优先使用平台账号表。
  3. 平台账号表上线后,新增发票记录必须写入 platform_codeplatform_account_code
  4. 历史记录缺少平台字段时,只允许在唯一候选账号场景自动兜底。
  5. InvoiceApplyReqVO 增加可选 supplier 字段,前端在多供应商场景必须传值。

测试策略

层级 覆盖点
Mapper/Service 单测 平台账号创建、更新、禁用、唯一性、分页过滤。
Secret Provider 单测 env:nacos: 引用解析,缺失密钥报错,日志不包含密钥值。
Resolver 单测 单配置自动选择、多配置歧义报错、停用账号不可用、记录字段优先。
Registry 单测 多客户端注册、重复平台编码启动失败、未知平台编码报错。
InvoiceRecordService 单测 开票申请写入平台字段,查询/作废/冲红按历史记录路由。
Nuonuo 合约测试 请求体使用账号表配置组装,查询接口兼容空数组、对象数组和失败响应。
Stage Smoke 使用 NUONUO_STAGE_APP_KEY 环境变量执行诺诺测试环境查询接口,默认跳过。

落地顺序

  1. 新增平台账号表、DO、Mapper、VO、Controller、Service。
  2. 增加 biz_invoice 默认平台账号字段,并更新创建、更新、返回 VO。
  3. 新增密钥解析接口和运行时账号配置对象。
  4. 新增平台账号解析器和平台客户端注册表。
  5. 改造发票申请、查询、作废、冲红调用链。
  6. 把诺诺客户端从固定属性读取改为接收运行时账号配置。
  7. 增加单元测试、契约测试和默认跳过的联调 Smoke 测试。