对接票通实例

发票相关内容对接

对方只提供了一个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";

    //3DES秘钥(票通提供)
    private static $password = "xxx";

    //请更换请求平台简称(票通提供)
    private static $platformAlias = "xxx";

    //请更换请求平台编码(票通提供)
    private static $platformCode = "xxx";
    //销售方税号(测试环境票通提供,正式环境使用正式税号)
    private static $taxpayerNum = "xxx";

    private static $isInit = false;

    /**
     * 初始化一下
     * @return void
     * @author luwc
     * @datetime 2025/3/8 17:39
     */
    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 = "";
        }
    }

    /**
     * 随机发票流水号生成
     * @return string
     * @version 1.0.0
     * @author luwc
     * @time 2025/3/3 12:43
     */
    public static function invoiceReqSerialNo($length = 26)
    {
        $date = date('YmdHis'); // 获取当前日期和时间
        $platformAlias = self::$platformAlias; // 替换为实际的平台别名
        // 生成随机数部分,范围是10到99
        $randomNumber = $length == 26 ? rand(10000000, 99999999) : rand(10, 99);
        // 拼接字符串
        $serialNo = $platformAlias . $date . $randomNumber;
        return $serialNo;
    }

    /**
     * 发起请求
     * @param $url
     * @param $data
     * @return mixed
     * @throws Exception
     * @version 1.0.0
     * @author luwc
     * @time 2025/3/3 15:02
     */
    public static function request($url, $data)
    {
        if (!self::$isInit) {
            self::init();
        }
        $client = new Client();
        $options = [
            'json' => [
                //'sign' => '',// 签名串 256 位 是 商户请求参数的签名串
                'content' => self::encrypt3DES(json_encode($data), self::$password),
                'format' => 'JSON',//业务报文格式 10 位 否 目前支持 JSON
                'platformCode' => self::$platformCode,// 平台编码 8 位 是 票通分配给开发者的平台编码
                'serialNo' => self::invoiceReqSerialNo(),// 交易请求流水号26位是4位平台简称+14位日期yyyymmddhhmmss)+8 位随机数
                'signType' => 'RSA',// 加密类型 10 位 是 目前支持 RSA
                'timestamp' => date('Y-m-d H:i:s'), //请求时间 19 位 是 yyyy-MM-dd HH:mm:ss
                'version' => '1.0',// 版本号 4 位 是 调用接口版本,固定为 1.0
            ],
            'headers' => [
                'Content-Type' => 'application/json',
            ]
        ];
        $options['json']['sign'] = self::generateRSASign($options['json'], self::$privateKey);
        // 待签名串示例:
        //content=Base64.(3DES(content))&format=JSON&platformCode=platformCode&serialNo=DEMO20170504145147zEm9cQ05&signType=RSA&timestamp=2017-05-04 14:51:47&version=1.0
        $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;
    }

    /**
     * 3DES加密 (ECB模式)
     * @param $data
     * @param $key
     * @return string
     * @version 1.0.0
     * @author luwc
     * @time 2025/3/3 15:00
     */
    public static function encrypt3DES($data, $key)
    {
        $key = substr($key, 0, 24);
        // php5.6不支持需要手动实现
        $block_size = mcrypt_get_block_size(MCRYPT_TRIPLEDES, MCRYPT_MODE_ECB);
        $pad = $block_size - (strlen($data) % $block_size);
        $data .= str_repeat(chr($pad), $pad); // PKCS7填充‌:ml-citation{ref="2,3" data="citationList"}
        $encrypted = mcrypt_encrypt(MCRYPT_TRIPLEDES, $key, $data, MCRYPT_MODE_ECB);
        return base64_encode($encrypted);
        /*$encrypted = openssl_encrypt($data, 'DES-EDE3-ECB', $key, OPENSSL_RAW_DATA);
        return base64_encode($encrypted);*/
    }

    /**
     * 3DES解密 (ECB模式)
     * @param $encryptedData
     * @param $key
     * @return false|string
     * @version 1.0.0
     * @author luwc
     * @time 2025/3/3 15:00
     */
    public static function decrypt3DES($encryptedData, $key = '')
    {
        $encryptedData = base64_decode($encryptedData);
        if (empty($key)) {
            $key = self::$password;
        }
        $key = substr($key, 0, 24);
        // php5.6不支持需要手动实现
        $decrypted = mcrypt_decrypt(MCRYPT_TRIPLEDES, $key, $encryptedData, MCRYPT_MODE_ECB);
        $pad = ord($decrypted[strlen($decrypted) - 1]); // 获取填充值‌:ml-citation{ref="2,3" data="citationList"}
        return substr($decrypted, 0, -$pad);
        // return openssl_decrypt($encryptedData, 'DES-EDE3-ECB', $key, OPENSSL_RAW_DATA);
    }

    /**
     * 生成RSA签名
     * @param $params
     * @param $privateKey
     * @return string
     * @throws Exception
     * @version 1.0.0
     * @author luwc
     * @time 2025/3/3 15:01
     */
    public static function generateRSASign($params, $privateKey)
    {
        // 加载私钥并检查有效性
        $content = chunk_split($privateKey, 64, "\n");  // 确保换行符为 LF(Unix 格式)
        $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']); // 排除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);
    }

    /**
     * 验证RSA签名
     * @param $params
     * @param $publicKey
     * @return bool
     * @version 1.0.0
     * @author luwc
     * @time 2025/3/3 15:01
     */
    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;
    }

    /**
     * 生成密钥对(PKCS8格式)
     * @return array
     * @throws Exception
     * @version 1.0.0
     * @author luwc
     * @time 2025/3/3 15:01
     */
    public static function generateRSAKeyPair()
    {
        // 检查 OpenSSL 扩展是否加载
        if (!extension_loaded('openssl')) {
            throw new RuntimeException("OpenSSL extension is not enabled");
        }

        // 配置参数(Windows需指定openssl.cnf路径)
        $config = [
            "digest_alg" => "sha1",
            "private_key_bits" => 1024,
            "private_key_type" => OPENSSL_KEYTYPE_RSA,
            // "config" => "C:/php/extras/ssl/openssl.cnf" // Windows需要此项
        ];

        // 生成密钥资源
        $keyResource = openssl_pkey_new($config);
        if (!$keyResource) {
            throw new Exception("Failed to generate key: " . openssl_error_string());
        }

        // 导出私钥(PKCS8)
        $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'))//Base64 字符串,需小于2M
            ]);

在这里插入图片描述

根据发票抬头查其他信息

Piaotong::request('/tp/openapi/getInvoiceTitleInfo.pt', ['enterpriseName' => '票通']);

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值