Skip to content

About

SDK oficial de fiscalapi para consumo desde aplicaciones Java

Resources

Stars

63 stars

Watchers

0 watching

Forks

Repository files navigation

Fiscalapi SDK para Java

Maven Central License

SDK oficial de https://fiscalapi.com para Java, la API de facturación CFDI y otros servicios fiscales en México. Simplifica la integración con los servicios de facturación electrónica, eliminando las complejidades del SAT y facilitando la generación de facturas, notas de crédito, complementos de pago, nómina, carta porte, y más. ¡Factura sin dolor!

📋 Facturación CFDI 4.0

  • Soporte completo para CFDI 4.0 con todas las especificaciones oficiales
  • Timbrado de facturas de ingreso con validación automática
  • Timbrado de notas de crédito (facturas de egreso)
  • Timbrado de complementos de pago en MXN, USD y EUR
  • Timbrado de facturas de nómina
  • Timbrado de facturas de carta porte
  • Timbrado de facturas de comercio exterior
  • Consulta del estatus de facturas en el SAT en tiempo real
  • Cancelación de facturas
  • Generación de archivos PDF de las facturas con formato profesional
  • Personalización de logos y colores en los PDF generados
  • Envío de facturas por correo electrónico automatizado
  • Descarga de archivos XML con estructura completa
  • Almacenamiento y recuperación de facturas por 5 años.
  • Dos modos de operación: Por valores o Por referencias

📥 Descarga Masiva

  • Acceso a catálogos de descarga masiva del SAT
  • Descarga de CFDI y Metadatos en lotes grandes
  • Descarga masiva XML con filtros personalizados
  • Reglas de descarga automática por RFC
  • Solicitudes de descarga via API y Dashboard.
  • Automatización de solicitudes de descarga

👥 Gestión de Personas

  • Administración de personas (emisores, receptores, clientes, usuarios, etc.)
  • Gestión de certificados CSD y FIEL (subir archivos .cer y .key a FiscalAPI)
  • Configuración de datos fiscales (RFC, domicilio fiscal, régimen fiscal)
  • Datos de empleado (agrega/actualiza/elimina datos de empleado a una persona. CFDI Nómina)
  • Datos de empleador (agrega/actualiza/elimina datos de empleador a una persona. CFDI Nómina)
  • Firma de carta manifiesto (firma el manifiesto con la FIEL de la persona y obtén el PDF)

🎖️ Gestión de Timbres

  • Gestión de folios fiscales Compra timbres a fiscalapi y transfiere/retira a las personas de tu organizacion segun tus reglas de negocio.
  • Listar transacciones de timbres con paginación
  • Consultar transacciones por ID
  • Transferir timbres entre personas
  • Retirar timbres de una persona
  • Créditos de validación SAT transfiere y retira créditos de validación con el mismo servicio, eligiendo el saldo con creditType
  • Créditos de ticket transfiere y retira créditos de ticket con CreditType.TICKET; su saldo se lee en balances de la persona

✅ Validaciones SAT

  • Validación de CFDI timbrados estructura del Anexo 20, vigencia del certificado del emisor, sello del CFDI y sello del SAT en el TimbreFiscalDigital
  • Estatus del comprobante en el SAT vigente, cancelado o no encontrado
  • Listas negras del SAT artículos 69-B y 69-B Bis del CFF, por CFDI o por RFC
  • Catálogo de validaciones consulta los tipos disponibles y los estatus que cada uno puede tomar

🧾 Tickets (Auto CFDI)

  • Ingesta de ventas pagadas del punto de venta con Idempotency-Key opcional (getTicketService().create)
  • Consulta con filtros por estado, emisor, rango de fechas de pago y página; detalle con conceptos e historia
  • Código para reimprimir y cancelación de tickets pendientes (ejemplo en examples/EjemplosTickets.java)

🛍️ Gestión de Productos/Servicios

  • Gestión de productos y servicios con catálogo personalizable
  • Administración de impuestos aplicables (IVA, ISR, IEPS)

📚 Consulta de Catálogos SAT

  • Consulta en catálogos oficiales del SAT actualizados
  • Consulta en catálogos oficiales de Descarga masiva del SAT actualizados
  • Búsqueda de información en catálogos del SAT con filtros avanzados
  • Acceso y búsqueda en catálogos completos

