Building SATIM-TS: My Journey to a Reliable Payments SDK
Transforming painful payment integrations into a seamless developer experience
When I began working with the SATIM payment gateway in Algeria, I quickly discovered how fragile and frustrating the integration process was. Merchants had to send raw HTTP requests to a REST endpoint and hope that everything lined up. What made matters worse was that the existing TypeScript and JavaScript libraries were either middleman service providers that charged fees for what should be a direct integration, or they were outdated packages that hadn't been maintained in years and lacked modern features like TypeScript support, proper error handling, and security best practices. Without a proper, modern SDK for direct integration, small mistakes like a typo in a query string meant failed transactions and lost sales. This gap in the ecosystem drove me to build SATIM-TS, a TypeScript SDK that turns those painful integrations into a straightforward and secure experience.
Why Payment Integration Was Painful
My first step was to understand why developers dreaded SATIM integration. The problems fell into five categories:
No Modern Direct SDK
The most critical gap was the absence of a modern, open-source TypeScript or JavaScript SDK for direct SATIM integration. The existing options fell into two unsatisfactory categories: middleman service providers that charged fees and added unnecessary complexity for what should be a straightforward API call, and outdated libraries that hadn't been maintained in years. These old packages were written before modern JavaScript features, lacked TypeScript definitions, had poor or no error handling, and often used deprecated security practices.
The prevalence of middlemen was also reinforced by a cultural factor: in Algeria, many businesses operate informally without proper registration, which makes it difficult or impossible for them to obtain direct SATIM merchant accounts. Rather than formalizing their operations, these businesses found it easier to hide behind payment aggregators and middleman services. This created a cycle where the lack of proper tooling discouraged direct integration, and the business culture of informal operations made middlemen the path of least resistance. For developers working with properly registered businesses, this meant being forced to either pay for unnecessary intermediaries or build everything from scratch with manual HTTP requests.
Manual Request Building
Without a library, you had to manually craft query strings, convert numbers to the gateway's units, and handle optional fields like udf1 through udf5. A single mis-typed parameter could cause the transaction to be rejected.
Missing Typed Interfaces
SATIM's API documentation didn't provide clear type definitions. Without strong typing, the shape of request objects and responses was ambiguous and integrations were brittle. This motivated me to export strict TypeScript types such as SatimConfig, RegisterOrderParams, ConfirmOrderResponse, and RefundOrderResponse.
Security Risks
Sensitive credentials (username, password, terminal ID) were often hard-coded or logged inadvertently. TLS configuration could be mis-managed, and some developers used GET requests that leaked credentials in the URL. I knew the SDK needed to enforce TLS and hide secrets.
Fragmented Documentation
SATIM's documentation was inconsistent. Error codes weren't always explained, so developers ended up reverse-engineering them. This meant that errors could be hard to interpret unless the SDK provided meaningful messages.
Design Goals and Features
To address these challenges, I set out several design goals and mapped them to concrete features in the SDK:
Type Safety
Write the SDK in TypeScript and export strict types for configuration, request parameters, and responses. Developers can import types like SatimConfig, SatimClient, RegisterOrderParams, ConfirmOrderResponse, and RefundOrderResponse.
Minimal Dependencies
Use native fetch so the library has no heavy runtime dependencies. Build both ESM and CommonJS bundles to support all environments.
Secure by Default
Enforce TLS, never log credentials, and default to POST requests. Secrets are loaded through environment variables, not hard-coded.
Configurable via Environment
Allow all configuration values username, password, terminal ID, API URL, language, currency, timeouts, log level to be set via environment variables. Support custom prefixes (e.g., PAYMENT_) for flexibility.
Pluggable HTTP and Middleware
Let developers supply a custom fetch function for proxy support or custom TLS, and define onRequest/onResponse hooks for logging or metrics.
Idempotency and Duplication Prevention
Accept an idempotencyKey (as externalRequestId) when registering orders so retries don't create duplicate orders.
Comprehensive Error Handling
Export classes like ValidationError, SatimApiError, TimeoutError, HttpError, and ConfigError. Map SATIM error codes to human-readable messages.
Precise Amounts & BigInt Support
Provide toMinorUnits() and fromMinorUnits() to convert dinar amounts to centimes and back. Accept number, string, or bigint amounts and validate that they're non-negative and have at most two decimal places.
Implementation Highlights
Quick Start Workflow
To prove that the SDK could dramatically simplify integrations, I built a quick-start example:
1import { createSatimClient, fromEnv } from '@bakissation/satim'; 2 3// Load configuration from environment variables 4const client = createSatimClient(fromEnv()); 5 6// Register an order 7const registerResponse = await client.register({ 8 orderNumber: 'ORD001', 9 amount: 5000, // 5,000 DZD 10 returnUrl: 'https://yoursite.com/payment/success', 11 failUrl: 'https://yoursite.com/payment/fail', 12 udf1: 'INV001', // your reference 13}); 14 15if (registerResponse.isSuccessful()) { 16 // Redirect the customer to the payment page 17 console.log('Redirect to:', registerResponse.formUrl); 18} 19 20// After the customer returns, confirm the payment 21const confirmResponse = await client.confirm(registerResponse.orderId!); 22 23if (confirmResponse.isPaid()) { 24 console.log('Payment successful!'); 25 console.log('Order:', confirmResponse.orderNumber); 26} 27 28// Refund a transaction 29const refundResponse = await client.refund('ORDER_ID', 5000); 30 31if (refundResponse.isSuccessful()) { 32 console.log('Refund processed'); 33}
This code uses fromEnv() to pull credentials from environment variables and demonstrates how registration, confirmation, and refunds are handled consistently. Methods like isSuccessful() and isPaid() make it easy to check the status of each operation.
Environment-Driven Configuration
All configuration values username, password, terminal ID, API URL, language, currency, timeout settings, and log level can be defined through environment variables. You can even customise the prefix (for example, PAYMENT_USERNAME and PAYMENT_PASSWORD). This design adheres to the twelve-factor principles and keeps secrets out of source control.
Extensibility via Custom Fetch, Middleware, and Logging
The SDK lets developers supply a custom fetch implementation, which is useful when working behind proxies or with custom TLS settings. You can also define onRequest and onResponse middleware functions to capture metrics, add tracing, or log requests and responses. Logging itself is pluggable: by implementing the SatimLogger interface, you can plug in your favourite logging library such as pino or winston.
Typed Errors and Error Mapping
Instead of returning generic errors, the SDK throws specific exceptions like ValidationError, SatimApiError, TimeoutError, HttpError, and ConfigError. This allows you to catch and handle each error appropriately. The library also maps SATIM's numeric error codes to human-readable messages, making it easier to understand what went wrong.
Secure by Design
Security was a non-negotiable requirement. The SDK never logs credentials, always enforces TLS verification, and uses POST requests to avoid leaking sensitive data in query strings. Environment variables are used for storing secrets, and I recommend disabling verbose logging in production. Idempotency keys prevent duplicate orders when retries occur.
Amount Conversion and BigInt Support
SATIM requires amounts to be sent as integers in minor units (centimes). To make this easy, I added toMinorUnits() and fromMinorUnits() helper functions. They accept numbers, strings, or bigint values and convert them to the appropriate format while ensuring that the input is non-negative and has at most two decimal places.
Results and Lessons Learned
Since the initial release of SATIM-TS, integrations have become dramatically simpler. Developers can now get a working payment flow running in minutes instead of hours, thanks to the quick-start example. The strong type definitions catch configuration mistakes at compile time, and the clear error messages make troubleshooting faster. Sensitive credentials are pulled from environment variables and are never logged, which has given teams confidence to deploy the integration in production.
The project is still young and I'm actively looking for feedback and collaboration. If you're working with SATIM or considering integrating it, I'd love for you to try SATIM-TS in your projects and share your experience. Whether it's opening issues, contributing code, suggesting features, or simply starring the repository, every bit of engagement helps shape the direction of the SDK. There is still work to do building recurring payments, adding more language support, and publishing higher-level Fastify or Express plugins and I welcome any contributions or reviews from the community.
Conclusion
Working with SATIM used to mean dealing with brittle HTTP requests, unclear parameter shapes, and security hazards. By building SATIM-TS, I transformed that painful experience into a clean, reliable, and secure SDK. Type safety, environment-based configuration, pluggable networking hooks, and robust error handling are the core ingredients that make this possible.
If you're integrating with the SATIM gateway in Algeria, you can install the package from npm and explore the code and issues on GitHub.