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.
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.
WeakMap, terminal-ID injection prevention, IEEE 754-safe currency conversion, and a deliberately conservative retry policy that never double-charges.SatimError subclasses with localized messages and a sanitized raw payload for logs (PII redacted).any escape 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-module
import { 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. |
ConfirmResponse)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:
WeakMap and never appear as enumerable properties. JSON.stringify() and console.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.)force_terminal_id is always set by the SDK and cannot be overridden via userDefinedFields.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.register, confirm, refund, reverseOrder) never retry on transient errors, preventing double-charges or double-refunds. Only idempotent queries (status) retry automatically.confirm() or status() to verify outcomes from your backend.verifyAmount() to detect partial-payment manipulation.process.env or a secrets manager.NODE_TLS_REJECT_UNAUTHORIZED=0 in 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.npm run docs (outputs to docs/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 reference
Contributions are welcome. Please open an issue first for anything beyond a small fix so we can align on scope. PRs should:
npm run typecheck and the full test suite.Released under the MIT License.