📖 Recursos Adicionales

  • Cientos de ejemplos de código disponibles en múltiples lenguajes de programación
  • Documentación completa con guías paso a paso
  • Ejemplos prácticos para casos de uso comunes
  • Soporte técnico especializado
  • Actualizaciones regulares conforme a cambios del SAT

📦 Instalación

Compatible con múltiples versiones de Java (desde Java 8 en adelante)

Maven:

<dependency>
    <groupId>com.fiscalapi</groupId>
    <artifactId>fiscalapi</artifactId>
    <version>4.0.462</version>
</dependency>

Gradle (Groovy):

implementation 'com.fiscalapi:fiscalapi:4.0.462'

Gradle (Kotlin):

implementation("com.fiscalapi:fiscalapi:4.0.462")

Para más información, consulta Snippets en Maven Central.

⚙️ Configuración

Puedes usar el SDK tanto en aplicaciones sin inyección de dependencias como en proyectos que usan Spring Boot. A continuación se describen ambas formas:

A) Aplicaciones sin Inyección de Dependencias

  1. Crea tu objeto de configuración con tus credenciales:
FiscalApiSettings settings = new FiscalApiSettings();
settings.setDebugMode(true);
settings.setApiUrl("https://test.fiscalapi.com"); // https://live.fiscalapi.com (producción) 
settings.setApiKey("<api-key>");
settings.setTenant("<tenant-key>");
FiscalApiClient client = FiscalApiClient.create(settings);
  1. Utiliza los servicios de FiscalApiClient según tus necesidades (ver ejemplos más adelante).

B) Aplicaciones con Spring Boot

  1. Agrega la configuración en tu application.properties o application.yml:
fiscalapi:
  api-url: https://test.fiscalapi.com
  api-key: <YourApiKeyHere>
  tenant: <YourTenantHere>
  1. Registra el bean en tu configuración de Spring:
@Configuration
public class FiscalApiConfig {
    @Bean
    public FiscalApiClient fiscalApiClient(
        @Value("${fiscalapi.api-url}") String apiUrl,
        @Value("${fiscalapi.api-key}") String apiKey,
        @Value("${fiscalapi.tenant}") String tenant
    ) {
        FiscalApiSettings settings = new FiscalApiSettings();
        settings.setApiUrl(apiUrl);
        settings.setApiKey(apiKey);
        settings.setTenant(tenant);
        return FiscalApiClient.create(settings);
    }
}

Posteriormente, podrás inyectar FiscalApiClient donde lo requieras:

@RestController
public class InvoicesController {
    private final FiscalApiClient fiscalApi;

    public InvoicesController(FiscalApiClient fiscalApi) {
        this.fiscalApi = fiscalApi;
    }
    
    // Usa fiscalApi en tus métodos del controlador...
}

📝 Ejemplos de Uso

1. Crear una Persona

// ***Crear persona ***//
Person person = new Person();
person.setLegalName("MI EMPRESA"); // razón social sin régimen de capital
person.setEmail("john.doe@fiscalapi.com");
person.setPassword("Password123!");
ApiResponse<Person> apiResponse = client.getPersonService().create(person);
System.out.printf("apiResponse: %s\n", apiResponse);

2. Subir Certificados CSD

// ***Subir archivo .cer (certificado)***//
TaxFile archivoCer = new TaxFile();
archivoCer.setPersonId("3f3478b4-60fd-459e-8bfc-f8239fc96257");
archivoCer.setTin("FUNK671228PH6"); // RFC de la persona (opcional)
archivoCer.setBase64File("MIIFgDCCA2igAwIBAgIUMzAwMDEwMDAwMDA1MDAwMDM0NDYwDQYJKoZIhvcNAQELBQAwggEr...");
archivoCer.setFileType(0); // 0 para certificado
archivoCer.setPassword("12345678a");

ApiResponse<TaxFile> apiResponseCer = client.getTaxFileService().create(archivoCer);
System.out.printf("apiResponse: %s\n", apiResponseCer);


// ***Subir archivo .key (clave privada)***//
TaxFile archivoKey = new TaxFile();
archivoKey.setPersonId("3f3478b4-60fd-459e-8bfc-f8239fc96257");
archivoKey.setTin("FUNK671228PH6"); // RFC de la persona (opcional)
archivoKey.setBase64File("MIIFDjBABgkqhkiG9w0BBQ0...==");
archivoKey.setFileType(1); // 1 para llave privada
archivoKey.setPassword("12345678a");

ApiResponse<TaxFile> apiResponseKey = client.getTaxFileService().create(archivoKey);
System.out.printf("apiResponse: %s\n", apiResponseKey);

3. Crear un Producto o Servicio

// ***Crear producto***//
Product product = new Product();
product.setDescription("Libro de Java sin impuestos");
product.setUnitPrice(100.75986);
ApiResponse<Product> apiResponseProduct = client.getProductService().create(product);
System.out.printf("apiResponse: %s\n", apiResponseProduct);

4. Crear una Factura de Ingreso (Por Referencias)

// Generar factura por referencias
Invoice invoice = new Invoice();
invoice.setVersionCode("4.0");
invoice.setSeries("F");
invoice.setDate(LocalDateTime.now());
invoice.setPaymentFormCode("01");
invoice.setCurrencyCode("MXN");
invoice.setTypeCode("I");
invoice.setExpeditionZipCode("42501");
invoice.setPaymentMethodCode("PUE");

// Emisor por referencia
InvoiceIssuer issuer = new InvoiceIssuer();
issuer.setId("3f3478b4-60fd-459e-8bfc-f8239fc96257");
invoice.setIssuer(issuer);

// Receptor por referencia
InvoiceRecipient recipient = new InvoiceRecipient();
recipient.setId("96b46762-d246-4a67-a562-510a25dbafa9");
invoice.setRecipient(recipient);

// Producto / Servicio por referencia
List<InvoiceItem> items = new ArrayList<>();
InvoiceItem item = new InvoiceItem();
item.setId("114a4be5-fb65-40b2-a762-ff0c55c6ebfa");
item.setQuantity(2.0);
item.setDiscount(255.85);
items.add(item);
invoice.setItems(items);

ApiResponse<Invoice> apiResponseInvoice = client.getInvoiceService().create(invoice);
System.out.println(apiResponseInvoice);

Para reintentos seguros, pasa tu propia Idempotency-Key (hasta 64 caracteres ASCII visibles, sin espacios): repetir la misma petición con la misma llave de idempotencia dentro de 24 horas devuelve la respuesta original en lugar de crear otra factura. El SDK no genera la llave de idempotencia ni reintenta por su cuenta, pero OkHttp reintenta por fallas de conexión (retryOnConnectionFailure) y la llave de idempotencia hace seguros esos reintentos.

// Con la misma llave de idempotencia, repetir esta llamada devuelve la respuesta original
ApiResponse<Invoice> apiResponseInvoice = client.getInvoiceService().create(invoice, "550e8400-e29b-41d4-a716-446655440000");
System.out.println(apiResponseInvoice);

5. Validar un CFDI ante el SAT

Cada tipo de validación solicitado consume un crédito de validación. El cobro es todo o nada: si el saldo no alcanza para todos, no se ejecuta ninguno. Se envía xml (CFDI timbrado en base64) o tin (RFC), nunca ambos; con tin solo se pueden solicitar listas negras.

// *** Validar un CFDI timbrado ***//
SatValidationRequest request = new SatValidationRequest();
request.setXml(Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get("C:\\facturas\\FacturaXml.xml"))));
request.setValidationTypes(new ArrayList<>(Arrays.asList(
        SatValidationTypeIds.XML_STRUCTURE,
        SatValidationTypeIds.CFDI_SELLO,
        SatValidationTypeIds.CFDI_STATUS,
        SatValidationTypeIds.BLACKLIST_69B)));

ApiResponse<List<SatValidationResult>> apiResponse = client.getSatValidationService().validate(request);

for (SatValidationResult result : apiResponse.getData()) {
    System.out.printf("%s -> %s (passed: %s)%n",
            result.getType().getId(), result.getStatus().getId(), result.isPassed());
}

6. Transferir créditos de validación o de ticket

El mismo servicio de timbres mueve los saldos, que nunca se mezclan: CreditType.STAMP (por defecto) mueve timbres, CreditType.VALIDATION mueve créditos de validación SAT y CreditType.TICKET mueve créditos de ticket.

