优化后端框架

This commit is contained in:
BBIT-Kai
2026-05-07 10:25:02 +08:00
parent c932419c73
commit f7a27d99e1
73 changed files with 1742 additions and 1596 deletions
@@ -0,0 +1,66 @@
using System;
using System.Security.Cryptography;
using System.Text;
namespace ConsoleDemo
{
public class EncryptDes
{
/**
* aStrString 加密内容
* aStrKey 加密秘钥
*/
public static String Encrypt3Des(String aStrString, String aStrKey, CipherMode mode = CipherMode.ECB, String iv = "12345678")
{
try
{
var des = new TripleDESCryptoServiceProvider
{
Key = Encoding.UTF8.GetBytes(aStrKey),
Mode = mode
};
if (mode == CipherMode.CBC)
{
des.IV = Encoding.UTF8.GetBytes(iv);
}
var desEncrypt = des.CreateEncryptor();
byte[] buffer = Encoding.UTF8.GetBytes(aStrString);
return Convert.ToBase64String(desEncrypt.TransformFinalBlock(buffer, 0, buffer.Length));
}
catch (Exception e)
{
return string.Empty;
}
}
public static string Decrypt3Des(string aStrString, string aStrKey, CipherMode mode = CipherMode.ECB, string iv = "12345678")
{
try
{
var des = new TripleDESCryptoServiceProvider
{
Key = Encoding.UTF8.GetBytes(aStrKey),
Mode = mode,
Padding = PaddingMode.PKCS7
};
if (mode == CipherMode.CBC)
{
des.IV = Encoding.UTF8.GetBytes(iv);
}
var desDecrypt = des.CreateDecryptor();
var result = "";
byte[] buffer = Convert.FromBase64String(aStrString);
result = Encoding.UTF8.GetString(desDecrypt.TransformFinalBlock(buffer, 0, buffer.Length));
return result;
}
catch (Exception e)
{
return string.Empty;
}
}
}
}
@@ -0,0 +1,37 @@
using System.IO;
using System.Net;
using System.Text;
namespace ConsoleDemo
{
public class PostJson
{
/**
* Json的请求头 post请求地址
*/
public static string Post4Json(string url,string buildRequest)
{
string result = "";
HttpWebRequest request =(HttpWebRequest) WebRequest.Create(url);
request.Method = "POST";
request.Timeout = 5000;
request.ContentType = "application/json";
byte[] byte4builde = Encoding.UTF8.GetBytes(buildRequest);
request.ContentLength = byte4builde.Length;
using (Stream reqStream=request.GetRequestStream())
{
reqStream.Write(byte4builde,0,byte4builde.Length);
reqStream.Close();
}
HttpWebResponse response = (HttpWebResponse) request.GetResponse();
Stream stream = response.GetResponseStream();
//获得响应内容
using (StreamReader reader=new StreamReader(stream,Encoding.UTF8))
{
result = reader.ReadToEnd();
}
return result;
}
}
}
@@ -0,0 +1,4 @@
// See https://aka.ms/new-console-template for more information
using ConsoleDemo;
Console.WriteLine("Hello, World!");
@@ -0,0 +1,123 @@
using System;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Text;
namespace ConsoleDemo
{
public class PublicData
{
/**
* 公共报文组装
*/
public static string publicparam(string content,string platformCode ,
string platformAlias,string privateKey,string password)
{
StringBuilder sign =new StringBuilder();
string contentstr=EncryptDes.Encrypt3Des(content, password);
sign.Append("content="+contentstr+"&");
sign.Append("format=JSON&");
sign.Append("platformCode="+platformCode+"&");
string time = DateTime.Now.ToString("yyyyMMddHHmmss");
string serianlNo = platformAlias + time + GenerateCheckCode(8);
sign.Append("serialNo="+serianlNo+"&");
sign.Append("signType=RSA&");
string timestamp=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
sign.Append("timestamp="+timestamp+"&");
sign.Append("version=1.0");
string rsasign = RSA.sign(sign.ToString(), privateKey);
Hashtable publictable=new Hashtable();
publictable.Add("sign",rsasign);
publictable.Add("format","JSON");
publictable.Add("platformCode",platformCode);
publictable.Add("serialNo",serianlNo);
publictable.Add("signType","RSA");
publictable.Add("timestamp",timestamp);
publictable.Add("version","1.0");
publictable.Add("content",contentstr);
return ToJson.Table2Json(publictable);
}
public static string disposeResponse(string str,string ptpublickey, string deskey)
{
Hashtable strtable=new Hashtable();
StringBuilder content=new StringBuilder();
Hashtable dispose=new Hashtable();
string sign = "";
string serialNo = "";
str=str.Replace("{","").Replace("}","").Replace("\"","").Replace(",","&").Replace(":","&");
string[] arraystr = str.Split('&');
for (int i = 0; i < arraystr.Length-1; i+=2)
{
if (arraystr[i]=="sign")
{
sign = arraystr[i + 1];
}
if (arraystr[i]=="serialNo")
{
serialNo=arraystr[i + 1];
}
strtable.Add(arraystr[i],arraystr[i+1]);
}
strtable.Remove("serialNo");
strtable.Remove("sign");
ArrayList ke = new ArrayList(strtable.Keys);
ke.Sort();
foreach (string tableEntry in ke)
{
content.Append(string.Format("{0}={1}&", tableEntry, strtable[tableEntry]));
}
content.Append("serialNo=" + serialNo);
bool res=RSA.verify(content.ToString(), sign, ptpublickey, "UTF-8");
Console.WriteLine(res);
if (res)
{
for (int i = 0; i < arraystr.Length - 1; i += 2)
{
if (arraystr[i] == "content")
{
arraystr[i+1]=EncryptDes.Decrypt3Des(arraystr[i+1],deskey);
}
dispose.Add(arraystr[i],arraystr[i+1]);
}
return ToJson.Table2Json(dispose);
}
else
{
return "验签失败";
}
}
/**
* 随机数生成
*/
private static string GenerateCheckCode(int codeCount)
{
int rep = 0;
string str = string.Empty;
long num2 = DateTime.Now.Ticks + rep;
rep++;
Random random = new Random(((int)(((ulong)num2) & 0xffffffffL)) | ((int)(num2 >> rep)));
for (int i = 0; i < codeCount; i++)
{
char ch;
int num = random.Next();
if ((num % 2) == 0)
{
ch = (char)(0x30 + ((ushort)(num % 10)));
}
else
{
ch = (char)(0x41 + ((ushort)(num % 0x1a)));
}
str = str + ch.ToString();
}
return str;
}
}
}
@@ -0,0 +1,502 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;
namespace ConsoleDemo
{
internal class RSA
{
/**
* content 签名前的报文
* privateKey 私钥
* input_charset 编码格式 (以下默认UTF-8)
*/
public static string sign(string content, string privateKey)
{
byte[] Data = Encoding.GetEncoding("UTF-8").GetBytes(content);
RSACryptoServiceProvider rsa = DecodePemPrivateKey(privateKey);
SHA1 sh = new SHA1CryptoServiceProvider();
byte[] signData = rsa.SignData(Data, sh);
return Convert.ToBase64String(signData);
}
/// <summary>
/// 验签
/// </summary>
/// <param name="content">待验签字符串</param>
/// <param name="signedString">签名</param>
/// <param name="publicKey">公钥</param>
/// <param name="input_charset">编码格式</param>
/// <returns>true(通过)false(不通过)</returns>
public static bool verify(string content, string signedString, string publicKey, string input_charset)
{
bool result ;
byte[] Data = Encoding.GetEncoding(input_charset).GetBytes(content);
byte[] data = Convert.FromBase64String(signedString);
RSAParameters paraPub = ConvertFromPublicKey(publicKey);
RSACryptoServiceProvider rsaPub = new RSACryptoServiceProvider();
rsaPub.ImportParameters(paraPub);
SHA1 sh = new SHA1CryptoServiceProvider();
result = rsaPub.VerifyData(Data, sh, data);
return result;
}
/// <summary>
/// 加密
/// </summary>
/// <param name="resData">需要加密的字符串</param>
/// <param name="publicKey">公钥</param>
/// <param name="input_charset">编码格式</param>
/// <returns>明文</returns>
public static string encryptData(string resData, string publicKey, string input_charset)
{
byte[] DataToEncrypt = Encoding.ASCII.GetBytes(resData);
string result = encrypt(DataToEncrypt, publicKey, input_charset);
return result;
}
/// <summary>
/// 解密
/// </summary>
/// <param name="resData">加密字符串</param>
/// <param name="privateKey">私钥</param>
/// <param name="input_charset">编码格式</param>
/// <returns>明文</returns>
public static string decryptData(string resData, string privateKey, string input_charset)
{
byte[] DataToDecrypt = Convert.FromBase64String(resData);
string result = "";
for (int j = 0; j < DataToDecrypt.Length / 128; j++)
{
byte[] buf = new byte[128];
for (int i = 0; i < 128; i++)
{
buf[i] = DataToDecrypt[i + 128 * j];
}
result += decrypt(buf, privateKey, input_charset);
}
return result;
}
#region
private static string encrypt(byte[] data, string publicKey, string input_charset)
{
RSACryptoServiceProvider rsa = DecodePemPublicKey(publicKey);
SHA1 sh = new SHA1CryptoServiceProvider();
byte[] result = rsa.Encrypt(data, false);
return Convert.ToBase64String(result);
}
private static string decrypt(byte[] data, string privateKey, string input_charset)
{
string result = "";
RSACryptoServiceProvider rsa = DecodePemPrivateKey(privateKey);
SHA1 sh = new SHA1CryptoServiceProvider();
byte[] source = rsa.Decrypt(data, false);
char[] asciiChars = new char[Encoding.GetEncoding(input_charset).GetCharCount(source, 0, source.Length)];
Encoding.GetEncoding(input_charset).GetChars(source, 0, source.Length, asciiChars, 0);
result = new string(asciiChars);
//result = ASCIIEncoding.ASCII.GetString(source);
return result;
}
private static RSACryptoServiceProvider DecodePemPublicKey(String pemstr)
{
byte[] pkcs8publickkey;
pkcs8publickkey = Convert.FromBase64String(pemstr);
if (pkcs8publickkey != null)
{
RSACryptoServiceProvider rsa = DecodeRSAPublicKey(pkcs8publickkey);
return rsa;
}
else
return null;
}
private static RSACryptoServiceProvider DecodePemPrivateKey(String pemstr)
{
byte[] pkcs8privatekey;
pkcs8privatekey = Convert.FromBase64String(pemstr);
if (pkcs8privatekey != null)
{
RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8privatekey);
return rsa;
}
else
return null;
}
private static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8)
{
byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
byte[] seq = new byte[15];
MemoryStream mem = new MemoryStream(pkcs8);
int lenstream = (int)mem.Length;
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
bt = binr.ReadByte();
if (bt != 0x02)
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0001)
return null;
seq = binr.ReadBytes(15); //read the Sequence OID
if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
return null;
bt = binr.ReadByte();
if (bt != 0x04) //expect an Octet string
return null;
bt = binr.ReadByte(); //read next byte, or next 2 bytes is 0x81 or 0x82; otherwise bt is the byte count
if (bt == 0x81)
binr.ReadByte();
else
if (bt == 0x82)
binr.ReadUInt16();
//------ at this stage, the remaining sequence should be the RSA private key
byte[] rsaprivkey = binr.ReadBytes((int)(lenstream - mem.Position));
RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey);
return rsacsp;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
private static bool CompareBytearrays(byte[] a, byte[] b)
{
if (a.Length != b.Length)
return false;
int i = 0;
foreach (byte c in a)
{
if (c != b[i])
return false;
i++;
}
return true;
}
private static RSACryptoServiceProvider DecodeRSAPublicKey(byte[] publickey)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
byte[] seq = new byte[15];
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
MemoryStream mem = new MemoryStream(publickey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
seq = binr.ReadBytes(15); //read the Sequence OID
if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8203)
binr.ReadInt16(); //advance 2 bytes
else
return null;
bt = binr.ReadByte();
if (bt != 0x00) //expect null byte next
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
byte lowbyte = 0x00;
byte highbyte = 0x00;
if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
else if (twobytes == 0x8202)
{
highbyte = binr.ReadByte(); //advance 2 bytes
lowbyte = binr.ReadByte();
}
else
return null;
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order
int modsize = BitConverter.ToInt32(modint, 0);
byte firstbyte = binr.ReadByte();
binr.BaseStream.Seek(-1, SeekOrigin.Current);
if (firstbyte == 0x00)
{ //if first byte (highest order) of modulus is zero, don't include it
binr.ReadByte(); //skip this null byte
modsize -= 1; //reduce modulus buffer size by 1
}
byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes
if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data
return null;
int expbytes = (int)binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values)
byte[] exponent = binr.ReadBytes(expbytes);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = new RSAParameters();
RSAKeyInfo.Modulus = modulus;
RSAKeyInfo.Exponent = exponent;
RSA.ImportParameters(RSAKeyInfo);
return RSA;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
private static int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
return count;
}
#endregion
#region .net Pem
private static RSAParameters ConvertFromPublicKey(string pemFileConent)
{
byte[] keyData = Convert.FromBase64String(pemFileConent);
if (keyData.Length < 162)
{
throw new ArgumentException("pem file content is incorrect.");
}
byte[] pemModulus = new byte[128];
byte[] pemPublicExponent = new byte[3];
Array.Copy(keyData, 29, pemModulus, 0, 128);
Array.Copy(keyData, 159, pemPublicExponent, 0, 3);
RSAParameters para = new RSAParameters();
para.Modulus = pemModulus;
para.Exponent = pemPublicExponent;
return para;
}
private static RSAParameters ConvertFromPrivateKey(string pemFileConent)
{
byte[] keyData = Convert.FromBase64String(pemFileConent);
if (keyData.Length < 609)
{
throw new ArgumentException("pem file content is incorrect.");
}
int index = 11;
byte[] pemModulus = new byte[128];
Array.Copy(keyData, index, pemModulus, 0, 128);
index += 128;
index += 2;//141
byte[] pemPublicExponent = new byte[3];
Array.Copy(keyData, index, pemPublicExponent, 0, 3);
index += 3;
index += 4;//148
byte[] pemPrivateExponent = new byte[128];
Array.Copy(keyData, index, pemPrivateExponent, 0, 128);
index += 128;
index += ((int)keyData[index + 1] == 64 ? 2 : 3);//279
byte[] pemPrime1 = new byte[64];
Array.Copy(keyData, index, pemPrime1, 0, 64);
index += 64;
index += ((int)keyData[index + 1] == 64 ? 2 : 3);//346
byte[] pemPrime2 = new byte[64];
Array.Copy(keyData, index, pemPrime2, 0, 64);
index += 64;
index += ((int)keyData[index + 1] == 64 ? 2 : 3);//412/413
byte[] pemExponent1 = new byte[64];
Array.Copy(keyData, index, pemExponent1, 0, 64);
index += 64;
index += ((int)keyData[index + 1] == 64 ? 2 : 3);//479/480
byte[] pemExponent2 = new byte[64];
Array.Copy(keyData, index, pemExponent2, 0, 64);
index += 64;
index += ((int)keyData[index + 1] == 64 ? 2 : 3);//545/546
byte[] pemCoefficient = new byte[64];
Array.Copy(keyData, index, pemCoefficient, 0, 64);
RSAParameters para = new RSAParameters();
para.Modulus = pemModulus;
para.Exponent = pemPublicExponent;
para.D = pemPrivateExponent;
para.P = pemPrime1;
para.Q = pemPrime2;
para.DP = pemExponent1;
para.DQ = pemExponent2;
para.InverseQ = pemCoefficient;
return para;
}
#endregion
}
}
@@ -0,0 +1,277 @@
using System;
using System.Collections;
using System.Text;
namespace ConsoleDemo
{
public class StarDemo
{
//私钥(与发给票通的公钥为一对)
private static String privateKey =
"MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIVLAoolDaE7m5oMB1ZrILHkMXMF6qmC8I/FCejz4hwBcj59H3rbtcycBEmExOJTGwexFkNgRakhqM+3uP3VybWu1GBYNmqVzggWKKzThul9VPE3+OTMlxeG4H63RsCO1//J0MoUavXMMkL3txkZBO5EtTqek182eePOV8fC3ZxpAgMBAAECgYBp4Gg3BTGrZaa2mWFmspd41lK1E/kPBrRA7vltMfPj3P47RrYvp7/js/Xv0+d0AyFQXcjaYelTbCokPMJT1nJumb2A/Cqy3yGKX3Z6QibvByBlCKK29lZkw8WVRGFIzCIXhGKdqukXf8RyqfhInqHpZ9AoY2W60bbSP6EXj/rhNQJBAL76SmpQOrnCI8Xu75di0eXBN/bE9tKsf7AgMkpFRhaU8VLbvd27U9vRWqtu67RY3sOeRMh38JZBwAIS8tp5hgcCQQCyrOS6vfXIUxKoWyvGyMyhqoLsiAdnxBKHh8tMINo0ioCbU+jc2dgPDipL0ym5nhvg5fCXZC2rvkKUltLEqq4PAkAqBf9b932EpKCkjFgyUq9nRCYhaeP6JbUPN3Z5e1bZ3zpfBjV4ViE0zJOMB6NcEvYpy2jNR/8rwRoUGsFPq8//AkAklw18RJyJuqFugsUzPznQvad0IuNJV7jnsmJqo6ur6NUvef6NA7ugUalNv9+imINjChO8HRLRQfRGk6B0D/P3AkBt54UBMtFefOLXgUdilwLdCUSw4KpbuBPw+cyWlMjcXCkj4rHoeksekyBH1GrBJkLqDMRqtVQUubuFwSzBAtlc";
//票通公钥(票通提供)
private static String ptPublicKey =
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJkx3HelhEm/U7jOCor29oHsIjCMSTyKbX5rpoAY8KDIs9mmr5Y9r+jvNJH8pK3u5gNnvleT6rQgJQW1mk0zHuPO00vy62tSA53fkSjtM+n0oC1Fkm4DRFd5qJgoP7uFQHR5OEffMjy2qIuxChY4Au0kq+6RruEgIttb7wUxy8TwIDAQAB";
//3DES秘钥(票通提供)
private static String password = "lsBnINDxtct8HZB7KCMyhWSJ";
//请更换请求平台简称(票通提供)
private static String platform_alias = "DEMK";
//请更换请求平台编码(票通提供)
private static String platform_code = "11111111";
public static void Main(string[] args)
{
Console.Write(PublicData.disposeResponse(Blue(), ptPublicKey, password));//蓝票接口
// testqueryInvoice();//查询接口
// testGetInvoiceRepertoryInfo();//库存接口
// testAuthWeChatCards();//插入微信卡包接口
// testTitleInfo();//查询抬头接口
// testGetPTBoxStatus();//查询设备状态
// testGetQrCodeByItems();//获取开票二维码和提取码
// testdeleteInvoiceQrCode();//作为二维码
// testInvoiceRed();//冲红接口
// testRegister();// 注册接口
//string content = "{\"code\":\"0000\",\"msg\":\"处理成功\",\"sign\":\"ZjAqLXwvEEgz2+jzP/+vUWGuvhBr4N4Gg/pLLOt90sMP160SC1RrkOy6b5p1CCx3y4QYRkbqq2NmkYXpAJX5BdkoXFYUO1hF4ufUvYPmIjQvKT9JMnXt1RV0EdNLliiEowJPjjXDSlTZdthIsTXdVirCkGohzLt3b/2YU9moAM8=\",\"serialNo\":\"CTXP20181206100927n5ObjiJM\",\"content\":\"q55jwSlpLhWV7cnEgNTvm+bswSXLiOPDbw8HvqR7SKhQDWJ/x1qlcJHAOB2lYHmQmefePoaVJ4abG7O9aJwIssDZsit2a2pqNeiCWqVmKhceLAsD/IV4DAlHmwZZhb9tqqco+HDHmZlqJy9pQv478OW0UDx/X0kTbIy4au5pZvJdODh4t31o5I2HrGm1HNcykyKMDpr5D1Mx2mYsjHm95OKBAzLPKMo+o1JrotnyjlS08CbbF6CF5OPZPB8tu88g0xl1u7/3kkjgc0KEmE+bQTEF6RoLqtQ9XRdfHf+tjzLpUcfS7j/nzPcHJnU3d1PGU0NsR+QNyHvI2cfo8HLlmnL5V7GDX+iSMNKMJ8vq7lWwcHvxZjyrHRzSpmxsQJXkQN4hungnNjiNGWzJZ8FssSLLkHw3VlQVJ8mz9sugsCn3Gr/muwUG46W7AsxUqM0Oo1JrotnyjlT6yPhLDxoIzumCiet4Hf02Gxfox417aZ6Jw+BGXo/B9KtKAIlQfkV8Zen4leGaPYo+6G+NPE2a7E+g3FRb571HMiwddiHpNVYzpc/pGTxna1JDIOODExKTJPCrI47HGZGbG7O9aJwIssDZsit2a2pqTWa8x1ePRf8eLAsD/IV4DAlHmwZZhb9tqqco+HDHmZlqJy9pQv478OW0UDx/X0kTbIy4au5pZvJdODh4t31o5JaXBuJBVtYBkyKMDpr5D1Mx2mYsjHm95F0rfY1FyxjYo1JrotnyjlS08CbbF6CF5MlEbxEcfrXqIRR3QbB604fgc0KEmE+bQTEF6RoLqtQ9XRdfHf+tjzILJodvesFM/gbYzWVOUKLKkw3+uglxfg0H9K+suDCPJFRGRT6xxFCnTglsJo/q9b0bF+jHjXtpnu9+bNNgN22dfgeGCjXTZ52gwQVlALiNUkaR5cdbKH8gRWCS9TRcrE1pnHLSywkCgr2LCkRS/wEd1863dwA7HMJuY8TDqXWlYC/kkUF84Oo8kyKMDpr5D1NA31vurQ5/BHbrS0QR43dycGeIzhqufLnEBxK01e2CYnK3sqwBwwBa6qhdLFlh9DTb7pjjR5w00A/i74mi2g8Fq91vU5nj5kkoL7fsH3ChjxJwiAQA8RwGPsTnkCcQSnzqj8s+uTYMPFzmWuWd2UYP\"}";
//PublicData.disposeResponse(content,ptPublicKey,password);
}
/**
* 注册接口
*/
public static string testRegister()
{
String url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/register.pt";
Hashtable map = new Hashtable();
map.Add("taxpayerNum", platform_alias + "0003242300000"); //销方纳税人识别号
map.Add("enterpriseName", "测试C#"); //销方企业名称
map.Add("legalPersonName", "AA"); //法人名称
map.Add("contactsName", "AA"); //联系人名称
map.Add("contactsEmail", "1121@qq.com"); //联系人邮箱
map.Add("contactsPhone", "15111111133"); //联系人手机号
map.Add("regionCode", "11"); //地区编码
map.Add("cityName", "海淀区"); //市(区)名
map.Add("enterpriseAddress", "地址"); //详细地址
// TODO 请修改为正确的图片Base64传
map.Add("taxRegistrationCertificate", "sdddddddddddddddddddd"); //证件图片base64
string content = ToJson.Table2Json(map);
String builderrequest =
PublicData.publicparam(content, platform_code, platform_alias, privateKey, password);
string response = PostJson.Post4Json(url, builderrequest);
Console.Write("最终返回:" + response);
return response;
}
/**
* 开具蓝票
*/
public static string Blue()
{
string url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/invoiceBlue.pt";
ArrayList itemList = new ArrayList();
Hashtable OuterMessage = new Hashtable();
OuterMessage.Add("taxpayerNum", "500102201007206608");
// TODO 请更换请求流水号前缀
OuterMessage.Add("invoiceReqSerialNo", "SCPTT538117842484711");
OuterMessage.Add("buyerName", "购买购买方名称购买方名称购买方名称");
OuterMessage.Add("buyerAddress", "购买方地址");
OuterMessage.Add("buyerTel", "1234-56789104");
OuterMessage.Add("sellerBankAccount", "123456789");
OuterMessage.Add("sellerAddress", "深圳市福田区沙头街道天安社区深南大道车是多少大所大所大多所大所大cdtuiolj");
OuterMessage.Add("sellerTel", "17603327743");
OuterMessage.Add("takerEmail", "767034475@qq.com");
OuterMessage.Add("drawerName", "");
OuterMessage.Add("casherName", "收款人Dd");
OuterMessage.Add("reviewerName", "复核人Bb");
OuterMessage.Add("takerName", "");
OuterMessage.Add("definedData", "测试数据1,测试数据2");
Hashtable InnerMessageOne = new Hashtable();
InnerMessageOne.Add("taxClassificationCode", "1010101020000000000"); //税收分类编码(可以按照Excel文档填写)
InnerMessageOne.Add("quantity", "1.00"); //数量
InnerMessageOne.Add("goodsName", "货物名称"); //货物名称
InnerMessageOne.Add("unitPrice", "5.64"); //单价
InnerMessageOne.Add("invoiceAmount", "5.64"); //金额
InnerMessageOne.Add("taxRateValue", "0.16"); //税率
InnerMessageOne.Add("includeTaxFlag", "0"); //含税标识
Hashtable InnerMessageTwo = new Hashtable();
InnerMessageTwo.Add("taxClassificationCode", "1010101020000000000"); //税收分类编码(可以按照Excel文档填写)
InnerMessageTwo.Add("quantity", "1.00"); //数量
InnerMessageTwo.Add("goodsName", "货物名称"); //货物名称
InnerMessageTwo.Add("unitPrice", "5.64"); //单价
InnerMessageTwo.Add("invoiceAmount", "5.64"); //金额
InnerMessageTwo.Add("taxRateValue", "0.16"); //税率
InnerMessageTwo.Add("includeTaxFlag", "0"); //含税标识
itemList.Add(InnerMessageOne);
itemList.Add(InnerMessageTwo);
OuterMessage.Add("itemList", itemList);
string content = ToJson.Table2Json(OuterMessage);
String builderrequest =
PublicData.publicparam(content, platform_code, platform_alias, privateKey, password);
string response = PostJson.Post4Json(url, builderrequest);
Console.WriteLine("最终返回:" + response);
return response;
}
/**
* 红票开具接口
*/
public static void testInvoiceRed()
{
String url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/invoiceRed.pt";
Hashtable map = new Hashtable();
map.Add("taxpayerNum", "110101201702071"); //销方税号(请于要冲红的蓝票税号一致)
// TODO 请更换请求流水号前缀
map.Add("invoiceReqSerialNo", platform_alias + "5678902275418903"); //发票流水号 (唯一, 与蓝票发票流水号不一致)
map.Add("invoiceCode", "150003529999"); //冲红发票的发票代码
map.Add("invoiceNo", "61033842"); //冲红发票的发票号码
map.Add("redReason", "冲红"); //冲红原因
map.Add("amount", "-65.70"); //冲红金额 (要与原发票的总金额一致)
string content = ToJson.Table2Json(map);
String builderrequest =
PublicData.publicparam(content, platform_code, platform_alias, privateKey, password);
string response = PostJson.Post4Json(url, builderrequest);
Console.Write("最终返回:" + response);
}
/**
* 开票二维码接口
*/
public static void testGetQrCodeByItems() {
String url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/getQrCodeByItems.pt";
Hashtable map = new Hashtable();
map.Add("taxpayerNum", "110101201705230001"); //销方纳税人识别号
map.Add("enterpriseName", "测试"); //销方企业名称
map.Add("tradeNo", platform_alias + "10002001");//订单号(唯一)
map.Add("tradeTime", "2017-06-26 09:15:54"); //交易时间
map.Add("invoiceAmount", "100"); //发票金额(含税)
map.Add("casherName", "收款人A"); //收款人姓名(校验规则: 中文/字母大小写/及其两者组合)
map.Add("reviewerName", "审核人A"); //审核人姓名(校验规则: 中文/字母大小写/及其两者组合)
map.Add("drawerName", "开票人A"); //开票人姓名(校验规则: 中文/字母大小写/及其两者组合)
map.Add("allowInvoiceCount", "1"); //允许开票张数(非必填 默认值:1)
// map.put("smsFlag", "false"); //是否发送短信 (非必填 默认值:false 测试环境不发送短信)
// map.put("expireTime", ""); //有效时间 (非必填 默认值:永久有效 填写格式 yyyy-MM-dd HH:mm:ss)
// map.put("email","XXXXX@XX.com"); //二维码发送邮箱地址(非必填)
//其他参数见接口文档
ArrayList list = new ArrayList();
Hashtable listMapOne = new Hashtable();
listMapOne.Add("itemName", "小麦"); //开票项目名
listMapOne.Add("taxRateValue", "0.16"); //税率
listMapOne.Add("taxClassificationCode", "1010101020000000000");//税收分类编码
listMapOne.Add("quantity", "1"); //数量
listMapOne.Add("unitPrice", "50"); //单价
listMapOne.Add("invoiceItemAmount", "50"); //金额
Hashtable listMapTwo = new Hashtable();
listMapTwo.Add("itemName", "大米");
listMapTwo.Add("taxRateValue", "0.16");
listMapTwo.Add("taxClassificationCode", "1010101020000000000");
listMapTwo.Add("quantity", "1");
listMapTwo.Add("unitPrice", "50");
listMapTwo.Add("invoiceItemAmount", "50");
list.Add(listMapOne);
list.Add(listMapTwo);
map.Add("itemList", list);
string content = ToJson.Table2Json(map);
String builderrequest=PublicData.publicparam(content,platform_code,platform_alias,privateKey,password);
string response= PostJson.Post4Json(url, builderrequest);
Console.Write("最终返回:"+response);
}
/**
* 作废二维码接口
*/
public static void testdeleteInvoiceQrCode()
{
String url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/deleteInvoiceQrCode.pt";
string content = "[{\"taxpayerNum\":\"110101201705230001\",\"enterpriseName\":\"测试\",\"tradeNo\":\"DEMO10002001\",\"tradeTime\":\"2017-06-26 09:15:54\",\"invoiceAmount\":\"100\"}]";
String builderrequest=PublicData.publicparam(content,platform_code,platform_alias,privateKey,password);
string response= PostJson.Post4Json(url, builderrequest);
Console.Write("最终返回:"+response);
}
/**
* 查询发票
*/
public static void testqueryInvoice()
{
String url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/queryInvoice.pt";
// String url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/queryInvoiceInfo.pt"; //查询发票票面全面信息地址
String content =
"{ \"taxpayerNum\": \"110101201702071\", \"invoiceReqSerialNo\": \"DEMO6678997514279636\"}";
String builderrequest =
PublicData.publicparam(content, platform_code, platform_alias, privateKey, password);
string response = PostJson.Post4Json(url, builderrequest);
Console.Write("最终返回:" + response);
}
/*
* 获取库存接口
*/
public static void testGetInvoiceRepertoryInfo()
{
String url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/getInvoiceRepertoryInfo.pt";
String content = "{\"taxpayerNum\":\"110101201702071\",\"enterpriseName\":\"电子票测试新1\"}";
String builderrequest =
PublicData.publicparam(content, platform_code, platform_alias, privateKey, password);
string response = PostJson.Post4Json(url, builderrequest);
Console.Write("最终返回:" + response);
}
/**
* 插入微信卡包接口
*/
public static void testAuthWeChatCards()
{
String url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/authWeChatCards.pt";
String content = "{\"taxpayerNum\":\"110101201705230001\",\"invoiceReqSerialNo\":\"GAGA0000000000000009\"}";
String builderrequest =
PublicData.publicparam(content, platform_code, platform_alias, privateKey, password);
string response = PostJson.Post4Json(url, builderrequest);
Console.Write("最终返回:" + response);
}
/**
* 查询发票抬头
*/
public static void testTitleInfo()
{
String url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/getInvoiceTitleInfo.pt";
String content = "{\"enterpriseName\":\"测试\"}";
String builderrequest =
PublicData.publicparam(content, platform_code, platform_alias, privateKey, password);
string response = PostJson.Post4Json(url, builderrequest);
Console.Write("最终返回:" + response);
}
/**
* 查询票通宝状态接口
*/
public static void testGetPTBoxStatus()
{
String url = "http://fpkj.testnw.vpiaotong.cn/tp/openapi/getPTBoxStatus.pt";
String content = "{\"taxpayerNum\":\"110101201702071\",\"enterpriseName\":\"电子票测试新1\"}";
String builderrequest =
PublicData.publicparam(content, platform_code, platform_alias, privateKey, password);
string response = PostJson.Post4Json(url, builderrequest);
Console.Write("最终返回:" + response);
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Text;
namespace ConsoleDemo
{
internal class ToJson
{
public static String Table2Json(Hashtable table)
{
StringBuilder jsonstr =new StringBuilder();
jsonstr.Append("{");
foreach (DictionaryEntry tableEntry in table)
{
if (tableEntry.Key=="itemList")
{
String liststr=list2json((ArrayList)tableEntry.Value);
jsonstr.Append(string.Format("\"{0}\":{1},", tableEntry.Key,liststr));
}
else
{
jsonstr.Append(string.Format("\"{0}\":\"{1}\",", tableEntry.Key, tableEntry.Value));
}
}
jsonstr.Append("}");
jsonstr.Remove(jsonstr.Length - 2, 1);
return jsonstr.ToString();
// Console.Write(jsonstr.ToString());
}
private static String list2json(ArrayList list)
{
StringBuilder jsonstr =new StringBuilder();
jsonstr.Append("[");
foreach (Hashtable valuelist in list)
{
jsonstr.Append(Table2Json(valuelist)) ;
jsonstr.Append(",");
}
jsonstr.Remove(jsonstr.Length - 1, 1);
jsonstr.Append("]");
return jsonstr.ToString();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,206 @@
票通电子发票平台对接指引
V1.0
北京票通信息技术有限公司
2024 年 10 月 16 日
修订文档历史记录
日期
版本
2024-10-16
<1.0>
说明
编写初稿,包括对接文档及 sdk 获取,企业注册开通说明,
开票场景,冲红场景,数电账号管理、补充场景说明等内容
作者
饶森林
本文档基于票通电子发票平台提供的接口能力进行说明,为合作伙伴对接票通系统时提
供简要指引和参考。
一、对接前事项
票通提供对接服务,随时可启动对接。对接前合作伙伴或客户需提供一些简单的信息。
接入方名称:
接入方联系人及电话:
接入方产品类型:企业定制开发产品、企业私有化部署系统、SaaS 产品
接入方产品所属行业:餐饮收银、物业收银、停车收费、供热收费等等
接入方需要的开票场景:扫码开票、订单开票、小程序开票、支付开票等
接入方预计服务的企业数量:用于评估需要接入的接口范围
该信息可发给票通商务人员,或直接发到对接沟通群。
二、如何启动对接
票通商务人员确认需要提供对接服务时,建立微信群,拉入相关的人员,票通侧需拉入
产品人员、对接人员、服务或运营人员。客户侧视客户情况拉入相关人员,一般建议有商务、
产品和研发人员。
在微信群,票通对接人员发送标准接口文档《票通数电发票接口文档 3.X.X.pdf》和 SDK
工具包,目前 SDK 工具包支持 Java 和 C#两种开发语言。
对接所需的测试环境及参数信息:如平台编码,RSA 签名验签的证书,3des 加密秘钥,
可用于测试开票的税号等由票通对接人员提供。接入方系统开发完成,正式上线前,联系票
通对接人员提供正式环境的参数信息。
正式对接前,可先参考该文档,如有疑问可在微信群组织会议沟通。
三、企业注册开通
要通过接口开具发票,需要开票的企业先在票通平台完成入驻流程,完成入驻有以下几
种方法。(联调测试阶段,可使用票通对接人员提供测试税号)
1)客户在票通平台直接注册
客户可以在票通企业版平台申请注册(注册地址:https://fpkj.vpiaotong.com/register
也可以在票通集团版(票通集团版账号可联系票通商务人员获取)的机构管理功能中添加需
要入驻的企业。申请提交后,需要票通平台运营人员或有权限的代理商进行审核。该过程如
有问题可联系商务人员沟通。
2)接口注册
针对企业数量较多的平台,可通过票通接口完成注册,使用上述接口文档中的“2.2.注
册企业”提交注册信息。接口注册的企业,无登录密码,用户如需使用票通平台,可在票通
平台,通过注册时预留的手机号,获取短信验证码完成密码设置,使用设置后的密码即可登
录票通平台。
基础文档仅提供了提交注册接口,如需获取票通的审核状态,可通过查询接口或推送接
口获取审核状态,如需接口可联系票通对接人员提供。
3)可由票通代理商协助完成注册
联系票通代理商进行操作。
通过接口注册的企业,平台和企业的绑定关系自动生成,如果是使用票通产品功能完成
的注册或已经在票通完成入驻的企业,需要联系票通商务人员或运营人员完成接入方平台和
开票企业的绑定关系。
四、主要的开票场景
一般情况下,仅完成基础开票,对接少量的一至两个接口即可。根据开票场景不同,我
们分别介绍。
1)扫码开票
扫码开票是票通提供的特色能力,针对一些当面交易场景,企业的顾客消费完成后,现
场获取交易小票,可在小票后追加打印开票二维码,顾客可自行扫码,填写完成开票。
顾客侧操作如下图示意:
该过程中,二维码信息生成,可调用票通接口文档中提供的“2.16.获取开票二维码”
接入方系统传入交易相关信息,如商品、单价、数量、税率等,票通为该笔交易生成对应的
开票链接和二维码并同步返回给接入方,接入方系统展示或打印二维码到小票上。
用户扫码后,看到的开票界面是由票通提供,该界面集成了发票抬头模糊检索,获取微
信或支付宝抬头,默认记录上次开票的发票抬头等功能,可方便顾客快速填充抬头信息,并
支持填写邮箱地址和手机号,票通会自动发送邮件,已开通短信服务的企业票通也会自动发
送短信推送发票。票通开票 H5 支持扫描多个二维码合并开票,或将一个二维码拆分开票,
商户自行设置即可。
提交发票后,还可授权将发票插入微信发票卡包或支付宝发票管家,发票开具成功后,
票通自动推到发票到顾客的微信发票卡包或支付宝发票管家。
平台发票开具成功,会调用“2.13.推送发票主要信息”接口推送开具成功的发票,接入
方也可调用“2.18.查询二维码开票信息”主动查询二维码开票状态。
如果用户侧产生退货,未开票情况下可调用“2.17.批量作废开票二维码”直接作废开票
二维码。如果二维码已开票,可调用“2.10.快捷冲红数电发票(全额冲红)”冲红已开具发
票。
注意:2.10、2.13、2.17、2.18 为可选接口,如不对接接口,相关操作也可以通过票通
产品功能完成。
2)直接开票
直接开票接口是通用的开票能力,系统接入方组织好待开票信息后,直接调用“2.9.开
具蓝字数电发票”接口完成开票申请提交,票通服务会实时返回结果,并附带一个链接,通
过该链接可打开 H5 界面实时查看发票开具状态。
票通接收开票申请后,处理开票过程,开票成功或开票异常,都会通过“2.13.推送发票
主要信息”接口,推送相关的信息。
目前数电票开具,需要开票人保持电子税务局账号的登录状态,如果需要开票人员进行
登录认证或风险认证时,票通将会 2.13 接口返回 3999 的特定错误状态,该状态表明需要提
供开票人完成相关认证。数电账号的认证问题,会在后续第六章节详细说明。
注意:提交开票时,发票请求流水号字段,唯一代表一张发票,同一个发票请求流水号
不会重复开票,接入方系统遇到一些场景需重试开票时,如果为同一张发票,请不要变更发
票请求流水号,否则可能有导致重开发票的风险。
3)单据开票
票通集团版提供的单据管理的能力,支持拆分开票、合并开票、支持直接传入订单信息
后续补充发票抬头等进行开票。该接口并非基础能力接口,如有需要财务人员介入进行审核
或拆分合并开票的场景,可使用该套接口能力,联系票通商务人员,安排提供相关的接口文
档,该功能的使用需依赖票通集团版系统。
五、冲红场景
当顾客产生退货或发票开具错误时,票通提供了多种冲红操作能力,票通企业版、集团
版均提供有手工冲红和一键冲红的能力,接口能力也提供了快捷冲红(1 个接口)和全场景
冲红(需 4-8 个接口组合使用)
数电票冲红需要发起红字确认单,确认单申请通过后才可发起真正的冲红操作,流程相
对较长,接口较多,一般建议接入方系统使用快捷冲红接口即可,快捷冲红接口服务对冲红
逻辑进行了封装,由票通整合红字信单申请管理和冲红功能,有效减轻接入方系统的开发工
作量。
1)快捷冲红
快捷冲红时,如仅需全部冲红,可对接“2.10.快捷冲红数电发票(全额冲红)”,该接口
参数简单;如需支持部分冲红,可对接“2.37.快捷冲红数电发票(全额冲红、部分冲红)”,
该接口参数比较完整,支持场景更多,部分冲红和全额冲红都支持,如果需要进行部分冲红,
建议调用“2.36.初始化红字信息确认单”完成初始化,该初始化的目的是加载剩余可冲红的
发票信息,无需接入方系统自行管理剩余可冲红的商品信息。
2)全场景冲红管理
全场景冲红需对接“2.28.红字发票确认单申请”、“2.29.查看红字发票确认单”、“2.30.
开具红字数电发票”、“2.31.红字发票确认单审核”、“2.32.红字发票确认单撤销”、“2.33.红
字发票确认单查询(下载)”、“2.34.获取红字发票确认单查询(下载)结果”、
“2.36.初始化
红字信息确认单”接口,用于精细化管理红字发票确认单及冲红。
销方申请冲红管理流程如下:
发票冲红流程及事项:
数电发票冲红,均需发起红字发票确认单申请,
普通发票申请,如果对方未进行入账操作,则申请后无需确认,调用红字信息表查询接
口获取到已确认(或无需确认)状态后,可进行冲红。
专用发票申请,如果对方未入账未勾选,则申请后无需确认,其他情况需对方确认,对
方确认通过后,调用红字信息表查询接口获取已确认(或无需确认)状态,接下来可进行冲
红。
对方企业发起的红字信息表,可通过红字信息表查询(下载)接口获取红字信息表,对
需要审核的红字信息表,可通过红字信息表审核接口,进行拒绝或者通过。
数电发票(普通发票)可冲红对应的增值税普通发票,包括普通电子发票。
数电发票(增值税专用发票),可冲红对应的增值税专用发票,包括专用电子发票。
接口同时支持购方申请红字确认单,冲红动作需销方执行。
注:冲红接口为非必须对接的接口,有些开发资源紧张或财务人员可人工处理冲红的情
况下,可以不对接冲红接口。或者将冲红功能放入后续的迭代开发。
六、数电账号管理
数电发票的开具,需要用户先在票通平台完成电子税务局账号的登录和风险认证。对于
接入方系统期望在系统内完成认证过程的,可使用票通提供的接口能力。如接入方系统仅需
完成开票,不关注认证过程,可使用票通成熟的认证方式。可通过票通企业版、集团版、微
票通 APP、票通云小程序、票通云服务公众号都可完成认证。推荐使用票通云公众号,该方
式认证无需登录票通账号,可直接在公众号进行数电账号的绑定、认证和对未认证导致开
票失败的发票进行重开。
1)接口能力
票通提供了“2.3.数电账号登记”、“2.4.获取登录短信验证码”、“2.5.短信登录”、“2.6.
获取实名认证二维码”、
“2.7.查询实名认证二维码扫码状态”
“2.8.查询数电账号认证状态”、
“2.45.查询数电账号列表”
、“2.46.退出电子税局登录”等接口完成数电账号的认证。
这里支持几种场景。
1. 接入方系统仅需要数电账号信息,用于开票时指定开票人,可使用 2.45 接口获取
在票通维护好的数电账号及对应状态即可。在开票时传入开票人信息。
2. 接入方系统需进行是数电账号维护的,可调用 2.3 数电账号登记接口,该接口为实
时接口,可验证用户输入的账号密码是否正确。
3. 接入方系统如需完成认证,需首先通过 2.3 或 2.45 获取登录信息,然后调用 2.4 和
2.5 完成短信登录认证,使用 2.6、2.7 完成风险码扫码认证。如需查询各状态的登录或认证
状态,可通过 2.8 接口完成认证。
2)票通云公众号
票通云服务公众号提供了您和您的数电账号关联的两种方式,一种是通过在企业版或集
团版对应数电账号后的关注二维码,扫码关注后,建立用户和数电账号的绑定关系。一种是
直接在票通云公众号-数电认证功能,通过输入手机号或电子税务局的登录账号密码完成验
证,获取属于用户的数电账号列表进行绑定。
“票通云服务”公众号提供的能力:
1. 用户绑定的账号未登录或未认证,导致发票开票失败时,票通将会通过消息通知进
行提醒,用户可直接点击通知,进入到认证界面,完成相应的认证,认证完成后可直接选择
是否重开发票及重开发票的范围(当前失败的发票或 30 天内因为该错误导致失败的发票)
2. 可通过票通云服务-数电认证功能,主动完成登录认证或风险认证,该场景适用于
一些企业为提高顾客体验,要求企业员工按时认证的情况,数电认证功能,无论当前账号处
于何种状态,都可以随时进行再次认证。
3.支持退出登录,目前通过短信方式完成登录,可保持很长的登录状态。
注意:由于目前短信登录方式保持登录的有效期很长,在 PC 端登录进行审核或其他操
作时,可能会被票通平台挤掉,可提醒用户先在票通平台退出登录,然后在电子税务局 PC
端进行操作。退出登录操作可使用票通产品或公众号,也可以通过接口 2.46 完成。
七、补充场景
1)发票抬头获取接口
接入方系统如需自行开发 H5 或需要帮助用户补充抬头信息时,可使用发票抬头获取接
口,该接口非基础接口,请联系商务人员获取。
2)开票项目智能赋码接口
接入方系统如需管理大量的商品品目时,可通过智能赋码接口完成对商品的税收分类编
码设置。该接口非基础接口,请联系商务人员获取。
3SaaS 平台类对接工单接口
接入方系统如果服务的企业较多,需要完善的线上原因流程支持,可联系票通商务人员
和对接人员,提供工单的接口支持,工单接口包括套餐(订单)创建、企业绑定、套餐续费
等能力。
八、更多支持
票通平台提供了多种场景组合的接口能力,如需交流沟通,可在微信群直接沟通或组织
会议进行沟通。