Skip to content

Java SDK ​

The official SOAMTAR library for Java (com.soamtar:soamtar-java 1.1.0): collect payments and send payouts with Orange Money Sierra Leone, in leones (SLE).

  • Java 11+, one dependency (Jackson)
  • Sierra Leone defaults: currency SLE, provider orange_money_sl
  • Amounts as String ("150.00") or BigDecimal, never double/float
  • An Idempotency-Key is sent automatically on every POST
  • Webhook signature verification (X-Soamtar-Signature)

Server-side only

The client takes a secret key (sk_test_… or sk_live_…). Never ship it to a browser or a mobile app.

Installation ​

Maven:

xml
<dependency>
    <groupId>com.soamtar</groupId>
    <artifactId>soamtar-java</artifactId>
    <version>1.1.0</version>
</dependency>

Gradle:

groovy
implementation 'com.soamtar:soamtar-java:1.1.0'

Until the artifact is published, build it from the repository (mvn install).

Configuration ​

java
import com.soamtar.sdk.SoamtarClient;

SoamtarClient soamtar = new SoamtarClient();   // reads SOAMTAR_SECRET_KEY and SOAMTAR_API_URL
SoamtarClient explicit = new SoamtarClient(System.getenv("SOAMTAR_SECRET_KEY"));

