同大陆身份证验证一样,该部分是按照国家增值税发票代码的定制规则,进行发票代码验证,如果需要查验发票信息是否正确,应该通过第三方接口(大约一毛钱查验一次),或者直接上国家税务总局全国增值税发票查验平台进行查验。

目前能识别的增值税发票代码包含以下几类:增值税专用发票增值税普通发票(纸质非卷票)增值税普通发票(卷票)增值税电子普通发票。在类库中,增值税代码验证相关的代码均在NumberValidators.Invoices下,其包含接口定义以及具体实现。

IVATCodeValidator(增值税代码识别接口)定义如下:

    /// <summary>
/// 增值税发票代码验证接口
/// </summary>
public interface IVATCodeValidator <out TResult>: IValidator<TResult>
where TResult : VATCodeValidationResult, new()
{
/// <summary>
/// 用于验证的字典数据
/// </summary>
IValidationDictionary<int, string> Dictionary { get; set; }
/// <summary>
/// 生成增值税发票代码
/// </summary>
/// <param name="areaNumber">行政区划</param>
/// <param name="year">年份</param>
/// <param name="batch">批次</param>
/// <param name="kind">要生成的发票类型</param>
/// <returns></returns>
string GenerateVATCode(int areaNumber, ushort year, ushort batch, VATKind kind);
/// <summary>
/// 发票代码验证
/// </summary>
/// <param name="vatCode">待验证的发票代码</param>
/// <param name="kind">要验证的发票类型,不指定则传null</param>
/// <param name="minYear">允许的最小年份(注:2012年1月1日营改增开始上海试点)</param>
/// <returns></returns>
TResult Validate(string vatCode, VATKind? kind = null, ushort minYear = 2012);
}

增值税发票代码验证定义了两种验证结果

VATCodeValidationResult这是默认验证结果,其定义如下:

    /// <summary>
/// 增值税发票代码验证结果
/// </summary>
public class VATCodeValidationResult : ValidationResult
{
/// <summary>
/// 行政区划代码
/// </summary>
public int AreaNumber { get; internal set; }
/// <summary>
/// 行政区域名称
/// </summary>
public string AreaName { get; internal set; }
/// <summary>
/// 发票类型
/// </summary>
public VATKind? Category { get; internal set; }
/// <summary>
/// 印刷年份
/// </summary>
public int Year { get; internal set; }
/// <summary>
/// 印刷批次
/// </summary>
public int Batch { get; internal set; }
/// <summary>
/// 发票联次,仅10位长度和12位长度折叠票发票才有
/// </summary>
public int DuplicateNumber { get; internal set; }
}

VATCode10ValidationResult是在VATCodeValidationResult的基础上,额外定义了发票金额版本,其定义如下:

    /// <summary>
/// 增值税发票和普通(纸质)专有的验证结果
/// </summary>
public class VATCode10ValidationResult : VATCodeValidationResult
{
/// <summary>
/// 发票金额版本号,仅10位长度发票才有
/// </summary>
public AmountVersion AmountVersion { get; internal set; }
}

VATCode12ValidationResult是在VATCodeValidationResult的基础上,额外定义了增值税电子发票的细分类型,其定义如下:

    /// <summary>
/// 除增值税专项发票外的验证结果
/// </summary>
public class VATCode12ValidationResult : VATCodeValidationResult
{
/// <summary>
/// 增值税电子发票细分类型
/// </summary>
public ElectronicVATKind? ElectronicVATKind { get; set; }
}

可根据IsValid来判断验证是否成功,如果验证失败,Errors 属性则包含了验证失败的原因,具体的错误原因列表如下

        /// <summary>
