The stateless, secure, zero-dependency TypeScript SDK for the SATIM payment gateway.
CIB and Edahabia card payments for Algeria — production-grade, runtime-agnostic, security-first.
Warning
This SDK has never been tested against a live production SATIM environment.
It has been developed and validated exclusively against the SATIM test gateway (test.satim.dz) and simulated responses.
Use in production entirely at your own risk. The authors and contributors accept no responsibility for payment failures, data loss, financial losses, or any other damages arising from the use of this software in any environment.
Always perform your own end-to-end testing against the SATIM test gateway before going live.
- Zero runtime dependencies. Just the Web Fetch API. No transitive supply chain.
- Runs everywhere fetch runs. Node 20+, Bun, Deno, Cloudflare Workers, Vercel Edge.
- Strictly immutable, fluent API. Every setter returns a new instance — no cross-request state leaks.
- Security in depth by default. SSRF guards, credential isolation in a module-private
WeakMap, terminal-ID injection prevention, IEEE 754-safe currency conversion, and a deliberately conservative retry policy that never double-charges. - Typed gateway errors. Discriminated
SatimErrorsubclasses with localized messages and a sanitized raw payload for logs (PII redacted). - Zero-trust webhooks. Built-in HMAC verification, replay protection, and rate limiting — designed for distributed deployments.
- Strict TypeScript. Full type coverage, no
anyescape hatches in the public API.
| Runtime | Version | Notes |
|---|---|---|
| Node.js | ≥ 20 | Native fetch required |
| Bun | ≥ 1.0 | |
| Deno | ≥ 1.28 | |
| Cloudflare Workers | All | Edge-runtime safe |
| Vercel / Netlify Edge | All |
npm install satim-module
# or
bun add satim-module
# or
pnpm add satim-moduleimport { Satim } from 'satim-module';
const satim = new Satim({
username: process.env.SATIM_USERNAME!,
password: process.env.SATIM_PASSWORD!,
terminalId: process.env.SATIM_TERMINAL_ID!,
});
// Register a payment
const payment = await satim
.amount(1500)
.returnUrl('https://your-app.com/callback')
.register();
// Redirect the customer to the hosted payment page
return payment.redirectResponse(); // standard Web API Response (302)const response = await satim.confirm(orderId, expectedCartTotal);
if (response.isSuccessful()) {
// Amount verification is automatic for successful payments
console.log(response.getSuccessMessage());
} else if (response.isPending()) {
console.log('Payment not yet completed');
} else if (response.isCancelled()) {
console.log('Customer cancelled the payment');
} else if (response.isExpired()) {
console.log('Payment session expired');
} else {
console.log(response.getErrorMessage());
}| Method | Endpoint | Returns | Description |
|---|---|---|---|
register() |
/register.do |
RegisterResponse |
Register a payment order. |
confirm(orderId, amount) |
/confirmOrder.do |
ConfirmResponse |
Confirm and deposit a payment. |
status(orderId) |
/getOrderStatus.do |
ConfirmResponse |
Query the current status of an order. |
refund(orderId, amount) |
/refund.do |
ConfirmResponse |
Refund a captured payment. |
registerPreAuth() |
/registerPreAuth.do |
RegisterResponse |
Hold funds without capturing. |
reverseOrder(orderId) |
/reverse.do |
ConfirmResponse |
Void a transaction before settlement. |
The configuration is strictly immutable. Calling a setter returns a NEW instance.
| Method | Description |
|---|---|
amount(n) |
Payment amount in major currency units (e.g. DZD). |
returnUrl(url) |
Redirect URL after payment. |
failUrl(url) |
Redirect URL on failure (defaults to returnUrl). |
description(text) |
Text shown on the payment page (max 598 chars). |
language(lang) |
Payment page language: "FR", "AR", or "EN". |
currency(code) |
"DZD", "USD", or "EUR". |
orderNumber(n) |
Custom 10-digit order number. |
timeout(seconds) |
Session timeout (600 – 86400). |
userDefinedFields() |
Custom metadata forwarded in jsonParams. |
dynamicCallbackUrl() |
Server-to-server webhook for status notifications. |
setTestMode(bool) |
Route requests to test.satim.dz. |
All predicates are mutually exclusive — at most one terminal-state predicate will return true for any given response.
| Method | Condition |
|---|---|
isSuccessful() |
Payment deposited (OrderStatus 2). |
isPending() |
Registered but not yet paid (OrderStatus 0). |
isReversed() |
Authorization reversed/voided (OrderStatus 3). |
isFailed() |
Terminal failure (not successful, refunded, or pending). |
isRejected() |
Declined by the issuing bank. |
isRefunded() |
Refunded (OrderStatus 4). |
isCancelled() |
Customer cancelled before completing. |
isExpired() |
Session timed out (actionCode -2007). |
Available on RegisterResponse or ConfirmResponse:
| Method | Available On | Returns |
|---|---|---|
getOrderId() |
RegisterResponse | Order identifier |
getUrl() |
RegisterResponse | Hosted payment form URL |
redirectResponse() |
RegisterResponse | Web API 302 Response |
getIpAddress() |
ConfirmResponse | Cardholder IP |
getCardHolderName() |
ConfirmResponse | Name on card |
getCardExpiry() |
ConfirmResponse | Expiration (YYYYMM) |
getCardPan() |
ConfirmResponse | Masked PAN |
getApprovalCode() |
ConfirmResponse | Issuer approval code |
getAmount() |
ConfirmResponse | Payment amount captured |
getOrderNumber() |
ConfirmResponse | Verified order number |
verifyAmount(n) |
ConfirmResponse | Security assertion |
getSuccessMessage() |
ConfirmResponse | Localized success text |
getErrorMessage() |
ConfirmResponse | Localized error text |
getRawResponse() |
Both | Sanitized raw gateway response (PII redacted) |
All errors extend SatimError for unified catching:
import { SatimError, SatimGatewayError } from 'satim-module';
try {
await satim.register();
} catch (err) {
if (err instanceof SatimGatewayError) {
// Typed BPC gateway error with err.errorCode and err.errorMessage
// Code 1 = Duplicate order, 3 = Unknown currency,
// 4 = Missing parameter, 7 = System error
}
if (err instanceof SatimError) {
// SatimMissingDataError - required field not set
// SatimInvalidArgumentError - validation failure
// SatimInvalidCredentialsError - wrong username/password/terminal
// SatimUnexpectedResponseError - network or malformed response
// SatimGatewayError - typed BPC gateway errors (1,3,4,7)
}
}The SDK ships with the following protections enabled by default:
- Credential isolation — Credentials live in a module-private
WeakMapand never appear as enumerable properties.JSON.stringify()andconsole.log()automatically redact them. (The SATIM API requires credentials as POST form parameters on every request — make sure reverse proxies, WAFs, and APM tools do not log raw request bodies.) - SSRF protection — All URLs are validated against private IP ranges (IPv4/IPv6), cloud metadata endpoints, and non-standard IP encodings (decimal, octal, hex).
- Terminal-ID injection prevention —
force_terminal_idis always set by the SDK and cannot be overridden viauserDefinedFields. - IEEE 754-safe currency conversion — Amounts are converted to minor units using
toPrecision(12)to avoid floating-point drift. Sub-centime amounts that would round to zero are rejected. The precision guard caps at ~10 billion major units. - Safe retry policy — Financial mutations (
register,confirm,refund,reverseOrder) never retry on transient errors, preventing double-charges or double-refunds. Only idempotent queries (status) retry automatically. - Immutable API — Every setter returns a new instance, preventing cross-request state leaks.
- Always verify server-side. Never trust client-side redirect parameters. Use
confirm()orstatus()to verify outcomes from your backend. - Always verify amounts. Call
verifyAmount()to detect partial-payment manipulation. - Use environment variables. Never hardcode credentials. Load them from
process.envor a secrets manager. - Never log request bodies. Outgoing POST bodies contain credentials; ensure logging middleware excludes them.
- Keep TLS valid. Never set
NODE_TLS_REJECT_UNAUTHORIZED=0in production.
For the full threat model, see SECURITY.md.
ARCHITECTURE.md— system topology, request lifecycle, technical constraints, state machine, predicate contract.src/README.md— module map and dependency order.src/responses/README.md— response wrappers and the status predicate contract.src/webhook/README.md— zero-trust verification flow and distributed deployment notes.SECURITY.md— threat model, mitigations, known limitations.- API reference — generated locally with
npm run docs(outputs todocs/api/).
bun install # install dev dependencies
bun test # run the unit test suite (vitest)
npm run typecheck # strict type checking (any runtime)
npm run build # compile to dist/
npm run docs # generate TypeDoc API referenceContributions are welcome. Please open an issue first for anything beyond a small fix so we can align on scope. PRs should:
- Keep the zero-dependency rule intact.
- Pass
npm run typecheckand the full test suite. - Include tests for behavioral changes.
- Follow the existing immutable, fluent API style.
Released under the MIT License.