PHP 怎么电子发票

wen PHP项目 1

本文目录导读:

PHP 怎么电子发票

  1. 核心概念:你处理的是“XML”或“OFD”
  2. 场景一:作为“收票方”(报销/入账)
  3. 场景二:作为“开票方”(对接税局平台)
  4. 关键库与工具推荐(PHP生态)
  5. 避坑指南(实战经验)
  6. 总结代码框架(收票方入库)
  7. 实现建议

在 PHP 中处理电子发票(尤其是中国的全面数字化的电子发票,即“数电票”),通常不涉及生成发票(由税务系统生成),而是围绕获取、解析、验签、查验、归集这几个核心环节。

根据你的业务场景(是作为开票方对接税局,还是作为收票方处理报销),方案截然不同,以下是全流程的技术指南:


核心概念:你处理的是“XML”或“OFD”

中国的电子发票(数电票)底层是板式文件(OFD/PDF)和结构化数据文件(XML)。

  • OFD:国标版式文件(类似于PDF),用于打印和预览。
  • XML:包含发票全部结构化数据(抬头、金额、税额、购销方信息),是财务系统入账和验签唯一法定依据

PHP处理电子发票,本质上是处理 XML字符串OFD压缩包


场景一:作为“收票方”(报销/入账)

这是最常见的需求(企业内部报销系统、费控系统),你需要解析用户上传的电子发票。

A. 解析 XML(数电票)

数电票的XML文件可以直接通过PHP的 simplexml_load_string() 解析。

<?php
$xmlContent = file_get_contents('invoice.xml');
$xml = simplexml_load_string($xmlContent);
// 注册命名空间(数电票的命名空间是 urn:gb:266:2022-1)
$xml->registerXPathNamespace('inv', 'urn:gb:266:2022-1');
// 提取关键字段
$invoiceCode = $xml->xpath('//inv:InvoiceCode');
$totalAmount = $xml->xpath('//inv:TotalAmount');
$buyerName = $xml->xpath('//inv:BuyerName');
$sellerName = $xml->xpath('//inv:SellerName');
// 提取明细
$items = $xml->xpath('//inv:ItemList/inv:Item');
foreach ($items as $item) {
    $name = $item->xpath('//inv:Name')[0];
    $price = $item->xpath('//inv:Price')[0];
    // ... 循环获取
}
?>

B. 解析 OFD(关键点:OFD 是 ZIP 压缩包)

OFD 文件本质是一个 ZIP 压缩包,核心的发票数据在 OFD.xml 文件中。

  • 技术方案:推荐使用 ZipArchive 类(需开启 php_zip 扩展)。
<?php
$zip = new ZipArchive;
if ($zip->open('invoice.ofd') === TRUE) {
    // 1. 读取 OFD.xml (包含文件清单)
    $ofdXml = $zip->getFromName('OFD.xml');
    // 2. 解析OFD.xml,找到Document/XML的文件路径,通常是 Doc_0/Document.xml
    // 简易写法(需查具体路径):
    $docXml = $zip->getFromName('Doc_0/Document.xml');
    // 3. 用 SimpleXML 解析 $docXml,提取发票数据 (同上XML解析)
    $invoiceXml = simplexml_load_string($docXml);
    $zip->close();
}
?>

C. 发票验真(防伪)

重要安全提示:仅解析XML不够,必须验证发票是否被篡改。

  • 国家公示平台:调用 国家税务总局全国增值税发票查验平台 的接口(企业需申请接口权限)。
  • 本地验签:数电票的XML末尾包含Signature字段(数字签名),你可以使用 openssl_verify() 配合税局公布的CA根证书进行验签。
    // 伪代码示例
    $publicKey = openssl_pkey_get_public('file://path/to/ca.cer');
    $signature = base64_decode($xml->SignatureValue);
    $dataToVerify = $xml->CanonicalizedContent; // 需要按国标算法规范化
    $result = openssl_verify($dataToVerify, $signature, $publicKey, OPENSSL_ALGO_SHA256);
    if ($result === 1) {
        // 验签成功
    }

场景二:作为“开票方”(对接税局平台)

如果你是电商平台或企业系统,需要给客户自动开票。

技术路线

  1. 税局接口对接:需要向当地税务局申请“全电发票试点”接入资格,获取AppKey/Secret(密钥)。
  2. 组装XML:按照国家标准,组装开票XML报文(包含销方、购方、明细、价税分离)。
  3. 数字签名:使用保险库(UKey)或云证书,使用 openssl_sign() 对报文进行签名。
  4. HTTP通信:使用 cURL 向税局公共服务平台发送请求,获取“开票成功”的回执(内含OFD文件和XML文件)。
// 签名示例
$privateKey = openssl_pkey_get_private('file://private_key.pem', 'password');
$data = $requestXml; // 待签名的XML
openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256);
$signatureBase64 = base64_encode($signature);
// 之后将 $data 和 $signatureBase64 打包发给税局。

关键库与工具推荐(PHP生态)

不要重复造轮子,以下库能极大提高效率:

  • FPDI / TCPDF:用于在PDF/OFD基础上添加水印或生成打印版PDF(通常只用于预览效果)。
  • PHPOffice/PhpSpreadsheet:如果你需要将发票明细导出为Excel对账。
  • 只读OFD解析:目前没有特别成熟的纯PHP库专门解析OFD(因为OFD在中国使用较多),多数情况下直接用ZipArchive提取XML即可,不需要复杂的图形渲染库。
  • Validation:可使用 symfony/validator 校验发票代码、金额等格式。

避坑指南(实战经验)

① 关于金额精度

绝对禁止直接使用浮点数 float 计算金额,必须使用 字符串BCMath 函数。

$amount1 = "100.00";
$amount2 = "13.00";
$tax = bcadd($amount1, $amount2, 2); // "113.00"
// 判断是否相等
if (bccomp($total, $sumOfItems, 2) === 0) {
    // 平账
}

② 关于编码

  • 发票XML的默认编码是 UTF-8,如果包含生僻字或特殊符号(如“①”、“※”),要确保你的数据库连接和文件读取都使用 UTF-8MB4。

③ 关于OFD的坑

  • 有些OFD文件结构复杂(多附件、嵌套目录),不要硬编码路径,必须解析 OFD.xml 中的 FilePath 来动态定位 Document.xml

④ 关于状态管理

  • 如果对接税局,开票通常不是同步返回的(有异步回调),你的系统需要有定时任务(Cron Job)去轮询开票状态或接收回调Webhook。

总结代码框架(收票方入库)

public function importInvoice($filePath) {
    $extension = pathinfo($filePath, PATHINFO_EXTENSION);
    $data = [];
    if ($extension === 'ofd') {
        $zip = new ZipArchive();
        $zip->open($filePath);
        // 关键:找到Document.xml
        $documentPath = $this->findDocumentXml($zip); // 自定义查找方法
        $xmlString = $zip->getFromName($documentPath);
        $zip->close();
    } else { // xml or pdf
        // PDF需要先提取OCR或解析水印,通常不建议直接解析PDF获取数据,应要求提供XML或OFD
        return false;
    }
    // 解析
    $invoicData = simplexml_load_string($xmlString);
    $data['invoice_code'] = (string)$invoicData->InvoiceCode;
    $data['total_amount'] = (string)$invoicData->TotalAmount;
    // ... 存储到你的数据库
    // 3. 校验(调用国家平台或本地验签)
    // $isValid = $this->verify($invoicData);
    return $data;
}

实现建议

  1. 确定你的税务属地:使用哪个省的电子税务局接口(如广东、上海等),接口文档略有差异。
  2. 加入发票查验API:如果是企业内部报销,强烈建议对接第三方或税局的查验API,防止PS过的PDF黑客伪造的XML通过审核。
  3. 不要用PDF解析数据:PDF仅供人类阅读,机器解析应以OFD或XML为准。

如果你需要处理的是数电票(全电发票),我还可以协助你细化具体的XML字段含义(如如何区分专票与普票、如何识别“原名”与“数电票”),欢迎提供更多背景细节。

抱歉,评论功能暂时关闭!