Skip to Content
v1.0Documentación oficial de la API LX CloudPos · estable en producción

Node.js TypeScript

Integración tipada para Node.js sobre axios. Incluye el cliente HTTP, los tipos de request/response y un receptor de webhook con verificación HMAC sobre el body crudo.

Setup

npm install axios uuid npm install -D @types/uuid typescript ts-node

Cliente completo

La clase LxsClient expone los métodos de emisión, consulta, descarga de PDF/XML, reenvío de correo, anulación y siguiente consecutivo. Cada emisión recibe un Idempotency-Key único (o el que vos le pases para reintentar).

// src/lxs-client.ts import axios, { AxiosInstance } from 'axios'; import { randomUUID } from 'crypto'; export type TipoComprobante = 'FacturaElectronica' | 'TiqueteElectronico' | 'NotaCredito' | 'NotaDebito'; export interface Linea { idProducto: number; cantidad: number; precioUnitario?: number; montoDescuento?: number; naturalezaDescuento?: string; montoExoneracion?: number; } export interface FacturaRequest { tipo: TipoComprobante; cliente?: { idCliente: number } | null; condicionVenta: string; plazoCredito?: number; medioPago: string; moneda: string; tipoCambio?: number; codigoActividad?: string; comentario?: string; lineas: Linea[]; referencias?: any[]; } export interface FacturaResponse { clave: string; numeroConsecutivo: string; tipoDocumento: '01' | '02' | '03' | '04'; estadoHacienda: string; totalComprobante: number; moneda: string; fechaEmision: string; } export class LxsClient { private http: AxiosInstance; constructor(apiKey: string, baseURL = 'https://api.lxsyscr.com') { this.http = axios.create({ baseURL, headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' }, timeout: 30000 }); } async emitir(data: FacturaRequest, idempotencyKey?: string): Promise<FacturaResponse> { const key = idempotencyKey || randomUUID(); const { data: response } = await this.http.post<FacturaResponse>( '/api/v1/facturas', data, { headers: { 'Idempotency-Key': key } } ); return response; } async consultar(clave: string): Promise<FacturaResponse> { const { data } = await this.http.get<FacturaResponse>(`/api/v1/facturas/${clave}`); return data; } async descargarPdf(clave: string): Promise<Buffer> { const { data } = await this.http.get(`/api/v1/facturas/${clave}/pdf`, { responseType: 'arraybuffer' }); return Buffer.from(data); } async descargarXml(clave: string): Promise<string> { const { data } = await this.http.get(`/api/v1/facturas/${clave}/xml`); return data; } async reenviarEmail(clave: string): Promise<{ mensaje: string }> { const { data } = await this.http.post(`/api/v1/facturas/${clave}/email`); return data; } async anular(clave: string): Promise<void> { await this.http.delete(`/api/v1/facturas/${clave}`); } async proximoConsecutivo(tipo: '01' | '02' | '03' | '04'): Promise<number> { const { data } = await this.http.get(`/api/v1/consecutivos/proximo`, { params: { tipo } }); return data.proximo; } } // Uso: const lxs = new LxsClient(process.env.LXS_API_KEY!); const factura = await lxs.emitir({ tipo: 'FacturaElectronica', cliente: { idCliente: 42 }, condicionVenta: '01', medioPago: '01', moneda: 'CRC', lineas: [{ idProducto: 100, cantidad: 2 }] }); console.log('Clave:', factura.clave);

Receptor de webhook (Express)

Usá express.raw({ type: 'application/json' }) para conservar el body crudo: si dejás que Express lo parsee, perdés bytes (espacios, orden de claves) y la firma HMAC no va a coincidir. Después de validar, encolá el procesamiento con setImmediate o tu cola favorita para responder en menos de 10 s.

// src/webhook-server.ts import express from 'express'; import { createHmac, timingSafeEqual } from 'crypto'; const app = express(); const WEBHOOK_SECRET = process.env.LXS_WEBHOOK_SECRET!; app.post('/webhooks/lxs', express.raw({ type: 'application/json' }), async (req, res) => { const sig = (req.headers['x-lxs-signature'] || '').toString().replace(/^sha256=/, ''); const expected = createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'); if (sig.length !== expected.length || !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) { return res.status(401).end(); } const evento = JSON.parse(req.body.toString('utf8')); console.log('Evento recibido:', evento.evento, evento.clave, evento.ind_estado); // Procesa de forma asíncrona setImmediate(() => procesarEvento(evento)); res.status(200).end(); } ); async function procesarEvento(evento: any) { // Dedup por nonce // Actualizar BD local // Notificar a otros sistemas } app.listen(3000, () => console.log('Webhook server en :3000'));