// *** Transferir créditos de validación ***//
StampTransactionParams transParams = new StampTransactionParams();
transParams.setFromPersonId("0e82a655-5f0c-4e07-abab-8f322e4123ef");
transParams.setToPersonId("da71df0c-f328-45ee-9bd9-3096ed02c164");
transParams.setAmount(10);
transParams.setComments("venta de creditos de validacion");
transParams.setCreditType(CreditType.VALIDATION);

ApiResponse<Boolean> apiResponse = client.getStampService().transferStamps(transParams);
System.out.printf("apiResponse: %s\n", apiResponse);

// *** Transferir créditos de ticket ***//
StampTransactionParams ticketParams = new StampTransactionParams();
ticketParams.setFromPersonId("0e82a655-5f0c-4e07-abab-8f322e4123ef");
ticketParams.setToPersonId("da71df0c-f328-45ee-9bd9-3096ed02c164");
ticketParams.setAmount(100);
ticketParams.setComments("venta de creditos de ticket");
ticketParams.setCreditType(CreditType.TICKET);

ApiResponse<Boolean> ticketResponse = client.getStampService().transferStamps(ticketParams);
System.out.printf("ticketResponse: %s\n", ticketResponse);

// El saldo de créditos de ticket se lee en balances de la persona (un tipo ausente tiene saldo 0)
Person person = client.getPersonService().getById("da71df0c-f328-45ee-9bd9-3096ed02c164", false).getData();
int creditosDeTicket = person.getBalances().stream()
        .filter(balance -> balance.getCreditType() == CreditType.TICKET.getValue())
        .mapToInt(CreditBalance::getAvailable)
        .sum();

7. Factura con Complemento de Comercio Exterior (Por Valores)

El complemento se cuelga de Complement.setComercioExterior(...). El TotalUSD no se envía: la API lo calcula sumando el valorDolares de las mercancías.

⚠️ La escala decimal importa. Construye los importes con new BigDecimal("...") a partir de una cadena, nunca desde un double. El SAT valida el número de decimales: 120 en lugar de 120.00 se rechaza con CCE122, y 0.16 en lugar de 0.160000 con CFDI40179.

InvoiceRecipient recipient = new InvoiceRecipient();
recipient.setTin("XEXX010101000");
recipient.setLegalName("Persona Fisica Extranjera");
recipient.setZipCode("42501");
recipient.setTaxRegimeCode("616");
recipient.setCfdiUseCode("S01");
recipient.setCountryId("USA");        // Residencia fiscal (c_Pais)
recipient.setForeignTin("123456789"); // NumRegIdTrib

ComercioExteriorEmisorDomicilio domicilioEmisor = new ComercioExteriorEmisorDomicilio();
domicilioEmisor.setCalle("CALLE DEL PAPEL");
domicilioEmisor.setColoniaId("0214");
domicilioEmisor.setLocalidadId("01");
domicilioEmisor.setMunicipioId("014");
domicilioEmisor.setEstadoId("QUE");
domicilioEmisor.setPaisId("MEX");
domicilioEmisor.setCodigoPostalId("76199");

ComercioExteriorEmisor emisor = new ComercioExteriorEmisor();
emisor.setDomicilio(domicilioEmisor);

// El domicilio del receptor es extranjero: va en texto libre, sin sufijo Id
ComercioExteriorReceptorDomicilio domicilioReceptor = new ComercioExteriorReceptorDomicilio();
domicilioReceptor.setCalle("ST. A");
domicilioReceptor.setEstado("TX");
domicilioReceptor.setPaisId("USA");
domicilioReceptor.setCodigoPostal("00000");

ComercioExteriorReceptor receptor = new ComercioExteriorReceptor();
receptor.setNumRegIdTrib("123456789");
receptor.setDomicilio(domicilioReceptor);

ComercioExteriorMercancia mercancia = new ComercioExteriorMercancia();
mercancia.setNoIdentificacion("131494-1055");
mercancia.setFraccionArancelariaId("2402200100");
mercancia.setCantidadAduana(new BigDecimal("2.00"));
mercancia.setUnidadAduanaId("01");
mercancia.setValorUnitarioAduana(new BigDecimal("11.74"));
mercancia.setValorDolares(new BigDecimal("23.47"));

