Skip to main content

Overview

The CompraVenta service handles electronic invoice operations:
  • Build invoice documents with header and line items
  • Generate CUF (Código Único de Factura)
  • Sign XML documents
  • Compress and hash invoice data
  • Submit invoices to SIAT
  • Cancel invoices
Invoice submission requires valid CUIS, CUFD, and a registered point of sale. Complete the setup guides first.

Initialize the Service

import (
    "bytes"
    "compress/gzip"
    "crypto/sha256"
    "encoding/base64"
    "encoding/hex"
    "encoding/xml"
    "time"

    "github.com/ron86i/go-siat"
    "github.com/ron86i/go-siat/internal/core/domain/datatype"
    "github.com/ron86i/go-siat/pkg/config"
    "github.com/ron86i/go-siat/pkg/models"
)

s, err := siat.New("https://pilotosiatservicios.impuestos.gob.bo/v2", nil)
if err != nil {
    log.Fatalf("Error: %v", err)
}

cvService := s.CompraVenta

cfg := config.Config{Token: "YOUR_API_TOKEN"}
ctx := context.Background()

Generate CUF

The CUF is a unique invoice identifier. Generate it before building the invoice:
nit := int64(123456789)
fechaEmision := time.Now()
codigoControl := "XYZ789"  // From CUFD response

cuf, err := models.CompraVenta.GenerarCUF(
    nit,              // Taxpayer NIT
    fechaEmision,     // Invoice date/time
    0,                // Branch code (sucursal)
    1,                // Modality (1=Electronic)
    1,                // Emission type (1=Online)
    1,                // Invoice type (1=With VAT)
    1,                // Document sector (1=Sale/Purchase)
    1,                // Invoice number
    0,                // Point of sale code
    codigoControl,    // CUFD control code
)
if err != nil {
    log.Fatalf("Error generating CUF: %v", err)
}

fmt.Printf("CUF: %s\n", cuf)
The CUF helper uses the Mod 11 algorithm and Base16 encoding as specified by SIAT regulations.

Build Invoice Structure

1

Build Invoice Header

The header contains seller and buyer information plus totals:
cabeceraReq := models.CompraVenta.NewCabecera().
    // Seller information
    WithNitEmisor(nit).
    WithRazonSocialEmisor("Mi Empresa S.A.").
    WithMunicipio("La Paz").
    WithDireccion("Av. Principal 123").
    
    // Invoice identifiers
    WithNumeroFactura(1).
    WithCuf(cuf).
    WithCufd("CODIGO_CUFD_EJEMPLO").
    WithCodigoSucursal(0).
    WithCodigoPuntoVenta(0).
    WithFechaEmision(fechaEmision.Format("2006-01-02T15:04:05.000")).
    
    // Buyer information
    WithNombreRazonSocial("JUAN PEREZ").
    WithCodigoTipoDocumentoIdentidad(1).  // 1=CI
    WithNumeroDocumento("5544332").
    WithCodigoCliente("CLI-001").
    
    // Payment and amounts
    WithCodigoMetodoPago(1).         // 1=Cash
    WithMontoTotal(100.0).
    WithMontoTotalSujetoIva(100.0).
    WithCodigoMoneda(1).             // 1=BOB
    WithTipoCambio(1.0).
    WithMontoTotalMoneda(100.0).
    
    // Legal requirements
    WithLeyenda("Ley N° 453: El proveedor deberá suministrar el servicio...").
    WithUsuario("admin").
    WithCodigoDocumentoSector(1).    // 1=Sale/Purchase
    Build()
All monetary amounts should be calculated accurately. SIAT validates totals against line items.
2

Build Line Items (Detalle)

Add one or more line items to the invoice:
detalleReq := models.CompraVenta.NewDetalle().
    WithActividadEconomica("461000").
    WithCodigoProductoSin("12345").      // SIN product code
    WithCodigoProducto("PROD-001").      // Your internal code
    WithDescripcion("Producto de prueba").
    WithCantidad(1.0).
    WithUnidadMedida(57).                // Unit code from catalog
    WithPrecioUnitario(100.0).
    WithSubTotal(100.0).
    Build()