/// 发票代码为空
/// </summary>
public const string Empty = "发票代码为空";
/// <summary>
/// 错误的发票代码
/// </summary>
public const string Error = "错误的发票代码";
/// <summary>
/// 发票年份超出允许的年份范围
/// </summary>
public const string YearOutOfRange = "发票年份超出允许的年份范围{0} ~ {1}";
/// <summary>
/// 发票发行区域识别失败
/// </summary>
public const string InvalidArea = "发票发行区域识别失败";
/// <summary>
/// 无效的发票类别
/// </summary>
public const string InvalidKind = "无效的发票类别";
/// <summary>
/// 发票类别错误,无法生成发票代码
/// </summary>
public const string GenerateWrongKind = "发票类别错误,无法生成发票代码";
/// <summary>
/// 无效实现
/// </summary>
public const string InvalidImplement = "未能找到或无效的 {0} 位发票代码实现";
/// <summary>
/// 长度不符
/// </summary>
public const string LengthOutOfRange = "发票代码非 {0} 位";

因为目前类库中已经完整收集了所有发票代码中支持的行政区划编号(可在航信官网上查看都有哪些区域存在税务局),所以暂时不再需要自行传递Dictionary来进行支持区域的修正。

目前IVATCodeValidator包含VATCode10Validator以及VATCode12Validator两种具体实现

  • VATCode10Validator 对应长度为10的发票代码,包含增值税专用发票、增值税普通发票
  • VATCode12Validator 对应长度为12的发票代码,包含增值税普通发票、增值税普通发票(卷票)、增值税电子普通发票
  • VATCodeValidatorHelper 为静态类,用于辅助验证,其内部简单的封装了按发票代码长度调用对应的IVATCodeValidator实现

使用例子如下

            Console.WriteLine("***增值税发票***");
var vat10Validator = new VATCode10Validator();
var vat12Validator = new VATCode12Validator();
Console.WriteLine("随机的增值税发票:" + vat10Validator.GenerateRandomNumber());
Console.WriteLine("生成指定的增值税专用发票:" + vat10Validator.GenerateVATCode(3700, 2017, 1, Invoices.VATKind.Special));
Console.WriteLine("生成指定的10位增值税普通发票:" + vat10Validator.GenerateVATCode(1100, 2017, 2, Invoices.VATKind.Plain));
Console.WriteLine("生成指定的12位增值税普通发票:" + vat12Validator.GenerateVATCode(1100, 2018, 6, Invoices.VATKind.Plain));
Console.WriteLine("随机的增值税电子/卷票/普票:" + vat12Validator.GenerateRandomNumber());
string[] vatArr = { "031001600311", "3100153130", "011001800304" };
foreach (var vat in vatArr)
{
var valid = VATCodeValidatorHelper.Validate(vat, minYear: 2012);
Console.WriteLine("{0}验证结果:{1} 类型{2} 行政区划名称({3}) 验证结果类型:{4}", vat, valid.IsValid, valid.Category, valid.AreaName, valid);
}

PS:目前1.0版本中VATCode12Validator未支持12位的增值税普通发票以及收费公路通行费增值税电子发票,如果需要支持,需从git上下载代码后自行生成dll

