Skip to content
Bitzsoft.Integrationsbitzsoft.integrations

Concept

Finance 财务(嵌套域)

财务嵌套域完整手册——公共基础 FinanceException、Invoice 发票子域 7 方法 + OCR/税收编码能力探针、Tax 税务 6 接口细粒度拆分、Payroll 薪酬接口与各子域注册示例。

Last updated

Finance 是”域中有域”。公共基础包只提供共享的领域异常 FinanceException,下面分发票(Invoice)、税务(Tax)、薪酬(Payroll)三个子域,每个子域各自完整地走三层包模式。总聚合包把三个子域全部拉入。

本页是完整手册:从嵌套结构到三个子域各自的接口契约、能力探针、异常体系和注册方式。

为什么嵌套

发票、税务、薪酬三个业务虽然都属于财务范畴,但它们的 API 能力、业务流程差异很大:

  • 发票管开具、红冲、查验、交付;
  • 税务管申报、风险查询、退税;
  • 薪酬管算薪、个税、代发、社保。

把它们塞进一个扁平的三层结构,要么接口臃肿(单个接口塞 20+ 方法),要么厂商被迫实现一堆空方法。嵌套域让每个子域保持独立的接口契约和厂商实现,同时通过共享的 FinanceException 保持一致的错误格式。

域结构

Finance(公共基础)
│ └── FinanceException.cs ← 共享领域异常(domain = "Finance")
├── Finance.Invoice ← 发票子域
│ ├── Finance.Invoice.Baiwang ← 百望
│ ├── Finance.Invoice.Kingdee ← 金蝶
│ ├── Finance.Invoice.Nuonuo ← 诺诺
│ └── Finance.Invoice.All ← 发票聚合(3 厂商)
├── Finance.Tax ← 税务子域
│ └── Finance.Tax.QiXiangYun ← 企享云
├── Finance.Payroll ← 薪酬子域
│ └── Finance.Payroll.All ← 薪酬聚合(暂无厂商实现)
└── Finance.All ← 总聚合(拉入全部子域)

公共基础:FinanceException

FinanceException 继承 IntegrationExceptiondomain 固定为 "Finance",是三个子域异常的共同基类:

public class FinanceException : IntegrationException
{
public string ProviderName => Provider ?? string.Empty;
public new string? ErrorCode => base.ErrorCode;
public new string? ProviderMessage => base.ProviderMessage;
// 消息格式: "[{providerName}] {message}"
public FinanceException(string providerName, string message,
string? errorCode = null, string? providerMessage = null,
Exception? innerException = null) : base(domain: "Finance", ...) { }
}

InvoiceExceptionPayrollException 都继承 FinanceException(补充本子域上下文)。TaxException 是例外——它直接继承 System.Exception,不经过 FinanceException/IntegrationException,因为税务子域有独立的错误码体系。

子域对照表

子域抽象接口异常厂商实现聚合包
Invoice 发票IInvoiceProvider + 2 能力探针InvoiceException : FinanceException百望 / 金蝶 / 诺诺Finance.Invoice.All
Tax 税务6 个细粒度接口TaxException : Exception企享云
Payroll 薪酬IPayrollProviderPayrollException : FinanceException暂无Finance.Payroll.All
Finance 总聚合Finance.All

Invoice 发票子域

IInvoiceProvider 涵盖发票开具、红冲、作废、查询、查验、交付,共 7 个方法:

public interface IInvoiceProvider
{
string ProviderName { get; }
// 开票
Task<InvoiceIssueResult> IssueAsync(InvoiceIssueRequest request, CancellationToken ct = default);
Task<InvoiceIssueResult> RedFlushAsync(InvoiceRedFlushRequest request, CancellationToken ct = default);
Task CancelAsync(string? invoiceCode, string invoiceNumber, CancellationToken ct = default);
// 查询与查验
Task<InvoiceQueryResult> QueryAsync(DateTime? startDate = null, DateTime? endDate = null, int pageIndex = 1, int pageSize = 20, CancellationToken ct = default);
Task<InvoiceVerifyResult> VerifyAsync(InvoiceVerifyRequest request, CancellationToken ct = default);
Task<InvoiceInfo> GetDetailAsync(string? invoiceCode, string invoiceNumber, CancellationToken ct = default);
// 交付
Task DeliverAsync(InvoiceDeliveryRequest request, CancellationToken ct = default);
}

invoiceCode 参数在数电票场景传 null(数电票没有发票代码,只有号码)。IssueAsync 返回的 InvoiceIssueResult 在异步开票模式下可能 Invoice 字段为 null,需通过 QueryAsync 查询最终结果。

