发票 Golang SDK 专为电子发票、数电发票(全电发票)场景设计,支持开票、红冲、版式文件下载等核心功能,快速对接税务平台API。
关键词: 电子发票SDK,数电票Golang,开票接口,发票api,发票开具,发票红冲,全电发票集成
- ✅ 获取授权 - 快速接入税务平台身份认证
- ✅ 人脸二维码登录 - 支持数电发票平台扫码登录
- ✅ 认证状态查询 - 实时获取纳税人身份状态
- 🟦 数电蓝票开具 - 支持增值税普通/专用电子发票
- 📄 版式文件下载 - 自动获取销项发票PDF/OFD/XML文件
- 🔍 红冲前蓝票查询 - 精确检索待红冲的电子发票
- 🛑 红字信息表申请 - 生成红冲凭证
- 🔄 负数发票开具 - 自动化红冲流程
go get github.com/fapiaoapi/invoice-sdk-golang- 精准覆盖中国数电发票标准 - 严格遵循国家最新接口规范
- 开箱即用 - 无需处理XML/签名等底层细节,专注业务逻辑
- 企业级验证 - 已在生产环境处理超100万张电子发票
| 发票类型 | 状态 |
|---|---|
| 数电发票(普通发票) | ✅ 支持 |
| 数电发票(增值税专用发票) | ✅ 支持 |
| 数电发票(铁路电子客票) | ✅ 支持 |
| 数电发票(航空运输电子客票行程单) | ✅ 支持 |
| 数电票(二手车销售统一发票) | ✅ 支持 |
| 数电纸质发票(增值税专用发票) | ✅ 支持 |
| 数电纸质发票(普通发票) | ✅ 支持 |
| 数电纸质发票(机动车发票) | ✅ 支持 |
| 数电纸质发票(二手车发票) | ✅ 支持 |
- 提交Issue: 问题反馈
- 商务合作: yuejianghe@qq.com
package main
import (
"bufio"
"context"
"fmt"
"os"
"strconv"
"time"
"github.com/fapiaoapi/invoice-sdk-golang"
"github.com/redis/go-redis/v9"
"github.com/skip2/go-qrcode"
)
var (
appKey = ""
appSecret = ""
nsrsbh = "" // 统一社会信用代码
title = "" // 名称(营业执照)
username = "" // 手机号码(电子税务局)
password = "" // 个人用户密码(电子税务局)
xhdwdzdh = "重庆市两江区xxxx 1912284xxxx" // 地址和电话 空格隔开
xhdwyhzh = "中国工商银行 31000867092xxxx" // 开户行和银行账号 空格隔开
token = ""
accountType = "6" // 默认6 6基础 7标准
debug = true // true 是否开启调试模式 false 不开启
)
func main() {
// 从缓存redis中获取Token
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis服务地址(默认端口6379)
Password: "test123456", // Redis密码(无密码则留空)
DB: 0, // 使用默认数据库
})
// 创建客户端
client := invoice.NewClient(appKey, appSecret, debug)
//一 获取授权
GetToken(rdb, client, false)
//二 开具蓝票
/*
* 前端模拟数电发票/电子发票开具 (蓝字发票)
* @see https://fa-piao.com/fapiao.html?source=github
*
* 开具数电发票文档
* @see https://fa-piao.com/doc.html#api6?source=github
*
*/
invoiceResponse := BlueTicket(client)
switch invoiceResponse.Code {
case 200:
// 三 下载发票
DownloadPdfOfdXml(client, invoiceResponse.Fphm, invoiceResponse.Kprq)
case 420:
fmt.Println("登录(短信认证)")
/**
* 前端模拟短信认证弹窗
* @link https://fa-piao.com/fapiao.html?action=sms&source=github
*/
// 1. 发短信验证码
/**
* @link https://fa-piao.com/doc.html#api2?source=github
*/
loginResponse, err3 := client.LoginDppt(nsrsbh, username, password, "")
if err3 != nil {
fmt.Printf("发送短信验证码失败: %v\n", err3)
return
}
if loginResponse.Code == 200 {
fmt.Println("请输入验证码")
fmt.Printf("请在300秒内(%s前)输入验证码: ", time.Now().Add(300*time.Second).Format("2006-01-02 15:04:05"))
input := make(chan string, 1)
go func() {
sms, _ := bufio.NewReader(os.Stdin).ReadString('\n')
input <- sms
}()
select {
case sms := <-input:
fmt.Printf("\n你输入了: %s", sms)
// 2. 输入验证码
/**
* @link https://fa-piao.com/doc.html#api2?source=github
*/
loginResponse2, err4 := client.LoginDppt(nsrsbh, username, password, sms)
if err4 != nil {
fmt.Printf("短信验证失败: %v\n", err4)
return
}
if loginResponse2.Code == 200 {
fmt.Println(string(loginResponse2.Data))
fmt.Println("短信验证成功")
invoiceResponse := BlueTicket(client)
if invoiceResponse.Code == 200 {
DownloadPdfOfdXml(client, invoiceResponse.Fphm, invoiceResponse.Kprq)
}
}
case <-time.After(300 * time.Second):
fmt.Println("\n\n[错误] 输入超时!")
}
}
case 430:
fmt.Println("人脸认证")
/**
* 前端模拟人脸认证弹窗
* @link https://fa-piao.com/fapiao.html?action=face&source=github
*/
// 1. 获取人脸二维码
/**
* @link https://fa-piao.com/doc.html#api3?source=github
*/
qrCodeResponse, err5 := client.GetFaceImg(nsrsbh, map[string]string{
"username": username,
"type": "1",
})
if err5 != nil {
fmt.Printf("获取人脸二维码失败: %v\n", err5)
return
}
switch qrCodeResponse.Ewmly {
case "swj":
//判断 Ewmly 不为空 长度小于500 字符串转二维码图片base64
if qrCodeResponse.Ewm != "" && len(qrCodeResponse.Ewm) <= 500 {
//go get github.com/skip2/go-qrcode
// qrBase64, err6 := StringToQRCodeBase64(qrCodeResponse.Ewm)
// if err6 != nil {
// fmt.Printf("生成二维码base64失败: %v\n", err6)
// return
// } else {
// qrCodeResponse.Ewm = qrBase64
// // 可选:加上 data URL 前缀,方便在 HTML 中直接使用
// qrCodeResponse.Ewm = "data:image/png;base64," + qrBase64
// fmt.Printf("人脸二维码base64: %s\n", qrCodeResponse.Ewm)
// }
}
fmt.Println("请使用电子税务局app扫码")
StringToQrcode(qrCodeResponse.Ewm)
fmt.Println("成功做完人脸认证,请输入数字 1")
fmt.Printf("请在300秒内(%s前)输入内容: ", time.Now().Add(300*time.Second).Format("2006-01-02 15:04:05"))
input := make(chan string, 1)
go func() {
text, _ := bufio.NewReader(os.Stdin).ReadString('\n')
input <- text
}()
select {
case text := <-input:
fmt.Printf("\n你输入了: %s", text)
// 2. 认证完成后获取人脸二维码认证状态
/**
* @link https://fa-piao.com/doc.html#api4?source=github
*/
rzid := qrCodeResponse.Rzid
faceStatusResponse, err6 := client.GetFaceState(nsrsbh, rzid, map[string]string{
"username": username,
"type": "1",
})
if err6 != nil {
fmt.Printf("获取人脸认证状态失败: %v\n", err6)
return
}
if faceStatusResponse != nil {
switch faceStatusResponse.Slzt {
case "1":
fmt.Println("未认证")
case "2":
fmt.Println("成功")
invoiceResponse := BlueTicket(client)
if invoiceResponse.Code == 200 {
DownloadPdfOfdXml(client, invoiceResponse.Fphm, invoiceResponse.Kprq)
}
case "3":
fmt.Println("二维码过期-->重新获取人脸二维码")
}
}
case <-time.After(300 * time.Second):
fmt.Println("\n\n[错误] 输入超时!")
}
case "grsds":
fmt.Println("个人所得税app扫码")
}
case 401:
fmt.Printf("%d 授权失败: %s\n", invoiceResponse.Code, invoiceResponse.Msg)
// 重新授权获取token
fmt.Println("成功")
GetToken(rdb, client, true)
invoiceResponse := BlueTicket(client)
if invoiceResponse.Code == 200 {
DownloadPdfOfdXml(client, invoiceResponse.Fphm, invoiceResponse.Kprq)
}
default:
fmt.Printf("异常 %d %s\n", invoiceResponse.Code, invoiceResponse.Msg)
}
}
func BlueTicket(client *invoice.Client) *invoice.InvoiceResponse {
/**
* 开具数电发票文档
* @link https://fa-piao.com/doc.html#api6?source=github
*
* 开票税额计算说明demo
* @link https://github.com/fapiaoapi/invoice-sdk-golang/blob/master/examples/tax_example.go
*/
invoiceParams := map[string]string{
"username": username,
"fpqqlsh": appKey + strconv.FormatInt(time.Now().Unix(), 10),//建议使用你们的订单ID
"fplxdm": "82",
"kplx": "0",
"xhdwsbh": nsrsbh,
"xhdwmc": title,
"xhdwdzdh": xhdwdzdh,
"xhdwyhzh": xhdwyhzh,
"ghdwmc": "个人",
"zsfs": "0",
"hjje": "9.99",
"hjse": "0.1",
"jshj": "10",
}
items := []invoice.InvoiceItem{
{
Fphxz: "0",
Spmc: "*信息技术服务*软件开发服务",
Je: "10",
Sl: "0.01",
Se: "0.1",
Hsbz: "1",
Spbm: "3040201010000000000",
},
}
invoiceResponse, err := client.BlueTicket(invoiceParams, items)
if err != nil {
fmt.Printf("开票失败: %v\n", err)
}
if invoiceResponse == nil {
fmt.Println("开票失败: 响应为空")
}
return invoiceResponse
}
func DownloadPdfOfdXml(client *invoice.Client, fphm string, kprq string) {
fmt.Printf("发票号码: %s\n", fphm)
fmt.Printf("开票日期: %s\n", kprq)
/**
* 获取销项数电版式文件 文档 PDF/OFD/XML
* @link https://fa-piao.com/doc.html#api7?source=github
*/
pdfResponse, err2 := client.GetVersionFile(nsrsbh, fphm, "4", map[string]string{
"kprq": kprq,
"username": username,
})
if err2 != nil {
fmt.Printf("下载发票失败: %v\n", err2)
} else if pdfResponse.Code == 200 {
fmt.Printf("下载发票结果: %s\n", string(pdfResponse.Data))
}
}
// 获取token 从redis获取或重新获取
func GetToken(rdb *redis.Client, client *invoice.Client, forceUpdate bool) {
key := nsrsbh + "@" + username + "@TOKEN"
if forceUpdate {
/**
* 获取授权Token文档
* @link https://fa-piao.com/doc.html#api1?source=github
*/
authResult, authErr := client.GetAuthorization(nsrsbh, accountType, "", "")
// authResult, authErr := client.GetAuthorization(nsrsbh, accountType, username, password)
if authErr != nil {
fmt.Printf("授权失败: %v\n", authErr)
} else {
token = authResult.Token
client.SetToken(token)
//缓存Token到redis 过期时间30天 key建议是nsrsbh+'@TOKEN'
err := rdb.Set(context.Background(), key, token, 30*24*time.Hour).Err()
if err != nil {
fmt.Printf("缓存Token到Redis失败: %v\n", err)
}
fmt.Printf("授权成功,Token: %s\n", token)
}
} else {
result, err := rdb.Get(context.Background(), key).Result()
if err == nil && result != "" {
token = result
client.SetToken(token)
fmt.Printf("Token from Redis")
} else if err != nil && err != redis.Nil {
fmt.Printf("从Redis获取Token异常: %v\n", err)
} else {
authResult, authErr := client.GetAuthorization(nsrsbh, accountType, "", "")
// authResult, authErr := client.GetAuthorization(nsrsbh, accountType, username, password)
if authErr != nil {
fmt.Printf("授权失败: %v\n", authErr)
} else {
token = authResult.Token
client.SetToken(token)
//缓存Token到redis 过期时间30天 key建议是nsrsbh+'@TOKEN'
err := rdb.Set(context.Background(), key, token, 30*24*time.Hour).Err()
if err != nil {
fmt.Printf("缓存Token到Redis失败: %v\n", err)
}
fmt.Printf("授权成功,Token: %s\n", token)
}
}
}
}
func StringToQrcode(text string) {
qr, err := qrcode.New(text, qrcode.Low)
if err != nil {
panic(err)
}
matrix := qr.Bitmap()
for y := 0; y < len(matrix); y += 2 {
for x := 0; x < len(matrix[y]); x++ {
top := matrix[y][x]
bottom := false
if y+1 < len(matrix) {
bottom = matrix[y+1][x]
}
switch {
case top && bottom:
fmt.Print("█")
case top && !bottom:
fmt.Print("▀")
case !top && bottom:
fmt.Print("▄")
default:
fmt.Print(" ")
}
}
fmt.Println()
}
}
// func StringToQRCodeBase64(text string) (string, error) {
// // 生成二维码(PNG 格式写入内存 buffer)
// var buf bytes.Buffer
// qr, err := qrcode.New(text, qrcode.Medium)
// if err != nil {
// return "", err
// }
// if err := qr.Write(256, &buf); err != nil {
// return "", err
// }
// // 将字节转为 Base64
// encoded := base64.StdEncoding.EncodeToString(buf.Bytes())
// return encoded, nil
// }
发票税额计算demo | 扫码登录demo | 发票红冲demo | 部分发票红冲demo|