e-factura-go

module
v0.0.0-...-ad16ad4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 4, 2024 License: Apache-2.0

README

e-factura-go Tests Coverage Status Go Report Card

Package efactura provides a client for using the ANAF e-factura API.

NOTICE

!!! This project is still in alpha stage, use it at you own risk !!!

Installation

e-factura-go requires Go version >= 1.21. With Go installed:

go get github.com/printesoi/e-factura-go

will resolve and add the package to the current development module, along with its dependencies.

Alternatively the same can be achieved if you use import in a package:

import "github.com/printesoi/e-factura-go"

and run go get without parameters.

Finally, to use the top-of-trunk version of this repo, use the following command:

go get github.com/printesoi/e-factura-go@main

Usage

This package can be use both for interacting with (calling) the ANAF e-factura API via the Client object and for generating an Invoice UBL XML.

import (
    "github.com/printesoi/e-factura-go/efactura"
    efactura_oauth2 "github.com/printesoi/e-factura-go/oauth2"
)

Construct the required OAuth2 config needed for the Client:

oauth2Cfg, err := efactura_oauth2.MakeConfig(
    efactura_oauth2.ConfigCredentials(anafAppClientID, anafApplientSecret),
    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
}

Construct a new simple client for production environment:

client, err := efactura.NewProductionClient(
	context.Background(),
    efactura_oauth2.TokenSource(token),
)
if err != nil {
    // Handle error
}

Construct a new simple client for sandbox (test) environment:

client, err := efactura.NewSandboxClient(
	context.Background(),
    efactura_oauth2.TokenSource(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:

onTokenChanged := func(ctx context.Context, token *xoauth.Token) error {
    fmt.Printf("Token changed...")
    return nil
}
client, err := efactura.NewSandboxClient(
    context.Background(),
    efactura_oauth2.TokenSourceWithChangedHandler(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 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
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/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
}

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

NOTE Only use efactura.UnmarshalInvoice, because encoding/xml package cannot unmarshal a struct like efactura.Invoice due to namespace prefixes!

Tasks

  • e-factura CLI tool
  • One-stop shop for ANAF APIs: VAT v8 API, E-Transport
  • Implement all business terms.
  • Extend the InvoiceBuilder to add all Invoice fields
  • Implement CreditNote.
  • Add tests for all REST API calls and more tests for validating generated XML (maybe checking with the tools provided by mfinante).
  • Validate signature from Invoice zip.
  • Godoc and more code examples.
  • Test coverage

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

Directories

Path Synopsis
cmd
Package efactura provides a client for using the ANAF e-factura API.
Package efactura provides a client for using the ANAF e-factura API.
internal
oauth2
Package oauth2 contains support packages for oauth2 package.
Package oauth2 contains support packages for oauth2 package.
ptr
Package oauth2 contains utilities for ANAF OAuth2 protocol.
Package oauth2 contains utilities for ANAF OAuth2 protocol.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL