本文目录导读:

- 核心概念:你处理的是“XML”或“OFD”
- 场景一:作为“收票方”(报销/入账)
- 场景二:作为“开票方”(对接税局平台)
- 关键库与工具推荐(PHP生态)
- 避坑指南(实战经验)
- 总结代码框架(收票方入库)
- 实现建议
在 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) { // 验签成功 }
场景二:作为“开票方”(对接税局平台)
如果你是电商平台或企业系统,需要给客户自动开票。
技术路线:
- 税局接口对接:需要向当地税务局申请“全电发票试点”接入资格,获取AppKey/Secret(密钥)。
- 组装XML:按照国家标准,组装开票XML报文(包含销方、购方、明细、价税分离)。
- 数字签名:使用保险库(UKey)或云证书,使用
openssl_sign()对报文进行签名。 - 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;
}
实现建议
- 确定你的税务属地:使用哪个省的电子税务局接口(如广东、上海等),接口文档略有差异。
- 加入发票查验API:如果是企业内部报销,强烈建议对接第三方或税局的查验API,防止PS过的PDF或黑客伪造的XML通过审核。
- 不要用PDF解析数据:PDF仅供人类阅读,机器解析应以OFD或XML为准。
如果你需要处理的是数电票(全电发票),我还可以协助你细化具体的XML字段含义(如如何区分专票与普票、如何识别“原名”与“数电票”),欢迎提供更多背景细节。