// Other base URL, request timeout (default 30 s) and optional request signing
SoamtarClient custom = new SoamtarClient(secretKey, SoamtarClient.DEFAULT_BASE_URL, Duration.ofSeconds(60), true);
Environment variableUsed for
SOAMTAR_SECRET_KEYSecret key, when none is passed to the constructor.
SOAMTAR_API_URLBase URL (default https://app.so-amtar.com/v1). SOAMTAR_BASE_URL is still read as a fallback (deprecated name).

Explicit constructor values always win over the environment.

sk_test_ keys run on the same host as live keys (https://app.so-amtar.com/v1): payments are simulated and no money moves. soamtar.isTestMode() tells you which key is in use.

Request signing ​

With the last constructor argument signRequests = true (new SoamtarClient(null, null, Duration.ofSeconds(30), true)), each POST also carries Soamtar-Timestamp: <unix> and Soamtar-Signature: v1=hex(HMAC_SHA256(secretKey, "<timestamp>.<METHOD>.<path>")), for example over 1700000000.POST.v1/payments (path without the leading slash; timestamp within ±300 s). It is optional hardening: the Bearer key over HTTPS already authenticates you. See Authentication.

Collect a payment ​

java
import com.soamtar.sdk.SoamtarException;
import java.util.Map;

try {
    Map<String, Object> response = soamtar.payments.create(Map.of(
        "merchant_ref", "ORDER-1042",
        "amount", "150.00",              // Le 150.00
        "phone", "+23276123456",         // +232 followed by 8 digits
        "description", "Order #1042",
        "success_url", "https://shop.example.sl/thanks",
        "failed_url", "https://shop.example.sl/cart",
        "callback_url", "https://shop.example.sl/webhooks/soamtar"   // optional
    ));                                  // currency SLE, provider orange_money_sl by default

    @SuppressWarnings("unchecked")
    Map<String, Object> payment = (Map<String, Object>) response.get("data");
    payment.get("status");               // "pending"
    payment.get("payment_url");          // hosted checkout page
} catch (SoamtarException e) {
    System.err.println(e.getHttpStatus() + " " + e.getCode() + " " + e.getMessage());
}

merchant_ref and amount are checked before anything is sent (IllegalArgumentException).

callback_url (optional, HTTPS, payments and payouts) is a per-payment webhook URL: SOAMTAR sends it every event of that transaction with the same payload and X-Soamtar-Signature as your registered endpoints (signed with the secret of your oldest active endpoint, so register at least one). Verify it the same way.

Pass your own Idempotency-Key to make retries safe:

java
soamtar.payments.create(params, "order-1042-attempt-1");

Other calls:

java
soamtar.payments.retrieve(id);                    // by SOAMTAR id or reference
soamtar.payments.retrieve(id, true);              // long poll until a final status
soamtar.payments.verify(id);                      // re-check the status with Orange Money
soamtar.payments.list(Map.of("status", "success", "per_page", 50));
soamtar.payments.status("TXN-1A2B3C4D-...");      // by SOAMTAR id or reference
soamtar.balance.retrieve();                       // available, pending, reserved

Send a payout ​

Payouts require a key with the payout permission. merchant_ref, amount and recipient.phone (+232 followed by 8 digits) are validated before sending.

java
soamtar.payouts.create(Map.of(
    "merchant_ref", "PAYOUT-77",
    "amount", "50.00",
    "recipient", Map.of("phone", "+23276123456", "name", "Aminata Kamara")
));
soamtar.payouts.retrieve(id);
soamtar.payouts.list(Map.of("status", "success"));

Refunds ​

Refund a successful payment, fully or partially: SOAMTAR credits the amount back to the payer's Orange Money number and debits your balance. The key needs the can_refund scope.

java
Map<String, Object> response = soamtar.refunds.create(Map.of(
    "transaction_id", paymentId,          // SOAMTAR id (or reference) of the payment
    "amount", "50.00",                    // optional: omit for a full refund of what remains
    "reason", "customer_request",         // optional
    "description", "Item out of stock"    // optional
), "refund-order-1042-1");                // Idempotency-Key

Map<String, Object> refund = (Map<String, Object>) response.get("data");
refund.get("reference");                  // "REF-…"
refund.get("status");                     // "success" (test key), "processing" (Orange Money) or "failed"

soamtar.refunds.retrieve("REF-…");        // id or reference
soamtar.refunds.list(Map.of("transaction_id", paymentId, "status", "success"));

The refund is submitted in the same request; with Orange Money it stays processing until Orange confirms the credit. The outcome arrives with refund.success / refund.failed, plus payment.refunded / payment.partially_refunded on success. See Refunds for the object and error codes.

Webhooks ​

SOAMTAR posts events to the endpoints you register (dashboard or webhooks.create), signed with that endpoint's secret: X-Soamtar-Signature: t=<unix>,v1=<hmac> (HMAC-SHA256 of "<t>.<raw body>"). During a secret rotation a v2= signature is added; either one is accepted. Signatures older than 300 seconds are rejected.

Spring Boot ​

java
import com.soamtar.sdk.Webhook;

@RestController
public class SoamtarWebhookController {

    @Value("${soamtar.webhook-secret}")
    private String webhookSecret;

    @PostMapping("/webhooks/soamtar")
    @SuppressWarnings("unchecked")
    public ResponseEntity<String> handle(@RequestBody String payload,
                                         @RequestHeader(Webhook.SIGNATURE_HEADER) String signature) {
        try {
            Map<String, Object> event = Webhook.constructEvent(payload, signature, webhookSecret);
            Map<String, Object> data = (Map<String, Object>) event.get("data");

            switch ((String) event.get("event")) {
                case "payment.success" -> markPaid((String) data.get("merchant_reference"));
                case "payment.failed", "payment.expired", "payment.cancelled" -> markFailed((String) data.get("merchant_reference"));
                case "refund.success" -> markRefunded((String) data.get("merchant_reference"), (String) data.get("amount"));
                default -> { }   // webhook.test and others: just 200
            }
            return ResponseEntity.ok("OK");
        } catch (Webhook.WebhookSignatureException e) {
            return ResponseEntity.badRequest().body("Invalid signature");
        }
    }
}

Take the body as a String (@RequestBody String) so the signature is checked against the raw bytes. The same event can be delivered more than once: deduplicate on event + data.id.

Events you can subscribe to: 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 is sent by soamtar.webhooks.sendTest() and the dashboard's test button whatever your subscriptions. See Webhooks.

Manage endpoints ​

java
soamtar.webhooks.create("https://shop.example.sl/webhooks/soamtar");   // default events
soamtar.webhooks.create("https://shop.example.sl/webhooks/soamtar",
    List.of("payment.success", "payment.failed", "refund.success", "refund.failed"));
soamtar.webhooks.list();
soamtar.webhooks.secret(endpointId);   // signing secret
soamtar.webhooks.sendTest();           // sends a webhook.test event

Without an event list (or with null / an empty list), create() subscribes to payment.success, payment.failed, payout.success and payout.failed (Webhooks.DEFAULT_EVENTS), like the other SDKs.

Errors ​

API errors throw SoamtarException: getHttpStatus(), getCode() (for example validation_error) and getBody() (decoded JSON). Network failures use status 0 and code network_error. An invalid key or a double amount throws IllegalArgumentException.

Security ​

  • Native java.net.http.HttpClient, no third-party HTTP library.
  • Constant-time signature comparison.
  • The secret key is masked in toString().

SOAMTAR Payment API Documentation.