PHP全电发票OFD生成实战
基于PHP的全电发票OFD文件生成案例
OFD(Open Fixed-layout Document)是一种国产的版式文档格式,广泛应用于电子发票、电子证照等领域。全电发票作为税务数字化的重要环节,其OFD文件的生成需要遵循严格的技术规范。以下通过PHP实现OFD文件生成的完整案例。
环境准备
PHP 7.4或更高版本
需要安装zip
扩展(OFD本质是ZIP压缩包)
推荐使用Composer安装依赖包:
composer require smalot/pdfparser // 用于解析PDF模板(如需)
composer require endroid/qr-code // 生成二维码
OFD文件结构分析
标准OFD文件包含以下目录结构:
OFD/
├── Doc_0/
│ ├── Pages/
│ ├── PublicRes.xml
│ ├── Document.xml
│ ├── Res/
└── OFD.xml
核心代码实现
1. 创建基础文件结构
function createOFDStructure(string $outputPath): void {$baseDir = $outputPath . '/OFD';mkdir($baseDir, 0777, true);$subDirs = ['Doc_0/Pages','Doc_0/Res'];foreach ($subDirs as $dir) {mkdir($baseDir . '/' . $dir, 0777, true);}// 创建占位文件file_put_contents($baseDir . '/OFD.xml', generateOFDRoot());file_put_contents($baseDir . '/Doc_0/Document.xml', generateDocumentRoot());
}
2. 生成OFD.xml(根文件)
function generateOFDRoot(): string {return <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<OFD xmlns="http://www.ofdspec.org/2016" Version="1.0"><DocBody><DocInfo><DocID>OFD_</DocID><Title>电子发票</Title></DocInfo><DocRoot>Doc_0/Document.xml</DocRoot></DocBody>
</OFD>
XML;
}
3. 发票内容填充模板
function generateInvoiceContent(array $data): string {$qrContent = sprintf("发票代码:%s\n发票号码:%s\n金额:¥%.2f",$data['code'],$data['number'],$data['amount']);$qrCode = (new QrCode($qrContent))->setSize(200)->setMargin(10)->writeString();$html = <<<HTML
<div style="position: absolute; left: 100mm; top: 50mm;"><h1>电子发票</h1><p>发票代码:{$data['code']}</p><p>发票号码:{$data['number']}</p><img src="data:image/png;base64," . base64_encode($qrCode) . ">
</div>
HTML;return convertHtmlToOFD($html); // 需要实现HTML到OFD坐标的转换
}
4. PDF转OFD核心转换
function pdfToOFD(string $pdfPath, string $outputDir): void {$parser = new \Smalot\PdfParser\Parser();$pdf = $parser->parseFile($pdfPath);$pages = $pdf->getPages();foreach ($pages as $index => $page) {$content = $page->getText();$ofdPage = generateOFDPage($content, $index);file_put_contents("{$outputDir}/Doc_0/Pages/Page_{$index}.xml",$ofdPage);}updateDocumentXML(count($pages), $outputDir);
}
5. 生成Page_N.xml文件
function generateOFDPage(string $content, int $pageIndex): string {$layerId = "Layer_{$pageIndex}";return <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<Page xmlns="http://www.ofdspec.org/2016" ID="{$pageIndex}" BaseLoc="Res/PageRes.xml" PhysicalBox="0 0 210 297"><Content><Layer ID="{$layerId}"><TextObject ID="1" Boundary="10 10 190 277"><TextCode X="15" Y="20">{$content}</TextCode></TextObject></Layer></Content>
</Page>
XML;
}
关键问题解决方案
1. 坐标系统转换
OFD采用毫米为单位的绝对坐标:
function mmToPoint(float $mm): float {return $mm * 2.83465; // 1mm ≈ 2.83465点
}
2. 二维码生成优化
使用Endroid QR Code库时添加发票专用容错:
$qrCode = (new QrCode($content))->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH)->setEncoding('UTF-8')->setRoundBlockSizeMode(RoundBlockSizeMode::MARGIN);
3. 批量生成处理
对于大批量发票生成,建议采用队列处理:
// 使用Redis队列
$redis = new Redis();
$redis->connect('127.0.0.1');
while ($data = $redis->rpop('invoice_queue')) {generateOFDInvoice(json_decode($data, true));
}
完整调用示例
$invoiceData = ['code' => '144021900111','number' => '12345678','amount' => 486.50,'seller' => '某某科技有限公司','buyer' => '测试客户'
];$outputPath = '/var/www/invoices/OFD_'.time();
createOFDStructure($outputPath);
generateInvoiceContent($invoiceData, $outputPath);// 压缩为最终OFD文件
$zip = new ZipArchive();
$zip->open($outputPath.'.ofd', ZipArchive::CREATE);
addFolderToZip($outputPath.'/OFD', $zip);
$zip->close();
注意事项
- 必须遵循《GB/T 33190-2016》OFD国家标准
- 电子发票需要包含税务监制章数字签名
- 实际生产环境应添加异常处理:
try {generateOFDInvoice($data);
} catch (OFDException $e) {logError($e->getMessage());retryLater($data);
}
通过上述实现方案,可以构建符合税务要求的全电发票OFD生成系统。实际应用中还需结合数字签名、发票流水号管理等完整业务流程。