【NumberValidators】增值税发票代码验证的更多相关文章

  1. odoo开发笔记 -- 官方模块一览表

    模块名称 技术名称 作者 电子发票管理 account OpenERP SA 会计与财务 account_accountant OpenERP SA 合同管理 account_analytic_ana ...

  2. 【NumberValidators】大陆身份证验证

    需要说明的是这里的大陆身份证识别并不是公安局联网的识别,而是按国标GB 11643进行的验证,所以其验证结果只能说符合国标规范,但不能保证该身份证一定真实存在,如果你实际需求是希望身份证一定真实存在, ...

  3. 【NumberValidators】工商营业执照号码和统一社会信用代码验证

    从本质上讲,工商营业执照号码和统一社会信用代码是两套完全不一样的编码规则,识别结果也仅有行政区划部分为两者共有,但因为这两种编码同时存在的原因,所以如果需要在系统中唯一标志一家企业时,还是可以通过工商 ...

  4. 【NumberValidators】类库介绍

    NumberValidators是一个用于验证中国大陆证件.号码是否符合国家标准的类库,因为该类库在昨日已经正式发布1.0.0版本至nuget,所以在此介绍下该类库的具体功能. NumberValid ...

  5. 【探索】无形验证码 —— PoW 算力验证

    先来思考一个问题:如何写一个能消耗对方时间的程序? 消耗时间还不简单,休眠一下就可以了: Sleep(1000) 这确实消耗了时间,但并没有消耗 CPU.如果对方开了变速齿轮,这瞬间就能完成. 不过要 ...

  6. C# 中参数验证方式的演变

    一般在写方法的时候,第一步就是进行参数验证,这也体现了编码者的细心和缜密,但是在很多时候这个过程很枯燥和乏味,比如在拿到一个API设计文档的时候,通常会规定类型参数是否允许为空,如果是字符可能有长度限 ...

  7. Yii1.1的验证规则

    在Yii1.1的数据验证是由CValidator完成,在CValidator中提供了各种基本的验证规则 <?php public static $builtInValidators=array( ...

  8. 【WCF】使用“用户名/密码”验证的合理方法

    我不敢说俺的方法是最佳方案,反正这世界上很多东西都是变动的,正像老子所说的——“反(返)者,道之动”.以往看到有些文章中说,为每个客户端安装证书嫌麻烦,就直接采用把用户名和密码塞在SOAP头中发送,然 ...

  9. PHP验证用户登录例子-学习笔记

    1.基本流程: 2.UML类图: 3.PHP代码: 3.1 index.php <?php /** * Created by PhpStorm. * User: andy * Date: 16- ...

随机推荐

  1. War(最短路+最大流)

    War http://acm.hdu.edu.cn/showproblem.php?pid=3599 Time Limit: 2000/1000 MS (Java/Others)    Memory ...

  2. 【校招面试 之 C/C++】第5题 C++各种构造函数的写法

    构造函数 ,是一种特殊的方法 .主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 .特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数 ...

  3. collections之deque【双向队列】与Queue【单向队列】

    今天来向大家介绍两个队列,一个是deque,双向队列,另外一个是Queue,单向队列,队列和堆栈不同,队列为先进先出,大家还需要注意一下,双向队列为collections模块中的类,而Queue为qu ...

  4. tmpFile.renameTo(classFile) failed解决

    完整异常: 严重: Servlet.service() for servlet [bjbr] in context with path [/HerPeisWechat] threw exception ...

  5. 四种强制类型转换的总结(const_cast、static_cast、dynamic_cast、reinterpreter_cast)

    四种强制类型转换的总结(const_cast.static_cast.dynamic_cast.reinterpreter_cast) 转载 2011年10月03日 23:59:05 标签: stru ...

  6. YAML教程

    一.简介 YAML是一种人们可以轻松阅读的数据序列化格式,并且它非常适合对动态编程语言中使用的数据类型进行编码.YAML是YAML Ain't Markup Language简写,和GNU(" ...

  7. Spark设计思想浅析

    Spark is no rocket science!——博主 了解分布式计算的朋友,一定知道DAG这样一个概念.其实我接触DAG也是在学习MapReduce时了解到的.(具体可查阅<大数据日知 ...

  8. bluez蓝牙测试工具

    http://blog.csdn.net/talkxin/article/details/50610984

  9. 深入浅出 JMS(三) - ActiveMQ 安全机制

    深入浅出 JMS(三) - ActiveMQ 安全机制 一.认证 认证(Authentication):验证某个实体或者用户是否有权限访问受保护资源. MQ 提供两种插件用于权限认证: (一).Sim ...

  10. 07 Maven 使用Nexus创建私服

    7. Maven 使用Nexus创建私服 私服不是 Maven 的核心概念,它仅仅是一种衍生出来的特殊的 Maven 仓库.通过建立自己的私服,就可以降低中央仓库负荷.节省外网带宽.加速 Maven ...