List<ComercioExteriorMercancia> mercancias = new ArrayList<ComercioExteriorMercancia>();
mercancias.add(mercancia);

ComercioExterior comercioExterior = new ComercioExterior();
comercioExterior.setClaveDePedimentoId("A1");
comercioExterior.setCertificadoOrigen(0);
comercioExterior.setIncotermId("FOB");
comercioExterior.setTipoCambioUSD(new BigDecimal("17.4948")); // tipo de cambio DOF de la fecha
comercioExterior.setEmisor(emisor);
comercioExterior.setReceptor(receptor);
comercioExterior.setMercancias(mercancias);

Complement complement = new Complement();
complement.setComercioExterior(comercioExterior);

invoice.setRecipient(recipient);
invoice.setComplement(complement);

ApiResponse<Invoice> apiResponse = client.getInvoiceService().create(invoice);

En modo por referencias basta con recipient.setId(...), siempre que la persona tenga capturados countryId y foreignTin.

Los conceptos también pueden ir por referencia: un InvoiceItem con sólo id y quantity toma del producto la clave del SAT, la unidad, la descripción, el precio y los impuestos. En ese caso el NoIdentificacion del CFDI queda con el id del producto, así que mercancias[].noIdentificacion debe llevar ese mismo id:

InvoiceItem item = new InvoiceItem();
item.setId(PRODUCTO_CIGARROS_ID);
item.setQuantity(new BigDecimal("2"));

mercancia.setNoIdentificacion(PRODUCTO_CIGARROS_ID);

Un producto siempre tiene precio mayor que cero, así que los traslados con valor unitario 0 envían sus conceptos en línea.


8. Firmar Carta Manifiesto

Requiere la FIEL (e.firma), no el CSD de timbrado, y que el RFC del certificado corresponda a una persona del tenant.

SignManifestRequest request = new SignManifestRequest();
request.setBase64Cer("MIIGBDCCA+ygAwIBAgIUMzAwMDEwMDAwMDA1MDAwMDM0MTU..."); // .cer de la FIEL
request.setBase64Key("MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI..."); // .key de la FIEL
request.setPassword("12345678a");

ApiResponse<FileResponse> apiResponse = client.getManifestService().sign(request);

if (apiResponse.isSucceeded()) {
    FileResponse manifiesto = apiResponse.getData();
    byte[] pdf = Base64.getDecoder().decode(manifiesto.getBase64File());
    Files.write(Paths.get("C:\\facturas", manifiesto.getFileName()), pdf);
}

Al firmar, todas las personas del tenant con el RFC del certificado quedan con manifestStatusId en Signed.


9. Recibir webhooks

FiscalAPI firma cada entrega con Standard Webhooks y el secreto whsec_… de su webhook. Webhook.verify (paquete com.fiscalapi.webhooks) comprueba la firma (en tiempo constante) y que webhook-timestamp no difiera más de 5 minutos del reloj del receptor, y devuelve el WebhookEvent. La firma es la única prueba de que la entrega viene de FiscalAPI.

Verifique el cuerpo crudo: páselo tal como llegó, como byte[] (@RequestBody byte[] en Spring, que no lo reserializa) o como el texto UTF-8 de esos bytes. Con byte[], Webhook.verify lo lee como UTF-8 estricto y rechaza bytes inválidos. Un cuerpo deserializado y vuelto a serializar (por ejemplo, con @RequestBody WebhookEvent o @RequestBody Map) cambia sus bytes y la firma deja de coincidir.