// For multiple items, create additional detalle objects
detalleReq2 := models.CompraVenta.NewDetalle().
    WithActividadEconomica("461000").
    WithCodigoProductoSin("67890").
    WithCodigoProducto("PROD-002").
    WithDescripcion("Servicio de consultoría").
    WithCantidad(2.0).
    WithUnidadMedida(58).
    WithPrecioUnitario(50.0).
    WithSubTotal(100.0).
    Build()
3

Assemble Complete Invoice

Combine header and line items:
facturaReq := models.CompraVenta.NewFactura().
    WithCabecera(cabeceraReq).
    AddDetalle(detalleReq).
    AddDetalle(detalleReq2).  // Add multiple items
    Build()

Sign and Compress Invoice

1

Serialize to XML

xmlData, err := xml.Marshal(facturaReq)
if err != nil {
    log.Fatalf("Error serializing XML: %v", err)
}
2

Sign the XML

Use your digital certificate to sign the invoice:
signedXML, err := models.CompraVenta.SignXML(
    xmlData, 
    "key.pem",   // Path to private key
    "cert.crt",  // Path to certificate
)
if err != nil {
    // In development, you might skip signing
    fmt.Println("Warning: Skipping signature (no certificates)")
    signedXML = xmlData
}
In production, digital signatures are mandatory. Obtain valid certificates from Agencia de Gobierno Electrónico (AGETIC).
3

Compress with Gzip

var buf bytes.Buffer
zw := gzip.NewWriter(&buf)
if _, err := zw.Write(signedXML); err != nil {
    log.Fatalf("Error compressing XML: %v", err)
}
zw.Close()
compressedBytes := buf.Bytes()
4

Calculate Hash and Encode

// SHA256 hash of compressed data
hash := sha256.Sum256(compressedBytes)
hashString := hex.EncodeToString(hash[:])

// Base64 encode for transmission
encodedArchivo := base64.StdEncoding.EncodeToString(compressedBytes)

Submit Invoice to SIAT

1

Build Submission Request

recepcionReq := models.CompraVenta.NewRecepcionFacturaRequest().
    WithCodigoAmbiente(2).               // 2=Pilot
    WithCodigoDocumentoSector(1).
    WithCodigoEmision(1).
    WithCodigoModalidad(1).
    WithCodigoPuntoVenta(0).
    WithCodigoSistema("ABC123DEF").
    WithCodigoSucursal(0).
    WithCufd("CODIGO_CUFD_EJEMPLO").
    WithCuis("C2FC682B").
    WithNit(nit).
    WithTipoFacturaDocumento(1).
    WithArchivo([]byte(encodedArchivo)).    // Base64 encoded
    WithFechaEnvio(datatype.TimeSiat(fechaEmision)).
    WithHashArchivo(hashString).            // SHA256 hash
    Build()
2

Submit and Validate Response

resp, err := cvService.RecepcionFactura(ctx, cfg, recepcionReq)
if err != nil {
    log.Fatalf("Error submitting invoice: %v", err)
}

if resp != nil && resp.Body.Content.RespuestaServicioFacturacion.Transaccion {
    codigoRecepcion := resp.Body.Content.RespuestaServicioFacturacion.CodigoRecepcion
    fmt.Printf("Success! Reception Code: %s\n", codigoRecepcion)
    
    // Store the reception code for future reference
} else {
    fmt.Printf("Submission failed: %v\n", 
        resp.Body.Content.RespuestaServicioFacturacion.Mensajes)
}

Cancel Invoice

Cancel a previously submitted invoice:
anulacionReq := models.CompraVenta.NewAnulacionFacturaRequest().
    WithCodigoAmbiente(2).
    WithCodigoDocumentoSector(1).
    WithCodigoEmision(1).
    WithCodigoModalidad(1).
    WithCodigoPuntoVenta(0).
    WithCodigoSistema("ABC123DEF").
    WithCodigoSucursal(0).
    WithCufd("CODIGO_CUFD_EJEMPLO").
    WithCuf(cuf).                  // CUF of invoice to cancel
    WithCuis("C2FC682B").
    WithNit(nit).
    WithTipoFacturaDocumento(1).
    WithCodigoMotivo(1).           // Cancellation reason code
    Build()

anulResp, err := cvService.AnulacionFactura(ctx, cfg, anulacionReq)
if err != nil {
    log.Fatalf("Error canceling invoice: %v", err)
}

if anulResp.Body.Content.RespuestaServicioFacturacion.Transaccion {
    fmt.Println("Invoice canceled successfully")
}
Consult the SincronizarParametricaMotivoAnulacion catalog for valid cancellation reason codes.

Complete Example

package main

import (
    "bytes"
    "compress/gzip"
    "context"
    "crypto/sha256"
    "encoding/base64"
    "encoding/hex"
    "encoding/xml"
    "fmt"
    "log"
    "time"

    "github.com/ron86i/go-siat"
    "github.com/ron86i/go-siat/internal/core/domain/datatype"
    "github.com/ron86i/go-siat/pkg/config"
    "github.com/ron86i/go-siat/pkg/models"
)

func main() {
    s, err := siat.New("https://pilotosiatservicios.impuestos.gob.bo/v2", nil)
    if err != nil {
        log.Fatalf("Error: %v", err)
    }

    cvService := s.CompraVenta
    ctx := context.Background()
    cfg := config.Config{Token: "YOUR_API_TOKEN"}

    nit := int64(123456789)
    fechaEmision := time.Now()
    codigoControl := "XYZ789"

    // Generate CUF
    cuf, err := models.CompraVenta.GenerarCUF(
        nit, fechaEmision, 0, 1, 1, 1, 1, 1, 0, codigoControl)
    if err != nil {
        log.Fatalf("Error generating CUF: %v", err)
    }

    // Build invoice
    cabeceraReq := models.CompraVenta.NewCabecera().
        WithNitEmisor(nit).
        WithRazonSocialEmisor("Mi Empresa S.A.").
        WithMunicipio("La Paz").
        WithNumeroFactura(1).
        WithCuf(cuf).
        WithCufd("CODIGO_CUFD_EJEMPLO").
        WithCodigoSucursal(0).
        WithDireccion("Av. Principal 123").
        WithCodigoPuntoVenta(0).
        WithFechaEmision(fechaEmision.Format("2006-01-02T15:04:05.000")).
        WithNombreRazonSocial("JUAN PEREZ").
        WithCodigoTipoDocumentoIdentidad(1).
        WithNumeroDocumento("5544332").
        WithCodigoCliente("CLI-001").
        WithCodigoMetodoPago(1).
        WithMontoTotal(100.0).
        WithMontoTotalSujetoIva(100.0).
        WithCodigoMoneda(1).
        WithTipoCambio(1.0).
        WithMontoTotalMoneda(100.0).
        WithLeyenda("Ley N° 453: El proveedor deberá suministrar el servicio...").
        WithUsuario("admin").
        WithCodigoDocumentoSector(1).
        Build()

    detalleReq := models.CompraVenta.NewDetalle().
        WithActividadEconomica("461000").
        WithCodigoProductoSin("12345").
        WithCodigoProducto("PROD-001").
        WithDescripcion("Producto de prueba").
        WithCantidad(1.0).
        WithUnidadMedida(57).
        WithPrecioUnitario(100.0).
        WithSubTotal(100.0).
        Build()

    facturaReq := models.CompraVenta.NewFactura().
        WithCabecera(cabeceraReq).
        AddDetalle(detalleReq).
        Build()

    // Serialize to XML
    xmlData, err := xml.Marshal(facturaReq)
    if err != nil {
        log.Fatalf("Error serializing XML: %v", err)
    }

    // Sign XML (skip in development if no certificates)
    signedXML, err := models.CompraVenta.SignXML(xmlData, "key.pem", "cert.crt")
    if err != nil {
        fmt.Println("Skipping signature (no certificates available)")
        signedXML = xmlData
    }

    // Compress
    var buf bytes.Buffer
    zw := gzip.NewWriter(&buf)
    if _, err := zw.Write(signedXML); err != nil {
        log.Fatalf("Error compressing: %v", err)
    }
    zw.Close()
    compressedBytes := buf.Bytes()

    // Hash and encode
    hash := sha256.Sum256(compressedBytes)
    hashString := hex.EncodeToString(hash[:])
    encodedArchivo := base64.StdEncoding.EncodeToString(compressedBytes)

    // Submit
    recepcionReq := models.CompraVenta.NewRecepcionFacturaRequest().
        WithCodigoAmbiente(2).
        WithCodigoDocumentoSector(1).
        WithCodigoEmision(1).
        WithCodigoModalidad(1).
        WithCodigoPuntoVenta(0).
        WithCodigoSistema("ABC123DEF").
        WithCodigoSucursal(0).
        WithCufd("CODIGO_CUFD_EJEMPLO").
        WithCuis("C2FC682B").
        WithNit(nit).
        WithTipoFacturaDocumento(1).
        WithArchivo([]byte(encodedArchivo)).
        WithFechaEnvio(datatype.TimeSiat(fechaEmision)).
        WithHashArchivo(hashString).
        Build()

    resp, err := cvService.RecepcionFactura(ctx, cfg, recepcionReq)
    if err != nil {
        log.Fatalf("Error submitting: %v", err)
    }

    if resp != nil && resp.Body.Content.RespuestaServicioFacturacion.Transaccion {
        fmt.Printf("✓ Success! Code: %s\n", 
            resp.Body.Content.RespuestaServicioFacturacion.CodigoRecepcion)
    } else {
        fmt.Printf("✗ Failed: %v\n", 
            resp.Body.Content.RespuestaServicioFacturacion)
    }
}

