SDK .NET (C#)
La bibliothèque officielle SOAMTAR pour .NET (paquet Soamtar 1.1.0, espace de noms Soamtar) : encaissez et envoyez des paiements avec Orange Money Sierra Leone, en leones (SLE).
- .NET 8, aucune dépendance hors bibliothèque de base (
System.Text.Json) - Valeurs par défaut pour la Sierra Leone : devise
SLE, fournisseurorange_money_sl - Montants en
decimal(150.00m) ou"150.00", jamais endouble/float - En-tête
Idempotency-Keyajouté automatiquement à chaque POST - Vérification de la signature des webhooks (
X-Soamtar-Signature)
Côté serveur uniquement
Le client utilise une clé secrète (sk_test_… ou sk_live_…). Ne l'embarquez jamais dans un navigateur ou une application mobile.
Installation
dotnet add package Soamtar --version 1.1.0Tant que le paquet n'est pas publié, référencez le projet depuis le dépôt (dotnet add reference chemin/vers/Soamtar.csproj).
Configuration
using Soamtar;
var soamtar = new SoamtarClient(); // lit SOAMTAR_SECRET_KEY et SOAMTAR_API_URL
var explicitClient = new SoamtarClient(Environment.GetEnvironmentVariable("SOAMTAR_SECRET_KEY")!);
// Votre propre HttpClient (par exemple via IHttpClientFactory), une autre URL de base, signature facultative
var custom = new SoamtarClient(secretKey, SoamtarClient.DefaultBaseUrl, httpClient, signRequests: true);| Variable d'environnement | Rôle |
|---|---|
SOAMTAR_SECRET_KEY | Clé secrète, si aucune n'est passée. |
SOAMTAR_API_URL | URL de base (par défaut https://app.so-amtar.com/v1). SOAMTAR_BASE_URL reste lu en repli (ancien nom, déprécié). |
Tous les arguments du constructeur sont facultatifs ; les valeurs explicites l'emportent toujours sur l'environnement.
Avec ASP.NET Core, enregistrez une instance unique :
builder.Services.AddSingleton(new SoamtarClient(builder.Configuration["Soamtar:SecretKey"]!));Les clés sk_test_ passent par le même hôte que les clés live (https://app.so-amtar.com/v1) : les paiements sont simulés et aucun argent ne circule. soamtar.IsTestMode indique quelle clé est utilisée.
Signature des requêtes
new SoamtarClient(signRequests: true) ajoute Soamtar-Timestamp: <unix> et Soamtar-Signature: v1=hex(HMAC_SHA256(secretKey, "<timestamp>.<METHODE>.<chemin>")) à chaque POST, par exemple sur 1700000000.POST.v1/payments (chemin sans la barre oblique initiale ; horodatage à ±300 s). SoamtarClient.SignRequest(secretKey, timestamp, method, path) expose le calcul. C'est un durcissement facultatif : la clé Bearer sur HTTPS vous authentifie déjà. Voir Authentification.
Encaisser un paiement
try
{
var response = await soamtar.Payments.CreateAsync(new Dictionary<string, object?>
{
["merchant_ref"] = "ORDER-1042",
["amount"] = 150.00m, // Le 150.00
["phone"] = "+23276000001", // +232 suivi de 8 chiffres
["description"] = "Commande n° 1042",
["success_url"] = "https://shop.example.sl/merci",
["failed_url"] = "https://shop.example.sl/panier",
["callback_url"] = "https://shop.example.sl/webhooks/soamtar", // facultatif
}); // devise SLE et fournisseur orange_money_sl par défaut
var payment = response.GetProperty("data");
var status = payment.GetProperty("status").GetString(); // "pending"
var paymentUrl = payment.GetProperty("payment_url").GetString(); // page de paiement hébergée
}
catch (SoamtarException e)
{
Console.Error.WriteLine($"{e.HttpStatus} {e.Code} {e.Message}");
}merchant_ref et amount sont vérifiés avant tout envoi (ArgumentException).
callback_url (facultatif, HTTPS, paiements et payouts) est une URL de webhook propre à la transaction : SOAMTAR y envoie tous ses événements avec le même payload et la même X-Soamtar-Signature que pour vos endpoints enregistrés (signée avec le secret de votre plus ancien endpoint actif : enregistrez-en au moins un). Vérifiez-le de la même façon.
Les appels renvoient un System.Text.Json.JsonElement (le corps {"data": …} décodé). Fournissez votre propre Idempotency-Key pour pouvoir relancer une requête sans risque :
await soamtar.Payments.CreateAsync(parameters, idempotencyKey: "order-1042-attempt-1");Autres appels :
await soamtar.Payments.RetrieveAsync(id); // par identifiant ou référence SOAMTAR
await soamtar.Payments.RetrieveAsync(id, wait: true); // attend un statut final (long polling)
await soamtar.Payments.VerifyAsync(id); // revérifie le statut auprès d'Orange Money
await soamtar.Payments.ListAsync(new Dictionary<string, object?> { ["status"] = "success", ["per_page"] = 50 });
await soamtar.Payments.StatusAsync("TXN-1A2B3C4D-..."); // par identifiant ou référence SOAMTAR
await soamtar.Balance.RetrieveAsync(); // disponible, en attente, réservéEnvoyer un paiement (payout)
Les payouts exigent une clé disposant de la permission de payout. merchant_ref, amount et recipient.phone (+232 suivi de 8 chiffres) sont vérifiés avant l'envoi.
await soamtar.Payouts.CreateAsync(new Dictionary<string, object?>
{
["merchant_ref"] = "PAYOUT-77",
["amount"] = 50.00m,
["recipient"] = new Dictionary<string, object?> { ["phone"] = "+23276000001", ["name"] = "Aminata Kamara" },
}, idempotencyKey: "payout-PAYOUT-77");
await soamtar.Payouts.RetrieveAsync(id);
await soamtar.Payouts.ListAsync(new Dictionary<string, object?> { ["status"] = "success" });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 disposer du scope can_refund.
var response = await soamtar.Refunds.CreateAsync(new Dictionary<string, object?>
{
["transaction_id"] = paymentId, // identifiant (ou référence) SOAMTAR du paiement
["amount"] = 50.00m, // facultatif : omis = remboursement total du restant
["reason"] = "customer_request", // facultatif
["description"] = "Article en rupture", // facultatif
}, idempotencyKey: "refund-order-1042-1");
var refund = response.GetProperty("data");
refund.GetProperty("reference").GetString(); // "REF-…"
refund.GetProperty("status").GetString(); // "success" (clé de test), "processing" (Orange Money) ou "failed"
await soamtar.Refunds.RetrieveAsync("REF-…"); // identifiant ou référence
await soamtar.Refunds.ListAsync(new Dictionary<string, object?> { ["transaction_id"] = paymentId, ["status"] = "success" });Le remboursement est soumis dans la même requête ; avec Orange Money il reste processing jusqu'à ce qu'Orange confirme le crédit. Le résultat arrive par refund.success / refund.failed, plus payment.refunded / payment.partially_refunded en cas de succès. Voir Remboursements pour l'objet et les codes d'erreur.
Webhooks
SOAMTAR envoie les événements aux endpoints que vous enregistrez (tableau de bord ou Webhooks.CreateAsync), signés avec le secret de l'endpoint : X-Soamtar-Signature: t=<unix>,v1=<hmac> (HMAC-SHA256 de "<t>.<corps brut>"). Pendant une rotation du secret, une signature v2= est ajoutée ; l'une ou l'autre est acceptée. Les signatures de plus de 300 secondes sont rejetées.
ASP.NET Core (minimal API)
app.MapPost("/webhooks/soamtar", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var payload = await reader.ReadToEndAsync(); // corps BRUT
try
{
var evt = Webhook.ConstructEvent(payload, request.Headers[Webhook.SignatureHeader],
Environment.GetEnvironmentVariable("SOAMTAR_WEBHOOK_SECRET")!);
var data = evt.GetProperty("data");
switch (evt.GetProperty("event").GetString())
{
case "payment.success":
MarkPaid(data.GetProperty("merchant_reference").GetString());
break;
case "payment.failed":
case "payment.expired":
case "payment.cancelled":
MarkFailed(data.GetProperty("merchant_reference").GetString());
break;
case "refund.success":
MarkRefunded(data.GetProperty("merchant_reference").GetString(), data.GetProperty("amount").GetString());
break;
// webhook.test et le reste : simple 200
}
return Results.Ok();
}
catch (WebhookSignatureException)
{
return Results.BadRequest("Invalid signature");
}
});Un même événement peut être livré plusieurs fois : dédoublonnez sur event + data.id.
É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. webhook.test est envoyé par soamtar.Webhooks.SendTestAsync() et par le bouton de test du tableau de bord, quels que soient vos abonnements. Voir Webhooks.
Gérer les endpoints
await soamtar.Webhooks.CreateAsync("https://shop.example.sl/webhooks/soamtar"); // événements par défaut
await soamtar.Webhooks.CreateAsync("https://shop.example.sl/webhooks/soamtar",
new[] { "payment.success", "payment.failed", "refund.success", "refund.failed" });
await soamtar.Webhooks.ListAsync();
await soamtar.Webhooks.SecretAsync(endpointId); // secret de signature
await soamtar.Webhooks.SendTestAsync(); // envoie un événement webhook.testSans liste d'événements (ou avec null / une liste vide), CreateAsync() abonne l'endpoint à payment.success, payment.failed, payout.success et payout.failed (Webhooks.DefaultEvents), comme les autres SDK.
Erreurs
Les erreurs de l'API lèvent une SoamtarException avec HttpStatus, Code (par exemple validation_error) et le corps brut Body. Les pannes réseau ont le statut 0 et le code network_error. Une clé invalide, un montant en double ou un décimal à plus de 2 décimales lèvent une ArgumentException.
Sécurité
- Comparaison des signatures en temps constant.
- La clé secrète est masquée dans
ToString(). - Les webhooks de plus de 1 Mo sont rejetés.