Skip to content

SDK Node.js ​

@soamtar/sdk est le SDK Node.js officiel : il encaisse des paiements et envoie des payouts via Orange Money Sierra Leone, en leones (SLE).

Côté serveur uniquement

Le SDK s'authentifie avec votre clé secrète (sk_test_… / sk_live_…) et refuse toute autre clé. Cette clé ne doit jamais se retrouver dans un navigateur ou une application mobile : utilisez-y le SDK navigateur avec votre clé publique.

Installation ​

bash
npm install @soamtar/sdk

Node.js 18 ou plus récent, aucune dépendance à l'exécution. Version actuelle : 1.1.0 (voir le package.json et le CHANGELOG du SDK). Tant que le paquet n'est pas publié sur npm, installez-le depuis le dépôt du SDK.

Configuration ​

js
const soamtar = require('@soamtar/sdk')(process.env.SOAMTAR_SECRET_KEY);
// ou, en modules ES / TypeScript :
// import soamtarFactory from '@soamtar/sdk'
// const soamtar = soamtarFactory(process.env.SOAMTAR_SECRET_KEY)
Variable d'environnementRôle
SOAMTAR_SECRET_KEYClé secrète, lue si aucune clé n'est passée à la fabrique.
SOAMTAR_API_URLRemplace l'URL de base (par défaut https://app.so-amtar.com/v1). SOAMTAR_BASE_URL reste lu en repli (ancien nom, déprécié).

Options (second argument) : baseUrl, timeout (en ms, 15000 par défaut), signRequests (voir Signature des requêtes).

Il n'existe pas d'hôte sandbox distinct : une clé sk_test_ passe par la simulation sur la même URL. soamtar.isTestMode indique quel type de clé est chargé.

Créer un paiement ​

js
const { data: payment } = await soamtar.payments.create({
  merchant_ref: 'ORDER-1042',
  amount: '1250.00',              // en SLE, 2 décimales
  phone: '+23276000001',          // +232 suivi de 8 chiffres
  description: 'Commande #1042',
  success_url: 'https://shop.example.sl/merci',
  failed_url: 'https://shop.example.sl/paiement-echoue',
  callback_url: 'https://shop.example.sl/webhooks/soamtar', // facultatif
})

// Redirigez le client vers le checkout hébergé, puis attendez le webhook.
console.log(payment.id, payment.status, payment.payment_url)
  • currency vaut SLE par défaut (USD et EUR sont aussi acceptés) et provider vaut orange_money_sl, le seul opérateur.
  • Les montants partent sous forme de chaîne à deux décimales : toAmount(1250) → "1250.00".
  • Le client valide le paiement sur son téléphone avec son code PIN Orange Money.
  • Les notifications sont envoyées aux endpoints webhook que vous avez enregistrés (dashboard ou webhooks.create). Le callback_url facultatif (HTTPS) reçoit en plus tous les événements de ce paiement, avec le même payload et la même X-Soamtar-Signature : vérifiez-le exactement comme votre endpoint (il vous faut au moins un endpoint enregistré, dont le secret signe le callback). Idem pour callback_url sur les payouts.

API ​

Chaque méthode renvoie la réponse JSON décodée : { data } pour un objet, { data: [], meta } pour une liste (refunds.list() renvoie { data: [], pagination }).