Builder Pattern Reference

Cabecera (Header) Builder

MethodDescriptionRequired
WithNitEmisor()Seller’s NITYes
WithRazonSocialEmisor()Seller’s business nameYes
WithNumeroFactura()Sequential invoice numberYes
WithCuf()Unique invoice codeYes
WithCufd()Daily billing codeYes
WithFechaEmision()Emission date (ISO format)Yes
WithMontoTotal()Total amountYes
WithCodigoMetodoPago()Payment method codeYes
WithNombreRazonSocial()Buyer’s nameYes
WithNumeroDocumento()Buyer’s document numberYes

Detalle (Line Item) Builder

MethodDescriptionRequired
WithActividadEconomica()Economic activity codeYes
WithCodigoProductoSin()SIN product codeYes
WithDescripcion()Item descriptionYes
WithCantidad()QuantityYes
WithUnidadMedida()Unit of measure codeYes
WithPrecioUnitario()Unit priceYes
WithSubTotal()Line totalYes

Best Practices

  • Use sequential numbering per point of sale
  • Never reuse invoice numbers
  • Store the last used number in your database
  • Validate uniqueness before submission
  • Validate NITs before creating invoices
  • Verify product codes exist in synced catalogs
  • Calculate totals accurately (sum of line items)
  • Format dates in ISO 8601 format
  • Store invoice data before submission
  • Implement retry logic for network failures
  • Log all submission attempts and responses
  • Handle duplicate submission errors gracefully
  • Store certificates securely
  • Monitor certificate expiration dates
  • Test signature validation regularly
  • Maintain backup certificates

Next Steps

XML Signing

Learn about digital signature requirements

Error Handling

Handle submission errors and validations

Build docs developers (and LLMs) love