发票相关内容对接
对方只提供了一个JAVA版的JDK及源码。
在这里插入代码片
自己攒了一个PHP版的。且版本较低
<?php
namespace app\common\sdk;
use GuzzleHttp\Client;
use think\App;
use think\Exception;
use think\Log;
class Piaotong
{
private static $domain = 'http://fpkj.testnw.vpiaotong.cn';
private static $privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIVLAoolDaE7m5oMB1ZrILHkMXMF6qmC8I/FCejz4hwBcj59H3rbtcycBEmExOJTGwexFkNgRakhqM+3uP3VybWu1GBYNmqVzggWKKzThul9VPE3+OTMlxeG4H63RsCO1//J0MoUavXMMkL3txkZBO5EtTqek182eePOV8fC3ZxpAgMBAAECgYBp4Gg3BTGrZaa2mWFmspd41lK1E/kPBrRA7vltMfPj3P47RrYvp7/js/Xv0+d0AyFQXcjaYelTbCokPMJT1nJumb2A/Cqy3yGKX3Z6QibvByBlCKK29lZkw8WVRGFIzCIXhGKdqukXf8RyqfhInqHpZ9AoY2W60bbSP6EXj/rhNQJBAL76SmpQOrnCI8Xu75di0eXBN/bE9tKsf7AgMkpFRhaU8VLbvd27U9vRWqtu67RY3sOeRMh38JZBwAIS8tp5hgcCQQCyrOS6vfXIUxKoWyvGyMyhqoLsiAdnxBKHh8tMINo0ioCbU+jc2dgPDipL0ym5nhvg5fCXZC2rvkKUltLEqq4PAkAqBf9b932EpKCkjFgyUq9nRCYhaeP6JbUPN3Z5e1bZ3zpfBjV4ViE0zJOMB6NcEvYpy2jNR/8rwRoUGsFPq8//AkAklw18RJyJuqFugsUzPznQvad0IuNJV7jnsmJqo6ur6NUvef6NA7ugUalNv9+imINjChO8HRLRQfRGk6B0D/P3AkBt54UBMtFefOLXgUdilwLdCUSw4KpbuBPw+cyWlMjcXCkj4rHoeksekyBH1GrBJkLqDMRqtVQUubuFwSzBAtlc";
private static $ptPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJkx3HelhEm/U7jOCor29oHsIjCMSTyKbX5rpoAY8KDIs9mmr5Y9r+jvNJH8pK3u5gNnvleT6rQgJQW1mk0zHuPO00vy62tSA53fkSjtM+n0oC1Fkm4DRFd5qJgoP7uFQHR5OEffMjy2qIuxChY4Au0kq+6RruEgIttb7wUxy8TwIDAQAB";
private static $password = "xxx";
private static $platformAlias = "xxx";
private static $platformCode = "xxx";
private static $taxpayerNum = "xxx";
private static $isInit = false;
private static function init()
{
self::$isInit = true;
if (!\think\facade\Env::get('app_trace', false)) {
self::$domain = 'http://fpkj.vpiaotong.com';
self::$password = "lsBnINDxtct8HZB7KCMyhWSJ";
self::$platformAlias = "DEMO";
self::$platformCode = "11111111";
self::$privateKey = "";
self::$ptPublicKey = "";
}
}
public static function invoiceReqSerialNo($length = 26)
{
$date = date('YmdHis');
$platformAlias = self::$platformAlias;
$randomNumber = $length == 26 ? rand(10000000, 99999999) : rand(10, 99);
$serialNo = $platformAlias . $date . $randomNumber;
return $serialNo;
}
public static function request($url, $data)
{
if (!self::$isInit) {
self::init();
}
$client = new Client();
$options = [
'json' => [
'content' => self::encrypt3DES(json_encode($data), self::$password),
'format' => 'JSON',
'platformCode' => self::$platformCode,
'serialNo' => self::invoiceReqSerialNo(),
'signType' => 'RSA',
'timestamp' => date('Y-m-d H:i:s'),
'version' => '1.0',
],
'headers' => [
'Content-Type' => 'application/json',
]
];
$options['json']['sign'] = self::generateRSASign($options['json'], self::$privateKey);
$res = $client->post(self::$domain . $url, $options);
$response = $res->getBody()->getContents();
\think\facade\Log::write('request:' . json_encode([$url, $options, $data]) . PHP_EOL . 'response:' . $response, 'piaotong');
if ($res->getStatusCode() != 200) {
throw new Exception("请求失败!");
}
$response = json_decode($response, true);
if ($response['code'] !== '0000') {
throw new Exception($response['msg']);
}
$response['content'] = json_decode(self::decrypt3DES($response['content'], self::$password), true);
return $response;
}
public static function encrypt3DES($data, $key)
{
$key = substr($key, 0, 24);
$block_size = mcrypt_get_block_size(MCRYPT_TRIPLEDES, MCRYPT_MODE_ECB);
$pad = $block_size - (strlen($data) % $block_size);
$data .= str_repeat(chr($pad), $pad);
$encrypted = mcrypt_encrypt(MCRYPT_TRIPLEDES, $key, $data, MCRYPT_MODE_ECB);
return base64_encode($encrypted);
}
public static function decrypt3DES($encryptedData, $key = '')
{
$encryptedData = base64_decode($encryptedData);
if (empty($key)) {
$key = self::$password;
}
$key = substr($key, 0, 24);
$decrypted = mcrypt_decrypt(MCRYPT_TRIPLEDES, $key, $encryptedData, MCRYPT_MODE_ECB);
$pad = ord($decrypted[strlen($decrypted) - 1]);
return substr($decrypted, 0, -$pad);
}
public static function generateRSASign($params, $privateKey)
{
$content = chunk_split($privateKey, 64, "\n");
$privateKey = "-----BEGIN PRIVATE KEY-----\n" . $content . "-----END PRIVATE KEY-----";
$privateKey = openssl_pkey_get_private($privateKey);
if (!$privateKey) {
throw new Exception("Invalid private key: " . openssl_error_string());
}
unset($params['sign']);
ksort($params);
$signStr = '';
foreach ($params as $key => $value) {
if ($value == '' || $value == null) {
continue;
}
$signStr .= $key . '=' . $value . '&';
}
$signStr = rtrim($signStr, '&');
$signature = '';
$r = openssl_sign($signStr, $signature, $privateKey, OPENSSL_ALGO_SHA1);
if (!$r) {
throw new Exception("生成签名失败!");
}
return base64_encode($signature);
}
public static function verifyRSASign($params, $publicKey)
{
$signature = base64_decode($params['sign']);
unset($params['sign']);
ksort($params);
$signStr = '';
foreach ($params as $key => $value) {
$signStr .= $key . '=' . $value . '&';
}
$signStr = rtrim($signStr, '&');
return openssl_verify($signStr, $signature, $publicKey, OPENSSL_ALGO_SHA1) === 1;
}
public static function generateRSAKeyPair()
{
if (!extension_loaded('openssl')) {
throw new RuntimeException("OpenSSL extension is not enabled");
}
$config = [
"digest_alg" => "sha1",
"private_key_bits" => 1024,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
];
$keyResource = openssl_pkey_new($config);
if (!$keyResource) {
throw new Exception("Failed to generate key: " . openssl_error_string());
}
$privateKey = null;
if (!openssl_pkey_export($keyResource, $privateKey, null, $config)) {
throw new Exception("Export private key failed: " . openssl_error_string());
}
$publicKeyDetails = openssl_pkey_get_details($keyResource);
if (!$publicKeyDetails) {
throw new Exception("Get public key failed: " . openssl_error_string());
}
return [
'private_key' => $privateKey,
'public_key' => $publicKeyDetails['key']
];
}
}
调用实例
生成密钥对
Piaotong::generateRSAKeyPair();

注册企业
$res = Piaotong::request('/tp/openapi/register.pt', [
"taxpayerNum" => "xxx",
"enterpriseName" => "xxx",
"legalPersonName" => "AA",
"contactsName" => "AA",
"contactsEmail" => "11@qq.com",
"contactsPhone" => "15111111111",
"regionCode" => "11",
"cityName" => "海淀区",
"enterpriseAddress" => "知春路",
"taxRegistrationCertificate" => base64_encode(file_get_contents(App::getRootPath() . '/xxx/39f1b6e195d3a5684644b4f7a828579c.png'))
]);

根据发票抬头查其他信息
Piaotong::request('/tp/openapi/getInvoiceTitleInfo.pt', ['enterpriseName' => '票通']);
