Package e-factura-go aims to be your one-stop shop for ANAF VAT, RO e-Factura and e-Transport APIs.
Features of this library:
- Support for all e-Factura API endpoints (update 2025-02-04)
via
*efactura.Client:- upload B2B XML invoice (
/upload) - upload B2C XML invoice (
/uploadb2c) - get message (invoice) state (
/stareMesaj) - get messages (invoices) list (
/listaMesajeFactura) - get messages list paginated (
/listaMesajePaginatieFactura) - download invoice zip (
/download) - validate XML (
/validare/{standard}) - transform invoice XML to PDF (
/transformare/{standard}) - validate XML signature (
/api/validate/signature)
- upload B2B XML invoice (
- Support for most e-Transport API endpoints (update 2024-07-29)
via
*etransport.Client:- upload V2 declaration (
/upload/{standard}/{cif}/2) - list declarations (
/lista/{zile}/{cif}) - get declaration state (
/stareMesaj/{id})
- upload V2 declaration (
- Support ANAF VAT v9 API via
*vat.Client. - Support for generating authorization links and exchange an authorization code for an access token (USB signature required).
- CLI commands for both e-Factura and e-Transport APIs.
- e-Factura Invoice-2 UBL (as defined by RO16931, CIUS-RO version 1.0.9) building via native Go structs (
CreditNotenot yet supported). - e-Transport v2 declaration XML building via native Go structs.
e-factura-go requires Go version >= 1.24. See RO e-Factura, RO e-Transport, ANAF VAT sections for using each package.
This package has also cli commands for the e-Factura and e-Transport API (the commands implement most of the APIs).
To install efactura-cli:
go install github.com/printesoi/e-factura-go/cmd/efactura-cli@latestthen run efactura-cli help for options (this assumes $GOPATH/bin is in your $PATH).
To install etransport-cli:
go install github.com/printesoi/e-factura-go/cmd/etransport-cli@latestthen run etransport-cli help for options (this assumes $GOPATH/bin is in your $PATH).
Construct the required OAuth2 config needed for an e-factura or e-transport Client:
import (
efactura_oauth2 "github.com/printesoi/e-factura-go/pkg/oauth2"
)
oauth2Cfg, err := efactura_oauth2.MakeConfig(
efactura_oauth2.ConfigCredentials(anafAppClientID, anafAppClientSecret),
efactura_oauth2.ConfigRedirectURL(anafAppRedirectURL),
)
if err != nil {
// Handle error
}Generate an authorization link for certificate authorization:
authorizeURL := oauth2Cfg.AuthCodeURL(state)
// Redirect the user to authorizeURLGetting a token from an authorization code (the parameter code sent via GET
to the redirect URL):
// Assuming the oauth2Cfg is built as above
token, err := oauth2Cfg.Exchange(ctx, authorizationCode)
if err != nil {
// Handle error
}If you specified a non-empty state when building the authorization URL, you
will also receive the state parameter with code.
Parse the initial token from JSON:
token, err := efactura_oauth2.TokenFromJSON([]byte(tokenJSON))
if err != nil {
// Handle error
}For a quick guide on how to use the CLI tools to get an access token for testing, check this documentation
This package can be use both for interacting with (calling) the RO e-factura API via the Client object and for generating an Invoice-2 UBL XML.
Important
In order to use the efactura.Client, a valid OAuth2 token is needed.
Construct a new simple client for production environment:
import (
"github.com/printesoi/e-factura-go/pkg/efactura"
)
ctx := context.TODO()
client, err := efactura.NewProductionClient(ctx, efactura_oauth2.TokenSource(ctx, token))
if err != nil {
// Handle error
}Construct a new simple client for sandbox (test) environment:
ctx := context.TODO()
client, err := efactura.NewSandboxClient(ctx, efactura_oauth2.TokenSource(ctx, token))
if err != nil {
// Handle error
}If you want to store the token in a store/db and update it everytime it
refreshes use efactura_oauth2.TokenSourceWithChangedHandler:
ctx := context.TODO()
onTokenChanged := func(ctx context.Context, token *xoauth.Token) error {
// Token changed, maybe update/store it in a database/persistent storage.
return nil
}
client, err := efactura.NewSandboxClient(ctx,
efactura_oauth2.TokenSourceWithChangedHandler(ctx, token, onTokenChanged))
if err != nil {
// Handle error
}E-factura APIs expect dates to be in Romanian timezone and will return dates
and times in Romanian timezone. This library tries to load the
Europe/Bucharest timezone location on init so that creating and parsing dates
will work as expected. The user of this library is responsible to ensure the
Europe/Bucharest location is available. If you are not sure that the target
system will have system timezone database, you can use in you main package:
import _ "time/tzdata"to load the Go embedded copy of the timezone database.
var invoice efactura.Invoice
// Build invoice (manually, or with the InvoiceBuilder)
uploadRes, err := client.UploadInvoice(ctx, invoice, "123456789")
if err != nil {
// Handle error
}
if uploadRes.IsOk() {
fmt.Printf("Upload index: %d\n", uploadRes.GetUploadIndex())
} else {
// The upload was not successful, check uploadRes.Errors
}For self-billed invoices, and/or if the buyer in not a Romanian entity, you can use
the UploadOptionSelfBilled(), UploadOptionForeign() upload options:
uploadRes, err := client.UploadInvoice(ctx, invoice, "123456789",
efactura.UploadOptionSelfBilled(), efactura.UploadOptionForeign())If you have already the raw XML to upload (maybe you generated it by other means), you can use the UploadXML method.
To upload an Invoice XML:
uploadRes, err := client.UploadXML(ctx, xml, UploadStandardUBL, "123456789")msg := efactura.RaspMessage{
UploadIndex: 5008787839,
Message: "test",
}
uploadRes, err := client.UploadRaspMessage(ctx, msg, "123456789")
if err != nil {
// Handle error
}resp, err := client.GetMessageState(ctx, uploadIndex)
if err != nil {
// Handle error
}
switch {
case resp.IsOk():
// Uploaded invoice was processed
fmt.Printf("Download ID: %d\n", resp.GetDownloadID())
case resp.IsNok():
// Processing failed
case resp.IsProcessing():
// The message/invoice is still processing
case resp.IsInvalidXML():
// The uploaded XML is invalidnumDays := 7
resp, err := client.GetMessagesList(ctx, "123456789", numDays, MessageFilterAll)
if err != nil {
// Handle error
}
if resp.IsOk() {
for _, message := range resp.Messages {
switch {
case message.IsError():
// The message is an error for an upload
case message.IsSentInvoice():
// The message is a sent invoice
case message.IsReceivedInvoice():
// The message is a received invoice
case message.IsBuyerMessage():
// The message is a message from the buyer
}
}
}downloadID := 3013004158
resp, err := client.DownloadInvoice(ctx, downloadID)
if err != nil {
// Handle error
}
if resp.IsOk() {
// The contents of the ZIP file is found in the resp.Zip byte slice.
}var invoice Invoice
// Build invoice (manually, or with the InvoiceBuilder)
validateRes, err := client.ValidateInvoice(ctx, invoice)
if err != nil {
// Handle error
}
if validateRes.IsOk() {
// Validation successful
}This library tries its best to overcome the not so clever API implementation and to detect limits exceeded errors. To check if the error is cause by a limit:
import (
efactura_errors "github.com/printesoi/e-factura-go/pkg/errors"
)
resp, err := client.GetMessageState(ctx, uploadIndex)
if err != nil {
var limitsErr *efactura_errors.LimitExceededError
var responseErr *efactura_errors.ErrorResponse
if errors.As(err, &limitsErr) {
// The limits were exceeded. limitsErr.ErrorResponse contains more
// information about the HTTP response, and the limitsErr.Limit field
// contains the limit for the day.
} else if errors.As(err, &responseErr) {
// ErrorResponse means we got the HTTP response but we failed to parse
// it or some other error like invalid response content type.
}
}TODO: See TestInvoiceBuilder() from builders_test.go for an example of using InvoiceBuilder for creating an Invoice.
In case you need to get the XML encoding of the invoice (eg. you need to store it somewhere before the upload):
var invoice Invoice
// Build invoice (manually, or with the InvoiceBuilder)
xmlData, err := invoice.XML()
if err != nil {
// Handle error
}To get the XML with indentation:
xmlData, err := invoice.XMLIndent("", " ")
if err != nil {
// Handle error
}Caution
Don't use the standard encoding/xml package for generating the XML
encoding, since it does not produce Canonical XML [XML-C14N]!
var invoice efactura.Invoice
if err := efactura.UnmarshalInvoice(data, &invoice); err != nil {
// Handle error
}Caution
Only use efactura.UnmarshalInvoice, because encoding/xml package
cannot unmarshal a struct like efactura.Invoice due to namespace prefixes!
The etransport package can be used for interacting with (calling) the
RO e-Transport v2 API
via the Client object or to build a declaration v2 XML using the PostingDeclarationV2 objects.
Important
In order to use the etransport.Client, a valid OAuth2 token is needed.
Construct a new simple client for production environment:
import (
"github.com/printesoi/e-factura-go/pkg/etransport"
)
ctx := context.TODO()
client, err := etransport.NewProductionClient(ctx, efactura_oauth2.TokenSource(ctx, token))
if err != nil {
// Handle error
}Construct a new simple client for sandbox (test) environment:
ctx := context.TODO()
client, err := etransport.NewSandboxClient(ctx, efactura_oauth2.TokenSource(ctx, token))
if err != nil {
// Handle error
}var declaration etransport.PostingDeclarationV2
// Build posting declaration
uploadRes, err := client.UploadPostingDeclarationV2(ctx, declaration, "123456789")
if err != nil {
// Handle error
}
if uploadRes.IsOk() {
fmt.Printf("Upload index: %d\n", uploadRes.GetUploadIndex())
} else {
// The upload was not successful, check uploadRes.Errors
fmt.Printf("Upload failed: %s\n", uploadRes.GetFirstErrorMessage())
}Check the message state for an upload index resulted from an upload:
resp, err := client.GetMessageState(ctx, uploadIndex)
if err != nil {
// Handle error
}
switch {
case resp.IsOk():
// Uploaded declaration was processed, we can now use the UIT.
case resp.IsNok():
// Processing failed
case resp.IsProcessing():
// The message/declaration is still processing
case resp.IsInvalidXML():
// The uploaded XML is invalidnumDays := 7 // Between 1 and 60
resp, err := client.GetMessagesList(ctx, "123456789", numDays)
if err != nil {
// Handle error
}
if resp.IsOk() {
for _, message := range resp.Messages {
// Process message
}
} else {
// Handle error
fmt.Printf("GetMessagesList failed: %s\n", resp.GetFirstErrorMessage())
}The vat (github.com/printesoi/e-factura-go/pkg/vat) package can be used for interacting with (calling) the
ANAF TVA v9 API
via the Client object.
import (
"log"
"github.com/printesoi/e-factura-go/pkg/vat"
"github.com/printesoi/e-factura-go/pkg/types"
)
func main() {
client, err := vat.NewClient()
// Query current status (current date in Romania)
res1, err := client.CheckVatV9(context.TODO(), MakeCheckVatRequest(vat.CIF(12345678)))
if err != nil {
// Handle error
}
// Query status for multiple companies for specific dates
res2, err := client.CheckVatV9(context.TODO(), MakeCheckVatRequestFromItems(
vat.MakeCheckVatRequestItem(vat.CIF(12345678), types.MakeDate(2025, 12, 31)),
vat.MakeCheckVatRequestItem(vat.CIF(98765432), types.MakeDate(2025, 1, 1)),
))
if err != nil {
// Handle error
}
if len(res2.Found) == 0 {
// None of the provides CIFs are valid.
} else {
for _, data := range res2.Found {
log.Printf("CIF: %s, Name: %s, VAT enabled: %v\n",
data.GeneralData.CIF, data.GeneralData.Name, data.HasVat())
}
}
}Pull requests are more than welcome :)
This library is distributed under the Apache License version 2.0 found in the LICENSE file.
If you need help integrating this library in your software or you need consulting services regarding e-factura APIs contact me (contact email in my Github profile).