Go library to convert GOBL invoices into TicketBAI declarations and send them to the Basque Country web services.
This library assumes that clients will handle a local database of previous invoices in order to comply with the local requirements of chaining all invoices together.
Copyright Invopop Ltd. 2023. Released publicly under the GNU Affero General Public License v3.0. For commercial licenses please contact the dev team at invopop. For contributions to this library to be accepted, we will require you to accept transferring your copyright to Invopop Ltd.
The basque country is split into 4 "foral" tax agencies. 3 of those tax agencies decided to adopt the TicketBAI format. Each of the three agencies has their own set of documentation and integration definitions.
Links to key information for each agency are described in the following subchapters.
- BOB: https://www.batuz.eus/fitxategiak/batuz/normativa/2020%2009%2011%20ORDEN%20FORAL%201482-2020,%20de%209%20de%20septiembre.pdf?hash=929e00a8bbd774ee911841d36e3301e2
- XSD basic TicketBAI request: https://www.batuz.eus/fitxategiak/batuz/ticketbai/ticketBaiV1-2.xsd
- XSD complete list: https://www.batuz.eus/fitxategiak/Batuz/LROE/esquemas/Esquemas%20XSD.7z
- Ley IVA Foral, con datos útiles sobre exclusiones (a partir de página 24): https://www.bizkaia.eus/ogasuna/zerga_arautegia/indarreko_arautegia/pdf/ca_7_1994.pdf
- FAQ Gipuzkoa: https://www.gipuzkoa.eus/es/web/ogasuna/ticketbai/faq (download the PDF from that page, it's much easier to read.)
You must have first created a GOBL Envelope containing an Invoice that you'd like to send to one of the TicketBAI web services.
For the document to be converted, the supplier contained in the invoice should have a "Tax ID" with the country set to ES
.
TicketBAI is used by three different tax agencies (Haciendas Forales), each of which has their own API and specific requirements. The es-tbai-region
extension defined in the GOBL Invoice's tax
property is used to set the determine the correct API to utilize. This will be set automatically in most cases.
The following is an example of how the GOBL TicketBAI package could be used:
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/invopop/gobl"
ticketbai "github.com/invopop/gobl.ticketbai"
"github.com/invopop/xmldsig"
)
func main() {
ctx := context.Background()
// Load sample envelope:
data, _ := os.ReadFile("./test/data/sample-invoice.json")
env := new(gobl.Envelope)
if err := json.Unmarshal(data, env); err != nil {
panic(err)
}
zone := ticketbai.ZoneFor(env)
// Prepare software configuration:
soft := &ticketbai.Software{
License: "XYZ", // provided by tax agency
NIF: "B123456789", // Software company's tax code
Name: "Invopop", // Name of application
Version: "v0.1.0", // Software version
}
// Load sample certificate:
cert, err := xmldsig.LoadCertificate(
"./test/certs/EnpresaZigilua_SelloDeEmpresa.p12", "IZDesa2021")
if err != nil {
panic(err)
}
// Instantiate the TicketBAI client with sofrward config
// and specific zone.
tc, err := ticketbai.New(soft, zone,
ticketbai.WithCertificate(cert), // Use the certificate previously loaded
ticketbai.WithSupplierIssuer(), // The issuer is the invoice's supplier
ticketbai.InTesting(), // Use the tax agency testing environment
)
if err != nil {
panic(err)
}
// Create a new TBAI document:
doc, err := tc.Convert(env)
if err != nil {
panic(err)
}
// Create the document fingerprint
// Assume here that we don't have a previous chain data object.
if err = tc.Fingerprint(doc, nil); err != nil {
panic(err)
}
// Sign the document:
if err := tc.Sign(doc, env); err != nil {
panic(err)
}
// Create the XML output
bytes, err := doc.BytesIndent()
if err != nil {
panic(err)
}
// Do something with the output, you probably want to store
// it somewhere.
fmt.Println("Document created:\n", string(bytes))
// Grab and persist the Chain Data somewhere so you can use this
// for the next call to the Fingerprint method.
cd := doc.ChainData()
// Send to TicketBAI, if rejected, you'll want to fix any
// issues and send in a new XML document. The original
// version should not be modified.
if err := tc.Post(ctx, doc); err != nil {
panic(err)
}
}
The GOBL TicketBAI package tool also includes a command line helper. You can find pre-built gobl.cfdi binaries in the github repository, or install manually in your Go environment with:
go install github.com/invopop/gobl.ticketbai
We recommend using a .env
file to prepare configuration settings, although all parameters can be set using command line flags. Heres an example:
CERTIFICATE_PATH="./test/certs/EntitateOrdezkaria_RepresentanteDeEntidad.p12"
CERTIFICATE_PASSWORD=IZDesa2021
SOFTWARE_COMPANY_NIF=B85905495
SOFTWARE_COMPANY_NAME="Invopop S.L."
SOFTWARE_NAME="Invopop"
SOFTWARE_LICENSE="TBAIBI00000000PRUEBA" # BI & SS
SOFTWARE_VERSION="1.0"
To convert a document to XML, run:
gobl.ticketbai convert ./test/data/sample-invoice.json
To submit to the tax agency testing environment:
gobl.ticketbai send ./test/data/sample-invoice.json
-
Tickebai allows more than one customer per invoice, but GOBL only has one possible customer.
-
Invoices should have a note of type general that will be used as a general description of the invoice. If an invoice is missing this info, it will be rejected with an error.
-
Currently GOBL does not allow to distinguish between different VAT regimes. Ticketbai format requires a list of the different regimes applied to the invoice so currently only equivalence surcharge and general regime are available (for a complete list of the other possibilities you can check the documentation on https://www.batuz.eus/es/documentacion-tecnica)
-
GOBL's corrective invoices aren't supported at the moment. Only credit and debit notes are, and they are converted into "Facturas Rectificativas por Diferencias" with either positive or inverted quantities depending on whether it is a debit or a credit note.
In order to provide the supplier specific data required by TicketBAI, invoices need to include a bit of extra data. We've managed to simplify these into specific cases.
Invoice tax tags can be added to invoice documents in order to reflect a special situation. The following schemes are supported:
simplified-scheme
- a retailer operating under a simplified tax regime (regimen simplificado) that must indicate that all of their sales are under this scheme. This implies that all operations in the invoice will have theOperacionEnRecargoDeEquivalenciaORegimenSimplificado
tag set toS
.reverse-charge
- B2B services or goods sold to a tax registered EU member who will pay VAT on the suppliers behalf. Implies that all items will be classified under theTipoNoExenta
value ofS2
.customer-rates
- B2C services, specifically for the EU digital goods act (2015) which imply local taxes will be applied. All items will specify theDetalleNoSujeta
cause ofRL
.
The following extension can be applied to each line tax:
-
es-tbai-product
– allows to correctly group the invoice's lines taxes in the TicketBAI breakdowns (a.k.a. desgloses). These are the valid values:services
- indicates that the product being sold is a service (as opposed to a physical good). Services are accounted in theDesgloseTipoOperacion > PrestacionServicios
breakdown of invoices to foreign customers. By default, all items are considered services.goods
- indicates that the product being sold is a physical good. Products are accounted in theDesgloseTipoOperacion > Entrega
breakdown of invoices to foreign customers.resale
- indicates that a line item is sold without modification from a provider under the Equalisation Charge scheme. (This implies that theOperacionEnRecargoDeEquivalenciaORegimenSimplificado
tag will be set toS
).
-
es-tbai-exemption
- identifies the specific TicketBAI reason code as to why taxes should not be applied to the line according to the whole set of exemptions or not-subject scenarios defined in the law. It has to be set along with the tax rate value ofexempt
. These are the valid values:E1
– Exenta por el artículo 20 de la Norma Foral del IVAE2
– Exenta por el artículo 21 de la Norma Foral del IVAE3
– Exenta por el artículo 22 de la Norma Foral del IVAE4
– Exenta por el artículo 23 y 24 de la Norma Foral del IVAE5
– Exenta por el artículo 25 de la Norma Foral del IVAE6
– Exenta por otra causaOT
– No sujeto por el artículo 7 de la Norma Foral de IVA / Otros supuestosRL
– No sujeto por reglas de localización (*)
(*) As noted elsewhere, RL
will be set automatically set in invoices using the customer-rates
tax tag. It can also be set explicitly using the es-tbai-exemption
extension in invoices not using that tag.
Under what situations should the TicketBAI system be expected to function:
- B2B & B2C: regular national invoice with VAT. Operation with minimal data.
- B2B Provider to Retailer: Include equalisation surcharge VAT rates
- B2B Retailer: Same as regular invoice, except with invoice lines that include
ext[es-tbai-product] = resale
when the goods being provided are being sold without modification (recargo de equivalencia), very much related to the next point. - B2B Retailer Simplified: Include the simplified scheme key. (This implies that the
OperacionEnRecargoDeEquivalenciaORegimenSimplificado
tag will be set toS
). - EU B2B: Reverse charge EU export, scheme: reverse-charge taxes calculated, but not applied to totals. By default all line items assumed to be services. Individual lines can use the
ext[es-tbai-product] = goods
value to identify when the line is a physical good. Operations like this are normally assigned the TipoNoExenta value of S2. If however the service or goods are exempt of tax, each line's taxext[exempt]
field can be used to identify a reason. - EU B2C Digital Goods: use tax tag
customer-rates
, that applies VAT according to customer location. In TicketBAI, these cases are "not subject" to tax, and thus should have the cause RL (por reglas de localización).
Some sample test data is available in the ./test
directory. To update the JSON documents and regenerate the XML files for testing, use the following command:
go test ./examples_test.go --update
All generate XML documents will be validated against the TicketBAI XSD documents.