开票请求模型

InvoiceIssueRequest 的核心字段:

属性类型说明
OrderNostring业务订单号,每销方税号下唯一(幂等控制)
InvoiceTypeInvoiceType发票类型(数电票/专票/普票等 8 种)
BuyerBuyerInfo购方信息(名称、税号、邮箱、手机)
SellerTaxNumberstring?销方税号(多税号企业指定)
ItemsIReadOnlyList<InvoiceItem>发票明细行
CallbackUrlstring?异步回调地址
PushModeInvoiceDeliveryMethod?推送方式(邮件/短信/两者)

能力探针接口

供应商特有能力通过独立接口提供,使用前用 is 运算符检查(能力探针模式),而不是塞进主接口让所有厂商实现空方法:

探针接口方法能力
IInvoiceOcrProviderRecognizeInvoiceAsync / RecognizeAndVerifyAsync发票 OCR 识别与识别后查验
ITaxCodeMatchProviderMatchTaxCodeAsync税收分类编码智能匹配
// 使用前检查能力
if (invoice is IInvoiceOcrProvider ocr)
{
var result = await ocr.RecognizeInvoiceAsync(imageBytes);
// 处理 OCR 结果...
}

发票状态

InvoiceStatus 枚举覆盖开票全流程:

名称说明
0Pending待开票
1Issuing开票中
2Signing签章中
3Issued已开具
4Failed开票失败
5SignFailed签章失败
6Cancelled已作废
7Cancelling作废中
8RedFlushed已红冲

Invoice 厂商

厂商Provider IDDI 方法
百望BaiwangAddBitzsoftBaiwangInvoice()
金蝶KingdeeAddBitzsoftKingdeeInvoice()
诺诺NuonuoAddBitzsoftNuonuoInvoice()

三家是国内主流电子发票服务商,均支持数电票。InvoiceException : FinanceException,错误格式与其他财务子域一致。

Tax 税务子域

税务子域用细粒度多接口而非单一接口,按业务能力拆成 6 个独立接口。每个接口都只有 2-3 个方法,调用方按需注入:

接口覆盖场景方法数
ITaxLoginProvider税务局登录、短信验证码2
ITaxDeclarationProvider申报目录、提交、结果查询3
ITaxInvoiceProvider发票查验、OCR 识别、查询、详情4
ITaxDataQueryProvider企业风险、企业 360 数据查询2
ITaxCustomsProvider海关报关单、出口退税2
ITaxRegulationProvider政策法规分类查询、详情2

拆成 6 个接口的原因是税务系统的功能跨度太大——登录认证、申报、发票、风控、海关、法规查询完全是不同的业务域和 API 分组。单接口会让调用方和厂商都负担过重,细粒度接口让每个能力可独立注入和演进。

TaxApiResult<T>

所有税务接口方法统一返回 TaxApiResult<T>(注意:是可变属性 set,和其他域的 init 不同):

public class TaxApiResult<T>
{
public bool Success { get; set; }
public string? Code { get; set; }
public string? Message { get; set; }
public T? Data { get; set; }
public long? Timestamp { get; set; }
}

异步任务还有配套的 AsyncTaskResult<T>(含 IsCompleted / IsSuccess / Status / ErrorCode / ErrorMessage),用于申报等需要轮询的场景。

TaxException

税务子域的异常体系独立于 FinanceException——TaxException 直接继承 System.Exception

public class TaxException : Exception
{
public string? ErrorCode { get; } // 错误码
public string? ErrorDetail { get; } // 错误详情
public TaxException(string message, string? errorCode = null, string? errorDetail = null) : base(message) { }
}

配套的 TaxErrorCode 枚举提供标准化错误码:

名称说明
0Unknown未知错误
1001AuthenticationFailed认证失败
1002TokenExpired令牌过期
2001InvalidParameter无效参数
3000BusinessError业务错误
4001NetworkError网络错误
5001AsyncTaskTimeout异步任务超时
6001ConfigurationError配置错误

Tax 厂商

厂商Provider IDDI 方法实现接口
企享云QiXiangYunAddBitzsoftQiXiangYunTax()全部 6 个

目前只有企享云实现了这套接口。

Payroll 薪酬子域

IPayrollProvider 涵盖薪资计算、个税计算、薪资代发、社保管理、工资单推送,共 8 个方法:

public interface IPayrollProvider
{
string ProviderName { get; }
// 薪资计算
Task<PayrollCalculationResult> CalculateAsync(PayrollCalculationRequest request, CancellationToken ct = default);
Task<TaxCalculationResult> CalculateTaxAsync(TaxCalculationRequest request, CancellationToken ct = default);
// 代发
Task<SalaryDisbursementResult> DisburseAsync(SalaryDisbursementRequest request, CancellationToken ct = default);
Task<SalaryDisbursementResult> GetDisbursementStatusAsync(string batchId, CancellationToken ct = default);
// 社保
Task<SocialInsuranceResult> EnrollAsync(SocialInsuranceRequest request, CancellationToken ct = default); // 参保
Task<SocialInsuranceResult> SuspendAsync(SocialInsuranceRequest request, CancellationToken ct = default); // 停保
Task<SocialInsuranceResult> DeclareAsync(SocialInsuranceRequest request, CancellationToken ct = default); // 申报
// 工资单
Task SendPayslipAsync(string employeeId, string period, CancellationToken ct = default); // period 格式 yyyy-MM
}

社保的三个操作(EnrollAsync / SuspendAsync / DeclareAsync)共用同一个 SocialInsuranceRequest 入参,返回 SocialInsuranceResultPayrollException : FinanceException,与发票子域错误格式一致。

目前只有抽象层和聚合包,还没有厂商实现,属于预留架构。

注册

Invoice 单厂商

// ① 发票——百望
builder.Services.AddBitzsoftBaiwangInvoice(builder.Configuration.GetSection("Finance:Invoice:Baiwang"));
// ② 发票——金蝶
builder.Services.AddBitzsoftKingdeeInvoice(builder.Configuration.GetSection("Finance:Invoice:Kingdee"));
// ③ 发票——诺诺
builder.Services.AddBitzsoftNuonuoInvoice(builder.Configuration.GetSection("Finance:Invoice:Nuonuo"));

Tax 单厂商

// 税务——企享云
builder.Services.AddBitzsoftQiXiangYunTax(builder.Configuration.GetSection("Finance:Tax:QiXiangYun"));

子域聚合

// ① 发票全厂商聚合(百望/金蝶/诺诺按配置注册)
builder.Services.AddBitzsoftInvoiceAll(builder.Configuration, "Finance:Invoice");
// ② 薪酬聚合(当前无厂商,仅注册抽象层)
builder.Services.AddBitzsoftPayrollAll(builder.Configuration, "Finance:Payroll");

总聚合

// 拉入发票 + 税务 + 薪酬全部子域
builder.Services.AddBitzsoftFinanceAll(builder.Configuration, "Finance");

配置按子域分节:

{
"Finance": {
"Invoice": {
"Baiwang": { "AppKey": "...", "AppSecret": "...", "TaxNumber": "..." },
"Nuonuo": { "AccessToken": "...", "UserTax": "..." }
},
"Tax": {
"QiXiangYun": { "ApiKey": "...", "ApiSecret": "..." }
}
}
}

消费

发票开具

public class InvoiceService(IInvoiceProvider invoice)
{
public async Task<InvoiceIssueResult> IssueAsync()
{
return await invoice.IssueAsync(new InvoiceIssueRequest
{
OrderNo = "ORD-20260801-001",
InvoiceType = InvoiceType.DigitalNormal,
Buyer = new BuyerInfo { Name = "买方公司", TaxNumber = "91...", Email = "finance@example.com" },
Items = [new InvoiceItem { GoodsName = "咨询服务", Price = 10000m, Quantity = 1, TaxRate = 0.06m }],
PushMode = InvoiceDeliveryMethod.Email,
});
}
}

发票能力探针

public class InvoiceOcrService(IInvoiceProvider invoice)
{
public async Task<OCRResult?> RecognizeAsync(byte[] image)
{
// 使用前检查能力探针
if (invoice is not IInvoiceOcrProvider ocr) return null;
return await ocr.RecognizeInvoiceAsync(image);
}
}

税务风险查询(企享云)

public class TaxRiskService(ITaxDataQueryProvider dataQuery)
{
public async Task<TaxApiResult<object>> CheckRiskAsync(string taxNumber)
{
var result = await dataQuery.QueryEnterpriseRiskAsync(taxNumber);
if (!result.Success)
throw new TaxException(result.Message ?? "查询失败", errorCode: result.Code);
return result;
}
}

薪资计算(预留)

public class PayrollService(IPayrollProvider payroll)
{
public async Task<PayrollCalculationResult> CalculateAsync(PayrollCalculationRequest request)
{
return await payroll.CalculateAsync(request);
}
}

相关

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%