import com.fiscalapi.models.webhooks.WebhookEvent;
import com.fiscalapi.models.webhooks.WebhookEventTypes;
import com.fiscalapi.services.FiscalApiClient;
import com.fiscalapi.webhooks.Webhook;
import com.fiscalapi.webhooks.WebhookVerificationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.TaskExecutor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class FiscalApiWebhooksController {
    private final TaskExecutor taskExecutor;
    private final FiscalApiClient fiscalApi;
    private final String webhookSecret; // whsec_… del webhook, desde su configuración segura

    public FiscalApiWebhooksController(TaskExecutor taskExecutor, FiscalApiClient fiscalApi,
                                       @Value("${fiscalapi.webhook-secret}") String webhookSecret) {
        this.taskExecutor = taskExecutor;
        this.fiscalApi = fiscalApi;
        this.webhookSecret = webhookSecret;
    }

    @PostMapping("/webhooks/fiscalapi")
    public ResponseEntity<Void> receive(@RequestHeader Map<String, String> headers, @RequestBody byte[] rawBody) {
        WebhookEvent event;
        try {
            event = Webhook.verify(headers, rawBody, webhookSecret); // los bytes tal como llegaron
        } catch (WebhookVerificationException e) {
            return ResponseEntity.badRequest().build(); // firma, header, timestamp o cuerpo UTF-8 inválido
        }

        // Responda 2xx rápido y procese después: FiscalAPI corta cada intento a los 15 s y lo reintenta.
        // El executor en memoria es ilustrativo: en producción use una cola durable.
        taskExecutor.execute(() -> process(event));
        return ResponseEntity.ok().build();
    }

    private void process(WebhookEvent event) {
        // La entrega es al menos una vez: event.getId() (el header webhook-id) se repite en cada reintento; descarte
        // los ya procesados. El orden de llegada no está garantizado: el estado autoritativo se lee del recurso.
        String resourceId = event.getData().getRelatedObject().getId();
        switch (event.getType()) {
            case WebhookEventTypes.INVOICE_CANCELED:
                fiscalApi.getInvoiceService().getById(resourceId, false);
                break;
            case WebhookEventTypes.DOWNLOAD_REQUEST_COMPLETED:
                fiscalApi.getDownloadRequestService().getById(resourceId, false);
                break;
            default:
                break; // un tipo que no conoce se ignora: el catálogo puede crecer
        }
    }
}
  • headers acepta Map<String, String> (como @RequestHeader Map<String, String> de Spring) o Map<String, List<String>> (un MultiValueMap de Spring o un mapa armado con HttpServletRequest#getHeaders); los nombres se comparan sin distinguir mayúsculas. Desde Spring 7, HttpHeaders ya no es un Map: pase headers.toSingleValueMap().
  • WebhookEvent es el mismo sobre para todo evento: getId(), getType() (texto; compárelo con las constantes de WebhookEventTypes), getCreatedAt() (OffsetDateTime en UTC), getPersonId() (null si el recurso no tiene persona) y getData() con getRelatedObject() (getId(), getType(), getUrl()), getStatus() y getPreviousStatus().
  • getRelatedObject().getUrl() es la ruta relativa del GET v4 del recurso (/api/v4/invoices/{id}); para leerlo, use el servicio de su tipo con getRelatedObject().getId().
  • El botón "Enviar prueba" del webhook (POST /api/v4/webhooks/{id}/test) envía WebhookEventTypes.WEBHOOK_TEST (webhook.test), cuyo relatedObject es el webhook mismo (type webhook, url /api/v4/webhooks/{id}), sin persona ni estados; no se suscribe.
  • Durante una rotación del secreto, FiscalAPI firma con el nuevo y con el anterior por 24 h: pase ambos, Webhook.verify(headers, body, Arrays.asList(secretoNuevo, secretoAnterior)).
  • La tolerancia es configurable (Webhook.verify(headers, body, secreto, Duration.ofMinutes(2))); el valor por omisión es Webhook.DEFAULT_TOLERANCE.
  • WebhookVerificationException (no verificada: extiende RuntimeException) indica una entrega que no es auténtica. Un secreto mal copiado (no es base64 canónico, por ejemplo truncado o con espacios), una lista de secretos vacía, una tolerancia negativa o un argumento null lanzan IllegalArgumentException, con la posición del secreto en la lista (nunca su valor): es un error de configuración, no de la entrega.

💡 Tip: Para más ejemplos (facturas por valores, notas de crédito, complementos de pago, catálogos SAT, cancelaciones, etc.), revisa la documentación oficial en docs.fiscalapi.com.

📄 Licencia

Este proyecto está licenciado bajo la Licencia Apache 2.0. Consulta el archivo LICENSE para más detalles.

🔗 Enlaces Útiles


Desarrollado con ❤️ por Fiscalapi

About

SDK oficial de fiscalapi para consumo desde aplicaciones Java

Resources

Stars

63 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages