Skip to content
标签
欢迎扫码关注公众号

C# 发票助手二维码生成

之前一起吃饭听说了发票助手这个东西,可以生成发票抬头的二维码,扫码就可以开票了。

官方也有个小程序的【税务发票助手】,微信中搜这个名字就可以了。

我准备在自己的小程序中也尝试一下,本来觉得只要拼接一下生成二维码就好了,结果发现不少坑。

具体的文档参照 github 上的 便捷开票二维码应用规范.pdf

拼接倒是没什么,最坑的是 CRC-16 校验码那里。通过网上找到的CRC(循环冗余校验)在线计算计算的和官方程序生成的总是不一样。

在网上找到了好几个版本,大部分生成的和上面的在线计算结果一样。最终找到了一个 Java 版的国税总局发票助手二维码生成的 CRC 计算,跑起来之后发现终于验证码一样了。

于是改成了 C# 版,还加上了 QRCode 的生成方法(需要安装 QRCoder 包)。

C#版代码

csharp
public class ReceiptHelper
{
    private const int crc16 = 0x8005;

    private const string ReceiptQRCodePath = "Uploads\\Receipt\\";

    /// <summary>
    /// 生成发票抬头 QRCode 图片
    /// </summary>
    /// <param name="id">数据 ID</param>
    /// <param name="companyName">公司名称</param>
    /// <param name="taxNo">纳税人识别号</param>
    /// <param name="address">地址</param>
    /// <param name="phone">电话</param>
    /// <param name="bankName">开户行</param>
    /// <param name="account">账户</param>
    /// <returns>QRCode 图片名</returns>
    public static string GenerateQRCodeImage(Guid id, string companyName, string taxNo, string address, string phone, string bankName, string account)
    {
        string strQRCode = GenerateQRCode(companyName, taxNo, address, phone, bankName, account);
        string path = System.Web.Hosting.HostingEnvironment.MapPath("~/") + ReceiptQRCodePath + id.ToString() + ".png";

        QRCodeGenerator qrGenerator = new QRCodeGenerator();
        QRCodeData qrCodeData = qrGenerator.CreateQrCode(strQRCode, QRCodeGenerator.ECCLevel.Q);
        QRCode qrCode = new QRCode(qrCodeData);
        Bitmap qrCodeImage = qrCode.GetGraphic(15);
        qrCodeImage.Save(path, System.Drawing.Imaging.ImageFormat.Png);

        return path;
    }

    /// <summary>
    /// 生成发票抬头 QRCode 内容
    /// </summary>
    /// <param name="companyName">公司名称</param>
    /// <param name="taxNo">纳税人识别号</param>
    /// <param name="address">地址</param>
    /// <param name="phone">电话</param>
    /// <param name="bankName">开户行</param>
    /// <param name="account">账户</param>
    /// <returns>QRCode 图片名</returns>
    public static string GenerateQRCode(string companyName, string taxNo, string address, string phone, string bankName, string account)
    {
        string crcInput = $"{companyName}</>{taxNo}</>{address}{phone}</>{bankName}{account}</>"; // CRC 校验内容
        string crcNo = CalcCRC16(crcInput); // 计算 CRC16 校验码

        // 起始符 + 版本号 + base64(名称</>纳税人识别号</>地址电话</>开户行及账号</>CRC) + 结束符 
        StringBuilder sbQRCode = new StringBuilder();
        sbQRCode.Append("$");
        sbQRCode.Append("01");
        sbQRCode.Append(Convert.ToBase64String(Encoding.UTF8.GetBytes(crcInput + crcNo)));
        sbQRCode.Append(" $");

        return sbQRCode.ToString();
    }

    /// <summary>
    /// 计算 CRC16 校验码
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public static string CalcCRC16(string input)
    {
        sbyte[] inputs = GetSBytesForEncoding("UTF-8", input);

        int a = 0;
        for (int i = 0; i < inputs.Length; i++)
        {
            a = div(inputs[i], a);
        }

        sbyte r = 0;
        a = div(r, a);
        a = div(r, a);

        return a.ToString("X4");
    }

    private static int div(sbyte input, int a)
    {
        int temp;
        int data = input;
        for (int i = 0; i < 8; i++)
        {
            temp = a & 0x8000;
            a = a << 1;
            a = a & 0x0000ffff;

            int numIn = data & 0x80;
            numIn = numIn >> 7;

            a = a ^ numIn;

            if (temp == 0x8000)
            {
                a = a ^ crc16;
            }

            data = data << 1;
            a = a & 0x0000ffff;
        }

        return a;
    }


    private static sbyte[] GetSBytesForEncoding(string encoding, string s)
    {
        return GetSBytesForEncoding(Encoding.GetEncoding(encoding), s);
    }

    private static sbyte[] GetSBytesForEncoding(Encoding encoding, string s)
    {
        sbyte[] sbytes = new sbyte[encoding.GetByteCount(s)];
        encoding.GetBytes(s, 0, s.Length, (byte[])(object)sbytes, 0);
        return sbytes;
    }

}

QRCoder 的安装命令

Install-Package QRCoder

也可以通过 NuGet 工具搜索 QRCoder 安装。

Page Layout Max Width

Adjust the exact value of the page width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the page layout
A ranged slider for user to choose and customize their desired width of the maximum width of the page layout can go.

Content Layout Max Width

Adjust the exact value of the document content width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the content layout
A ranged slider for user to choose and customize their desired width of the maximum width of the content layout can go.