MéthodeEndpoint
payments.create(params, { idempotencyKey? })POST/v1/payments
payments.retrieve(id)GET/v1/payments/{id}
payments.list({ per_page, page })GET/v1/payments (per_page de 1 à 100, 25 par défaut)
payments.verify(id)POST/v1/payments/{id}/verify (relit le statut auprès d'Orange Money)
payments.status(idOrReference)GET/v1/status/{idOrReference}
payouts.create(params, { idempotencyKey? })POST/v1/payouts
payouts.retrieve(id)GET/v1/payouts/{id}
payouts.list(query)GET/v1/payouts
refunds.create({ transaction_id, amount?, reason?, description? }, { idempotencyKey? })POST/v1/refunds
refunds.retrieve(idOrReference)GET/v1/refunds/{id} (identifiant ou référence REF-…)
refunds.list({ status?, transaction_id?, per_page?, page? })GET/v1/refunds
balance.retrieve()GET/v1/balance
webhooks.list()GET/v1/webhooks
webhooks.create({ url, subscribed_events?, description?, api_version? })POST/v1/webhooks (idempotent par URL ; subscribed_events vaut par défaut payment.success, payment.failed, payout.success, payout.failed)
webhooks.secret(endpointId)GET/v1/webhooks/{endpoint_id}/secret
webhooks.lookup(url)GET/v1/webhooks/lookup?url=…
webhooks.sendTest()POST/v1/webhooks/test (envoie un événement webhook.test ; webhooks.test() reste un alias déprécié)
request(method, path, body?, { query?, idempotencyKey? })tout autre endpoint

retrieve() et status() attendent l'identifiant ou la référence SOAMTAR (TXN-…), pas votre merchant_ref.

Idempotence ​

Chaque POST porte un en-tête Idempotency-Key. Le SDK génère un UUID v4 à chaque appel ; fournissez votre propre clé pour qu'une nouvelle tentative ne débite jamais deux fois :

js
await soamtar.payments.create(params, { idempotencyKey: `order-${order.id}` })

Remboursements ​

Remboursez un paiement réussi, en totalité ou en partie : SOAMTAR recrédite le montant sur le numéro Orange Money du payeur et débite votre solde. La clé doit avoir le scope can_refund.

js
const { data: refund } = await soamtar.refunds.create({
  transaction_id: payment.id,     // identifiant (ou référence) SOAMTAR du paiement
  amount: '400.00',               // omis : remboursement total du restant remboursable
  reason: 'customer_request',
  description: 'Remboursement partiel de la commande #1042',
}, { idempotencyKey: `refund-${order.id}-1` })

console.log(refund.reference, refund.status) // "REF-…", "processing"

const { data: again } = await soamtar.refunds.retrieve(refund.reference)
const { data: refunds, pagination } = await soamtar.refunds.list({ transaction_id: payment.id })

Le remboursement est soumis dans la même requête : avec une clé de test il revient success ; avec Orange Money il reste processing jusqu'à la confirmation d'Orange ; failed (avec failure_reason) si le crédit est refusé. Le résultat final arrive par webhook : refund.success ou refund.failed, plus payment.refunded / payment.partially_refunded en cas de succès. Voir Remboursements pour l'objet et les codes d'erreur.

Erreurs ​

Un appel en échec est rejeté avec une SoamtarError :

js
const { SoamtarError } = require('@soamtar/sdk')

try {
  await soamtar.payments.create({ merchant_ref: 'ORDER-1', amount: '0' })
} catch (e) {
  if (e instanceof SoamtarError) {
    console.error(e.status, e.code, e.message, e.errors, e.requestId)
    // 422 validation_error "…" { amount: ['…'] } "req_…"
  }
}

code reprend le error.code de l'API (validation_error, …), ou vaut timeout, connection_error, invalid_response pour les erreurs de transport. Communiquez le requestId (en-tête X-Request-Id) au support. Relancez les erreurs 5xx et réseau avec la même idempotencyKey ; ne relancez jamais une 4xx telle quelle.

Webhooks ​

SOAMTAR signe chaque envoi avec X-Soamtar-Signature: t=<unix>,v1=<hex>, un HMAC-SHA256 de "<t>.<corps brut>" calculé avec le secret de l'endpoint. Webhook.constructEvent() le vérifie sur le corps brut et renvoie l'événement décodé :

js
const express = require('express')
const { Webhook } = require('@soamtar/sdk')

const app = express()

app.post('/webhooks/soamtar', express.raw({ type: 'application/json' }), (req, res) => {
  let event
  try {
    event = Webhook.constructEvent(
      req.body,
      req.get('X-Soamtar-Signature'),
      process.env.SOAMTAR_WEBHOOK_SECRET,
    )
  } catch (e) {
    return res.status(400).send(e.message)
  }

  if (event.event === 'payment.success') {
    // event.data.id, event.data.reference, event.data.merchant_reference,
    // event.data.amount, event.data.currency …
  }
  res.sendStatus(200)
})
  • Les envois de plus de 300 secondes sont rejetés (quatrième argument { tolerance } pour changer ce délai).
  • Pendant une rotation du secret, l'en-tête contient aussi v2=…. Passez les deux secrets ([secret, secret_next], tels que renvoyés par webhooks.secret()) : l'une ou l'autre signature est acceptée.
  • Les endpoints créés avec api_version: '2026-06-01' reçoivent { id, object: 'event', type, created, data: { object } } au lieu de { event, created, data }.
  • Événements auxquels s'abonner : payment.success, payment.failed, payment.cancelled, payment.expired, payment.refunded, payment.partially_refunded, payout.success, payout.failed, payout.cancelled, refund.success, refund.failed, payout_request.submitted, payout_request.approved, payout_request.rejected, payout_request.completed, payout_request.failed. Voir Webhooks.
  • webhook.test est envoyé par webhooks.sendTest() et par le bouton de test du dashboard, quels que soient vos abonnements : répondez-y par un 2xx.

Corps brut

La plupart des frameworks Node décodent le JSON automatiquement. Exposez le corps brut (chaîne ou Buffer) sur la route du webhook, sinon la signature ne correspondra jamais. Avec NestJS, créez l'application avec rawBody: true et passez req.rawBody ; avec Fastify, déclarez un parseur de corps brut pour cette route.

Signature des requêtes ​

require('@soamtar/sdk')(key, { signRequests: true }) ajoute Soamtar-Timestamp et Soamtar-Signature: v1=hex(HMAC_SHA256(secretKey, "<timestamp>.<METHODE>.<chemin>")) à chaque POST (<chemin> sans la barre oblique initiale, par exemple v1/payments ; horodatage à ±300 s). C'est un durcissement facultatif : la clé Bearer sur HTTPS vous authentifie déjà. Voir Authentification.

Utilitaires ​

js
const { toAmount, formatLe } = require('@soamtar/sdk')

toAmount(1250)      // "1250.00"
formatLe('1250')    // "Le 1,250.00"

TypeScript ​

Le paquet embarque ses propres types (index.d.ts) : SoamtarClient, CreatePaymentParams, CreatePayoutParams, CreateRefundParams, Refund, WebhookEventType, SoamtarError, Webhook.

ts
import soamtarFactory, { SoamtarError, type CreatePaymentParams } from '@soamtar/sdk'

const soamtar = soamtarFactory(process.env.SOAMTAR_SECRET_KEY)
const params: CreatePaymentParams = { merchant_ref: 'ORDER-1043', amount: '500.00', phone: '+23276000002' }
const { data } = await soamtar.payments.create(params)

Tester votre gestionnaire de webhook ​

ts
import { createHmac } from 'node:crypto'
import { test } from 'node:test'
import assert from 'node:assert'
import { Webhook } from '@soamtar/sdk'

test('accepte une signature récente et valide', () => {
  const secret = 'whsec_test'
  const t = Math.floor(Date.now() / 1000)
  const body = JSON.stringify({ event: 'payment.success', created: new Date().toISOString(), data: {} })
  const sig = `t=${t},v1=${createHmac('sha256', secret).update(`${t}.${body}`).digest('hex')}`

  assert.strictEqual(Webhook.constructEvent(body, sig, secret).event, 'payment.success')
})

SOAMTAR Payment API Documentation.