SDK de Node.js
@n1co/epay llama al API de ePay desde tu servidor Node.js, con tipos de TypeScript, errores con código y verificación de webhooks.
- Instala
@n1co/epayen tu servidor (Node.js 22.12+, 24 o 26+). - Crea el cliente con tu llave secreta y el host de desarrollo.
- Crea intents con
paymentIntents.create()y verifica webhooks conconstructEvent().
npm install @n1co/epayUn servidor Express completo: crea el intent y recibe el webhook.
server.mjs
import express from 'express';
import { N1co, WebhookSignatureError } from '@n1co/epay';
const n1co = new N1co(process.env.N1CO_SECRET_KEY, {
baseUrl: 'https://api-epay.dev.n1co.dev', // desarrollo
});
const app = express();
// El webhook va antes de express.json(): necesita el cuerpo crudo.
app.post('/webhooks/n1co', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = N1co.webhooks.constructEvent(req.body, req.headers, process.env.N1CO_WEBHOOK_SECRET);
} catch (err) {
if (err instanceof WebhookSignatureError) return res.status(400).send('invalid signature');
throw err;
}
if (event.type === 'payment_intent.succeeded') {
// Entrega el pedido de event.data.object.reference; descarta los event.id repetidos.
}
res.sendStatus(204);
});
app.use(express.json());
app.post('/checkout/payment-intent', async (req, res) => {
// El monto sale de tu pedido, nunca del navegador.
const intent = await n1co.paymentIntents.create({
amount: 1250,
currency: 'USD',
customer: { id: 'cust-1001', name: 'Ana López', email: 'ana.lopez@example.com', phone: '+503 7000-1234' },
reference: 'order-8812',
});
res.json({ client_secret: intent.client_secret });
});
app.listen(3000);El paquete trae ES modules y CommonJS, sin dependencias: const { N1co } = require('@n1co/epay');. La página confirma el pago como en Cobrar.
Webhooks con NestJS#
Activa rawBody al crear la app y verifica req.rawBody:
webhooks.controller.ts
// main.ts: const app = await NestFactory.create(AppModule, { rawBody: true });
import { BadRequestException, Controller, Headers, HttpCode, Post, Req } from '@nestjs/common';
import type { RawBodyRequest } from '@nestjs/common';
import type { Request } from 'express';
import { N1co, WebhookSignatureError } from '@n1co/epay';
@Controller('webhooks')
export class WebhooksController {
@Post('n1co')
@HttpCode(204)
handle(@Req() req: RawBodyRequest<Request>, @Headers() headers: Record<string, string>) {
try {
const event = N1co.webhooks.constructEvent(req.rawBody!, headers, process.env.N1CO_WEBHOOK_SECRET!);
// Procesa event.type…
} catch (err) {
if (err instanceof WebhookSignatureError) throw new BadRequestException();
throw err;
}
}
}Errores#
Cada error del SDK tiene code (el código estable del API), status, details y requestId. Decide con code, nunca con el mensaje.
import { isN1coError } from '@n1co/epay';
try {
await n1co.paymentIntents.create(params);
} catch (err) {
if (!isN1coError(err)) throw err;
if (err.status === 400) console.log(err.details?.field, err.details?.code);
else console.log(err.code, err.status, err.requestId);
}Los códigos están en Errores. Incluye el requestId al contactar a n1co.
Lo que suele fallar#
- Parsear el cuerpo antes de verificar. Con
express.json()en la ruta del webhook, la firma no coincide. Usaexpress.raw()orawBody: true. - Reintentar un POST a ciegas. El SDK nunca reintenta un POST: el API no tiene claves de idempotencia. Tras un
ConnectionError, lee el intent o espera su webhook antes de crear otro. instanceofcon ESM y CommonJS a la vez. Si tu app carga las dos copias, usaisN1coError(err)concodeystatus.- Omitir
baseUrl. Sin él, el cliente usa el host de producción, que llega con el lanzamiento a producción.
Las opciones del cliente, los métodos, los tipos y los reintentos de GET están en el README del paquete.