本文目录导读:

在PHP项目中对接发票开具接口(如百望、航天信息、微信/支付宝电子发票、诺诺网等),通常遵循标准的HTTP API流程,以下是通用的对接步骤、核心代码示例及注意事项。
对接前的准备工作
- 获取API文档:向发票服务商申请接口文档(通常为RESTful API)。
- 申请密钥:
- Access Key (appKey / appId):用于身份标识。
- Secret Key (appSecret / signKey):用于签名计算(绝对不能泄露到前端)。
- 配置参数:税号、开票员、收款人、复核人等信息。
- 沙箱测试:先用测试环境(模拟开票)确保流程走通。
- 网络环境:确保服务器能访问API域名(部分厂商要求IP白名单)。
核心对接流程
大多数发票接口的流程如下:
- 客户端提交开票数据:前端收集订单号、购买方信息(名称、税号)、商品明细、金额等。
- PHP后端拼接请求参数:按API文档拼装JSON/XML。
- 生成签名:使用协商好的算法(如MD5、SHA256、RSA等)对参数进行签名。
- 发送HTTP请求:使用
cURL或Guzzle等库发送POST/GET请求。 - 处理响应:解析返回的JSON/XML,保存发票号码、发票代码、PDF下载链接等。
- 异步回调:部分接口采用异步(先返回受理成功,后回调通知开票结果),需配置回调接收地址。
通用PHP代码示例(基于cURL + JSON + 签名)
假设使用最常见的 MD5+签名 方式或 签名在请求头 的方式。
构建请求类
<?php
class InvoiceClient
{
private $appId;
private $appSecret;
private $baseUrl;
public function __construct($appId, $appSecret, $baseUrl)
{
$this->appId = $appId;
$this->appSecret = $appSecret;
$this->baseUrl = $baseUrl;
}
/**
* 生成签名 (示例使用MD5,实际以文档为准)
*/
private function generateSign(array $params, $timestamp): string
{
// 1. 按参数名排序
ksort($params);
// 2. 拼接成字符串: key1=value1&key2=value2
$queryString = urldecode(http_build_query($params));
// 3. 在字符串前后加上secret
$signStr = $this->appSecret . $queryString . $this->appSecret;
// 4. MD5加密并转大写
return strtoupper(md5($signStr));
}
/**
* 发送请求(通用方法)
*/
public function sendRequest(string $method, string $apiPath, array $data = []): array
{
$timestamp = time() * 1000; // 毫秒时间戳
$nonce = bin2hex(random_bytes(16));
// 构造请求头或参数 (根据API文档)
$params = [
'appId' => $this->appId,
'timestamp' => $timestamp,
'nonce' => $nonce,
'data' => json_encode($data, JSON_UNESCAPED_UNICODE) // 业务数据
];
// 生成签名
$sign = $this->generateSign($params, $timestamp);
// 将签名加入参数或header (示例加入参数)
$params['sign'] = $sign;
$url = rtrim($this->baseUrl, '/') . '/' . ltrim($apiPath, '/');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 生产环境应设为true
if (strtoupper($method) === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
// 方式1: JSON body
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params, JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json; charset=utf-8',
'Accept: application/json'
]);
} else {
// GET请求拼接参数
curl_setopt($ch, CURLOPT_URL, $url . '?' . http_build_query($params));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
return ['code' => -1, 'msg' => '请求失败: ' . $error];
}
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return ['code' => -2, 'msg' => '响应不是有效JSON', 'raw' => $response];
}
// 注意: 这里假设响应包含code/message/data字段 (具体看API)
return [
'code' => $result['code'] ?? 0,
'msg' => $result['message'] ?? 'success',
'data' => $result['data'] ?? []
];
}
/**
* 开票接口 (示例)
*/
public function createInvoice(array $invoiceData): array
{
// 业务数据组装 (参考具体接口文档)
$data = [
'orderNo' => $invoiceData['order_no'],
'buyerName' => $invoiceData['buyer_name'],
'buyerTaxNo' => $invoiceData['buyer_tax_no'] ?? '',
'buyerAddress' => $invoiceData['buyer_address'] ?? '',
'buyerPhone' => $invoiceData['buyer_phone'] ?? '',
'amount' => $invoiceData['amount'],
'taxRate' => $invoiceData['tax_rate'],
'items' => $invoiceData['items'] // 商品明细数组
];
return $this->sendRequest('POST', '/api/v1/invoice/create', $data);
}
}
调用示例
// 配置
$config = [
'appId' => 'your_app_id',
'appSecret' => 'your_app_secret',
'baseUrl' => 'https://sandbox-api.invoice.com' // 测试环境
];
$client = new InvoiceClient($config['appId'], $config['appSecret'], $config['baseUrl']);
// 准备开票数据
$orderData = [
'order_no' => 'ORDER202410001',
'buyer_name' => '某某科技有限公司',
'buyer_tax_no' => '91440101MA5XXXXX',
'amount' => 1000.00,
'tax_rate' => 0.13,
'items' => [
[
'name' => '技术服务费',
'quantity' => 1,
'price' => 1000.00,
'taxRate' => 0.13
]
]
];
// 发起开票
$result = $client->createInvoice($orderData);
if ($result['code'] == 0) {
// 成功
echo "开票成功,发票号码: " . $result['data']['invoiceNo'] . "\n";
// 保存发票数据到数据库
} else {
// 失败,记录日志
echo "开票失败: " . $result['msg'] . "\n";
}
几种常见厂商的特殊处理
| 厂商 | 签名方式 | 传输方式 | 特殊点 |
|---|---|---|---|
| 百望 | RSA/国密SM2 | XML | 商品编码需预先配置,支持全电发票,接口返回PDF或OFD下载链接 |
| 航天信息 | MD5+签名字符串 | JSON/XML | 需定期同步税号、开票点、商品编码 |
| 支付宝/微信 | RSA2(支付宝)/ MD5+盐 | JSON | 走支付通道,开票数据需关联交易单号,注意官方SDK |
| 诺诺网 | MD5+密钥 | JSON | 需先下单(提交开票申请),再查询结果 |
项目中需要注意的要点
-
数据一致性:
- 使用本地订单号作为幂等键,防止重复开票。
- 如果接口返回“处理中”,需单独设计定时任务轮询开票结果。
-
异常处理:
- 网络超时、服务端返回错误码时,不要立即放弃,应设计重试机制(最多3次,间隔递增)。
- 记录详细的请求和响应日志(不要记录敏感密钥,但可记录加密后的数据)。
-
同步 vs 异步:
- 同步:发请求后直接返回结果(适合即时开票),需控制超时时间(如60秒)。
- 异步:返回受理成功,需提供回调URL(
/api/invoice/callback),处理回调时更新数据库状态。
-
发票下载与展示:
- 开票成功后获取PDF/OFD下载URL。
- 推荐保存文件到本地云存储(OSS)并设置过期时间,避免原链接失效。
-
全电发票:
- 现在很多接口支持“全电发票”(无纸质发票,XML文件直接入账)。
- XML文件需保存到数据库或对象存储,用户可下载的格式通常是PDF(由服务商转换)。
安全与性能
- 密钥安全:密钥绝对不要写在代码里,使用环境变量(
.env文件)或配置中心(如阿里云ACM)。 - HTTPS:必须使用HTTPS,防止中间人攻击。
- 限流:接口通常有调用次数限制(如QPS=100),合理使用队列(Redis/Beanstalkd)缓冲。
- 数据脱敏:日志中的请求数据(如买家税号、手机号)应进行脱敏处理(如
914401*******X)。
常见错误及排查
- 签名错误(1001/401):检查参数顺序、编码(
http_build_query默认会对特殊字符编码,可能需要urldecode)、密钥是否正确。 - 商品编码不存在:需要先在发票平台维护商品编码(税收分类编码)。
- 金额不符:开票金额和明细合计必须与订单完全一致(需精确到分或以上)。
- 税号问题:购买方税号格式验证(企业必须为18位统一社会信用代码,个人发票可填空)。
推荐架构
┌─────────────┐ ┌─────────────┐ ┌────────────────┐
│ 前端应用 │────▶│ PHP后端 │────▶│ 发票API网关 │
│ (提交开票) │ │ (队列+处理) │ │ (百望/航信) │
└─────────────┘ └──────┬──────┘ └────────────────┘
│
▼
┌─────────────┐
│ 数据库 │
│ (订单+发票) │
└─────────────┘
- 队列:防止开票接口慢导致请求阻塞(如订单支付成功后,将开票任务丢进队列)。
- 定时任务:处理回调超时或轮询未完成的发票。
对接发票接口的核心是:理解文档 → 生成签名 → 发送请求 → 处理响应 → 状态同步,遇到问题时,仔细查看官方文档的“签名示例”和“错误码说明”,并在测试环境充分验证,如果使用成熟框架(如Laravel),可以考虑封装成Service Provider,使代码更整洁。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播