机动车合格证手机扫码开票实现方式

为方便公司开票人员准确快速的开票,通过两周的研究,码出此工具。
(试用下载地址https://download.csdn.net/download/super_farmers/12264989)
在此写下实现过程:

机动车合格证手机扫码开票实现方式_第1张图片
机动车合格证手机扫码开票实现方式_第2张图片
整个软件分为:
1.扫描二维码
手机扫描二维码使用DroidCam软件实现,DroidCam可以把手机做为二维码采集设备(由于机动车二维码的复杂性,手机摄像头像素越高越好);
首先在安装DroidCam软件的电脑端:
机动车合格证手机扫码开票实现方式_第3张图片
DroidCam可以使用wifi/usb两种方式连接手机,本文中使用wifi连接方式(方便);
接着安装手机端:
机动车合格证手机扫码开票实现方式_第4张图片
手机端安装好以后可以在电脑端输入手机端显示的IP地址,测试连接是否正常
在程序中使用下面代码读取摄像头列表会得到:DroidCam字串开头的四个设备,除编号为3的设备,其它均可使用。

    QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
        foreach (const QCameraInfo &cameraInfo, cameras) {
     

            ui->Camera_List->addItem(cameraInfo.description());
        }
获取到设备以后,显示视频及截图代码:
QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
    foreach (const QCameraInfo &cameraInfo, cameras) {
     
        if (cameraInfo.description() == ui->Camera_List->currentText())
        {
     
        	// 摄像头实例
            m_Camera = new QCamera(cameraInfo);
			// 模式、视频采集器
            m_Camera->setCaptureMode(QCamera::CaptureStillImage);
            m_Camera->setViewfinder(m_ViewFinder);
			// 开始获取视频
            m_Camera->start();
            // 定时器定时截图 m_ImageCapture->capture(QDir::currentPath() + "/code");
            m_Timer->start(2000);

            // 截图
            m_ImageCapture = new QCameraImageCapture(m_Camera);
            m_ImageCapture->setCaptureDestination(QCameraImageCapture::CaptureToFile);
            // 由slot_imageSaved槽处理截取的图像
            connect(m_ImageCapture, SIGNAL(imageSaved(int,QString)), this, SLOT(slot_imageSaved(int,QString)));
        }
    }
至此扫码功能实现完成。

2.识别二维码
Qt识别二维码有QZxing库(),经测试,这个库对机动车二维码无能为力,最终选择一个商业二维码识别控件PsyQrDcd这个要注册授权的,不然识别出只有"AAAAA…"(http://www.psytec.co.jp/),这个识别率很高,速度也非常快。在这一步由于使用第三方库,没什么好说的,使用函数DecodePictureFile设置二维码文件路径,GetDecodeData函数读取结果。
3.数据解密
这一步是本软件最核心的一步,因防伪等各种原因,二维码读取到的内容为des加密后base64编码的一串文本。解码不了数据,一切白搭。这里可以调用大神做的接口,没有解密能力的可以参考(https://vehcode.scznnet.cn:449/QRcodeDoc.html#15614)。调用接口需要编译了openssl的Qt版本,因为接口是https协议的,我之前编译的Qt没有openssl,重新编译了下QT(好崩溃,需要静态编译的同学编译openssl一定不要加–debug选项,不然你得重编译);数据解密后得到64个字段,开具发票只用到其中10个字段(生产企业名称|车辆类型|车辆型号|产地|合格证号|发动机号码|车辆识别号VIN|吨位|限乘人数|车辆名称),其中车辆类型对应开票系统中产品分类名称,用此名称从产品分类名称中获取分类编码,这个分类编译在生成发票的时候要用。
4.生成发票
依照《增值税发票税控开票软件数据接口规范3.0》中描述的机动车发票导入接口生成XML文档,模板如下:

<?xml version="1.0" encoding="gbk"?>
<business>
  <body>
    <djh>djh</djh>                    	//单据号(30字符)
	<bmb_bbh>bmb_bbh</bmb_bbh>     		//编码表版本号(20个字符)
    <fpdm>fpdm</fpdm>                	//发票代码(10个字符)
    <fphm>fphm</fphm>                	//发票号码(8个字符)
    <gfdwmc>gfdwmc</gfdwmc>         	//购方单位名称(72个字符)
    <sfzhm>sfzhm</sfzhm>            	//身份证号码/组织机构代码(22个字符)
    <gfdwsbh>gfdwsbh</gfdwsbh>     		//购方单位识别号(20个字符)
    <cllx>cllx</cllx>                	//车辆类型(40个字符)
    <cpxh>cpxh</cpxh>                	//厂牌型号(60个字符)
    <cd>cd</cd>                       	//产地(32个字符)
    <hgzh>hgzs</hgzh>                	//合格证书(50个字符)
    <jkzmsh>jkzmsh</jkzmsh>         	//进口证明书号(36个字符)
    <sjdh>sjdh</sjdh>                	//商检单号(32个字符)
    <fdjhm>fdjhm</fdjhm>            	//发动机号码(60个字符)
	<clsbdh>clsbdh</clsbdh>         	//车辆识别代号(23个字符)
	<scqymc>scqymc</scqymc>         	//生产企业名称(80个字符)
    <jshj>jshj</jshj>                	//价税合计
    <dh>dh</dh>                       	//电话(40个字符)
    <zh>zh</zh>                       	//账号(40个字符)
    <dz>dz</dz>                       	//地址(80个字符)
    <khyh>khyh</khyh>                	//开户银行(80个字符)
    <zzssl>zzssl</zzssl>            	//增值税税率(实际税率)
    <zzsse>zzsse</zzsse>            	//增值税税额
    <bhsj>bhsj</bhsj>                	//不含税价
    <dw>dw</dw>                       	//吨位(8个字符)
	<xcrs>xcrs</xcrs>                	//限乘人数(12个字符)
	<spbm> spbm</spbm>					//商品编码(19个字符)
	<zxbm>zxbm </zxbm>					//自行编码(20个字符)
	<yhzcbs>yhzcbs</yhzcbs>				//优惠政策标识(1个字符)  0:不使用,1:使用
	<lslbs>lslbs</lslbs> 				//税率标识空(1个字符):非零税率,0:出口退税,1:免税,2:不征收,3普通零税率
	<zzstsgl>zzstsgl</zzstsgl>			//增值税特殊管理(50个字符)
  </body>
</business>
单据号可自定义;编码版本为税收分类编码的版本号,可以在最新税收分类编码文档中获取,发票代码,发票号码留空,增值税税率(13% 要填入 0.13)
这里有个小写金额转大写的功能,,自行实现耗时一下午(开始想的太简单了)只实现 了最大千亿的转换(应该够了吧),代码奉上:
QString Invoice::AmountsConverted(double amount)
{
     
    QStringList cnNumber = {
     "零","壹","贰","叁","肆","伍","陆","柒","捌","玖"};

    // 拆分整数与小数部分
    QString strAmount, strInteger, strDecimal;  // 金额字符串, 整数部分, 小数部分
    strAmount.setNum(amount, 'f', 2);
    strInteger = strAmount.split('.').at(0);
    strDecimal = strAmount.split('.').at(1);

    QStringList strlInteger, strlDecimal;
    strlInteger = strInteger.split("");
    strlInteger.removeFirst(); strlInteger.removeLast();    //移除首尾空值
    strlDecimal = strDecimal.split("");
    strlDecimal.removeFirst(); strlDecimal.removeLast();    //移除首尾空值


    // 构建汉字列表
    QStringList strlCnInteger, strlCnDecimal;
    foreach (QString str, strlInteger) {
     
        strlCnInteger << cnNumber.at(str.toInt());
    }
    foreach (QString str, strlDecimal) {
     
        strlCnDecimal << cnNumber.at(str.toInt());
    }

    // 整数部分添加单位
    int nCount = strlInteger.size() - 1;     // 总共多少位
    bool yflag = false, sflag = false, bflag = false, qflag = false, wflag = false;
    int nStep = 0;
    for(int i = nCount; i >= 0; i--)
    {
     
        switch (nStep++)
        {
     
        case 0:         // 元
            if (strlInteger.at(i) == "0") strlCnInteger.removeAt(i);
            else
                yflag = true;
            break;
        case 1:         // 拾
            if (strlInteger.at(i) == "0")
            {
     
                if (!yflag)
                    strlCnInteger.removeAt(i);
            }
            else if (strlInteger.at(i) != "0")
                strlCnInteger.insert(i + 1, "拾"), sflag = true;
            break;
        case 2:         // 百
            if (strlInteger.at(i) == "0")
            {
     
                if (!yflag && !sflag)
                    strlCnInteger.removeAt(i);
                if (yflag && !sflag)
                    strlCnInteger.removeAt(i);
            }
            else if (strlInteger.at(i) != "0")
                strlCnInteger.insert(i + 1, "佰"), bflag = true;
            break;
        case 3:         // 仟
            if (strlInteger.at(i) == "0")
            {
     
                if (!yflag && !sflag && !bflag)
                    strlCnInteger.removeAt(i);
                if ((yflag || sflag) && !bflag)
                    strlCnInteger.removeAt(i);
            }
            else if (strlInteger.at(i) != "0")
                strlCnInteger.insert(i + 1, "仟"), qflag = true;
            break;
        case 4:         // 万
            if (!wflag)
            {
     
                if (strlInteger.at(i) == "0")
                {
     
                    if (!qflag)
                        strlCnInteger.removeAt(i);
                    strlCnInteger.insert(i, "万");
                    yflag = false;
                    wflag = true;
                }
                else
                    strlCnInteger.insert(i + 1, "万"), yflag = true, wflag = true;
            }
            else        // 亿
            {
     
                if (strlInteger.at(i) == "0")
                {
     
                    strlCnInteger.removeAt(i);
                    strlCnInteger.insert(i, "亿");
                    yflag = false;
                }
                else
                    strlCnInteger.insert(i + 1, "亿"), yflag = true;
            }
            nStep = 1; sflag = bflag = qflag = false;
            break;
        }
    }

    // 小数部分添加单位
    // 分
    if (strlDecimal.at(1) == "0")
    {
     
        strlCnDecimal.removeAt(1);
    }
    else
    {
     
        strlCnDecimal.insert(2, "分");
    }
    // 角
    if (strlDecimal.at(0) == "0")
    {
     
        if (strlDecimal.at(1) == "0")
            strlCnDecimal.removeAt(0);
    }
    else
    {
     
        strlCnDecimal.insert(1, "角");
    }

    QString result = strlCnInteger.join("") + "圆" + strlCnDecimal.join("") + "整";
    return result;
}

5.开票导入:
这里没什么说的,生成发票XML后,在开票界面导入就成。

X.openssl des加解密的学习:
在openssl中以evp模式使用des等加解密方便快捷,但一定要注意数据类型的转换,简要代码如下:

// 生成key及iv  len为返回key的长度,
int len = EVP_BytesToKey(/*方式*/, /*hex*/, NULL, /*密钥*/, /*密钥长度*/, /*强度*/, key, iv);
// ctx上下文结构申请内存 (在构造函数中)
m_evp_ctx = EVP_CIPHER_CTX_new();

// 加解密过程

// 释放dtx上下文结构内存 (在析构函数中)
EVP_CIPHER_CTX_free(m_evp_ctx);
// 加密
QString Common::des_EnCrypto(QString Text)
{
     
    QByteArray bText = Text.toUtf8();

    unsigned char *inText = (unsigned char*)bText.data();
    int inTextLen = bText.length();

    unsigned char *outText = (unsigned char*)malloc(inTextLen + 8);
    int outLen, outFinalLent, outTotalLen;

    EVP_CIPHER_CTX_reset(m_evp_ctx);

    int ret = EVP_EncryptInit(m_evp_ctx, EVP_des_cbc(), m_des_key, m_des_iv);
    if (ret != 1)
    {
     
        EVP_CIPHER_CTX_reset(m_evp_ctx);
        return QString();
    }

    ret = EVP_EncryptUpdate(m_evp_ctx, outText, &outLen, inText, inTextLen);
    if (ret != 1)
    {
     
        EVP_CIPHER_CTX_reset(m_evp_ctx);
        return QString();
    }
    else
    {
     
        EVP_EncryptFinal(m_evp_ctx, outText + outLen, &outFinalLent);
        outTotalLen = outLen + outFinalLent;
    }

    EVP_CIPHER_CTX_reset(m_evp_ctx);

    return QString::fromUtf8(QByteArray((char*)outText, outTotalLen).toHex());
}

// 解密
QString Common::des_DeCrypto(QString Text)
{
     
    QByteArray bText = QByteArray::fromHex(Text.toUtf8());
    unsigned char *inText = (unsigned char*)bText.data();
    int inTextLen = bText.length();

    unsigned char *outText = (unsigned char*)malloc(inTextLen + 8);
    int outLen, outFinalLent, outTotalLen;

    EVP_CIPHER_CTX_reset(m_evp_ctx);

    int ret = EVP_DecryptInit(m_evp_ctx, EVP_des_cbc(), m_des_key, m_des_iv);
    if (ret != 1)
    {
     
        EVP_CIPHER_CTX_reset(m_evp_ctx);
        return QString();
    }

    ret = EVP_DecryptUpdate(m_evp_ctx, outText, &outLen, inText, inTextLen);
    if (ret != 1)
    {
     
        EVP_CIPHER_CTX_reset(m_evp_ctx);
        return QString();
    }
    else
    {
     
        EVP_DecryptFinal(m_evp_ctx, outText + outLen, &outFinalLent);
        outTotalLen = outLen + outFinalLent;
    }

    EVP_CIPHER_CTX_reset(m_evp_ctx);

    return QString::fromUtf8((char*)outText, outTotalLen);
}

你可能感兴趣的:(开票系统二开)