@lacspace/connectips
v1.1.0
Published
Connect IPS (Nepal) merchant integration over Web Crypto — sign the redirect transaction token with your RSA private key and validate transactions server-to-server. Zero-dep, isomorphic (Node, edge, browser).
Maintainers
Readme
@lacspace/connectips
Connect IPS (Nepal) merchant integration over Web Crypto — RSA-signed redirect tokens + server-to-server transaction validation.
Connect IPS is Nepal's inter-bank payment gateway (operated by NCHL). Integrating it means building an exact
KEY=VALUE,…,TOKEN=TOKENmessage, signing it with your merchant RSA private key (RSA-SHA256), redirecting the payer, and later validating the transaction server-to-server. This package does all of that correctly.
- 🔐 RSA-SHA256 (RSASSA-PKCS1-v1_5) signing straight from a PKCS#8 PEM private key
- 🧾 Canonical token message built for you — no fragile string concatenation
- 🔁
buildForm()for the redirect andvalidateTxn()for the callback check - 🛡️ Built on Web Crypto (
globalThis.crypto.subtle) — never hand-rolled cryptography - ⚡ Isomorphic — Node 20+, edge runtimes & browsers · 📦 ESM + CJS · fully typed · zero dependencies
New in 1.1.0 — additive, fully backward compatible. Pure canonical message-string builders you can inspect (
paymentTokenMessage,validationTokenMessage), a no-networkbuildValidationRequest()that signs the validate-txn payload for you to POST yourself, offline field validation (validateRequest), aValidateTxnResponsetype, and anENDPOINTSpreset. The existingsignToken/buildForm/validateTxnsigning is unchanged.
Install
npm install @lacspace/connectips # or pnpm add / yarn add / bun addRedirect the payer
import { buildForm } from "@lacspace/connectips";
const form = await buildForm(
{
MERCHANTID: "123",
APPID: "APP123",
APPNAME: "lacspace-shop",
TXNID: "TXN001",
TXNDATE: "05-09-2026",
TXNCRNCY: "NPR",
TXNAMT: 100000, // paisa
REFERENCEID: "REF001",
REMARKS: "order-1",
PARTICULARS: "order-1",
},
{ privateKeyPem: process.env.CONNECTIPS_PRIVATE_KEY_PEM!, env: "prod" },
);
// Render an auto-submitting form:
// <form action={form.action} method="POST">
// {Object.entries(form.fields).map(([k, v]) => <input type="hidden" name={k} value={v} />)}
// </form>form.fields is your transaction params plus a base64 TOKEN (the RSA-SHA256 signature over the canonical message ending in the literal TOKEN=TOKEN).
Validate the transaction (server-to-server)
import { validateTxn } from "@lacspace/connectips";
const result = await validateTxn(
{ merchantId: "123", appId: "APP123", referenceId: "REF001", txnAmt: 100000 },
{
user: process.env.CONNECTIPS_API_USER!,
password: process.env.CONNECTIPS_API_PASSWORD!,
privateKeyPem: process.env.CONNECTIPS_PRIVATE_KEY_PEM!,
env: "prod",
},
);
// POSTs { merchantId, appId, referenceId, txnAmt, token } with a Basic-auth headerThe validation token signs MERCHANTID=…,APPID=…,REFERENCEID=…,TXNAMT=….
Just the token
import { signToken } from "@lacspace/connectips";
const token = await signToken(params, privateKeyPem); // base64 RSA-SHA256 signatureValidate without our fetch (build the request yourself)
import { buildValidationRequest } from "@lacspace/connectips";
const req = await buildValidationRequest(
{ merchantId: "123", appId: "APP123", referenceId: "REF001", txnAmt: 100000 },
{ privateKeyPem, env: "prod", user, password }, // user+password → Basic auth header
);
// { url, method: "POST", headers, body: { merchantId, appId, referenceId, txnAmt, token }, token }
await fetch(req.url, { method: req.method, headers: req.headers, body: JSON.stringify(req.body) });It signs the exact same canonical validation message as validateTxn(), but does no network — you own the request (retries, logging, custom runtimes).
Inspect the exact signed message + validate fields
import { paymentTokenMessage, validationTokenMessage, validateRequest } from "@lacspace/connectips";
paymentTokenMessage(params); // "MERCHANTID=…,…,PARTICULARS=…,TOKEN=TOKEN" (exactly what signToken signs)
validationTokenMessage(vparams); // "MERCHANTID=…,APPID=…,REFERENCEID=…,TXNAMT=…"
const { valid, errors } = validateRequest(params); // paisa integer TXNAMT, DD-MM-YYYY date, id charset, NPR…
if (!valid) throw new Error(errors.join("; "));API
| Export | Description |
| --- | --- |
| signToken(params, privateKeyPem) | Base64 RSA-SHA256 signature of the canonical redirect message. |
| buildForm(params, { privateKeyPem, env? }) | { action, method: "POST", fields } — params + signed TOKEN. |
| validateTxn(params, { user, password, privateKeyPem, env?, fetch? }) | POSTs the validation payload with Basic auth; returns the parsed response. |
| verifyToken(message, signatureB64, publicKeyPem) | Verify an RSA-SHA256 signature against an SPKI PEM public key. |
| buildValidationRequest(params, { privateKeyPem, env?, user?, password? }) | 1.1.0 — pure (no network) signed validate-txn request { url, method, headers, body, token }. |
| paymentTokenMessage(params) | 1.1.0 — the exact canonical redirect message string that gets signed. |
| validationTokenMessage(params) | 1.1.0 — the exact canonical validation message string that gets signed. |
| validateRequest(params) | 1.1.0 — pure field validation → { valid, errors } (paisa TXNAMT, DD-MM-YYYY, id charset, NPR). |
| LOGIN_URL, VALIDATE_URL, ENDPOINTS | { test, prod } endpoint maps; ENDPOINTS[env] groups { login, validate }. |
| ValidateTxnResponse, ValidationResult, ValidationRequest | 1.1.0 — response/result/request types. |
env defaults to "test" (UAT). All signing uses your PKCS#8 PEM private key; all HTTP goes through the global fetch (injectable for tests).
Security notes
- Keep your RSA private key on the server only — never ship it to the browser.
- The signed message must match Connect IPS's expected field order exactly; this package builds it for you.
Licensing
This package is free under the Lacspace Free Licence — permissive freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
Not every Lacspace package is free. We also offer Commercial (paid), Client-specific, and Private (proprietary) packages under separate terms. See the full Lacspace Licence Centre.
The Lacspace Developer Platform
@lacspace/connectips is part of 80+ zero-dependency, isomorphic TypeScript packages. Explore the ecosystem:
- 🗂️ All packages — https://developer.lacspace.com/packages
- 🧭 Developer handbook — https://developer.lacspace.com/handbook
- 🧪 Live playground — https://developer.lacspace.com/playground
- 🖥️ Finished app templates — https://templates.lacspace.com
- 🚀 Scaffold a full app —
npm create lacspace-app@latest
Free under the Lacspace Free Licence — a permissive, free-to-use licence.
