Skip to content

printesoi/e-factura-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

156 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

e-factura-go Go Reference Tests Coverage Status Go Report Card

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)
  • 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})
  • 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 (CreditNote not yet supported).
  • e-Transport v2 declaration XML building via native Go structs.

Installation

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@latest

then 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@latest

then run etransport-cli help for options (this assumes $GOPATH/bin is in your $PATH).

oauth2

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 authorizeURL

Getting 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

RO e-Factura

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
}

Time and dates in Romanian time zone

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.

Upload invoice

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")

Upload message for invoice

msg := efactura.RaspMessage{
    UploadIndex: 5008787839,
    Message: "test",
}
uploadRes, err := client.UploadRaspMessage(ctx, msg, "123456789")
if err != nil {
    // Handle error
}

Get message state

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 invalid

Get messages list

numDays := 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
        }
    }
}

Download invoice

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.
}

Validate invoice

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
}

Errors

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.
    }
}

Generating an Invoice

TODO: See TestInvoiceBuilder() from builders_test.go for an example of using InvoiceBuilder for creating an Invoice.

Getting the raw XML of the 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]!

Unmarshal XML to invoice

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!

RO e-Transport

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
}

Upload declaration

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())
}

Get message state

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 invalid

Get messages list

numDays := 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())
}

ANAF VAT

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())
		}
	}
}

Contributing

Pull requests are more than welcome :)

License

This library is distributed under the Apache License version 2.0 found in the LICENSE file.

Commercial support

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).

About

Go library for accessing the RO e-Factura and RO e-Transport APIs.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages