Skip to content

Ruby SDK ​

The official SOAMTAR gem (soamtar 1.1.0): collect payments and send payouts with Orange Money Sierra Leone, in leones (SLE).

  • Ruby 2.6+, no runtime dependencies (net/http, openssl, json)
  • Sierra Leone defaults: currency SLE, provider orange_money_sl
  • Amounts as 2-decimal strings ("150.00"), Integer or BigDecimal, never 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 ​

ruby
# Gemfile
gem 'soamtar'

Until the gem is published, install it from the repository (gem 'soamtar', git: '<repository URL>').

Configuration ​

ruby
require 'soamtar'

client = Soamtar::Client.new                  # reads SOAMTAR_SECRET_KEY
client = Soamtar::Client.new('sk_test_...')   # or pass the secret key

# Shared client, e.g. config/initializers/soamtar.rb
Soamtar.configure(ENV['SOAMTAR_SECRET_KEY'])
Soamtar.payments.create(...)
OptionDefaultDescription
base_url:SOAMTAR_API_URL, then SOAMTAR_BASE_URL (deprecated name), else https://app.so-amtar.com/v1API root
timeout:15Timeout in seconds
sign_requests:falseSigns every POST (see Request signing)
ruby
client = Soamtar::Client.new(ENV['SOAMTAR_SECRET_KEY'], timeout: 30)
client.test_mode?   # true with a sk_test_ key

sk_test_ keys run on the same host as live keys: payments are simulated and no money moves.

Request signing ​

Soamtar::Client.new(key, sign_requests: true) adds Soamtar-Timestamp: <unix> and Soamtar-Signature: v1=hex(HMAC_SHA256(secret_key, "<timestamp>.<METHOD>.<path>")) to every POST (path without the leading slash, e.g. v1/payments; timestamp within ±300 s). It is optional hardening: the Bearer key over HTTPS already authenticates you. See Authentication.

Collect a payment ​

ruby
payment = client.payments.create(
  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' and provider 'orange_money_sl' by default

payment['data']['status']           # "pending"
payment.payment_url                 # hosted checkout page (data.payment_url)

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).

In Rails, send the customer to the hosted page:

ruby
redirect_to payment.payment_url, allow_other_host: true

Pass your own Idempotency-Key to make retries safe:

ruby
client.payments.create(params, idempotency_key: 'order-1042-attempt-1')

Other calls:

ruby
client.payments.retrieve(id)                 # by SOAMTAR id or reference
client.payments.retrieve(id, wait: true)     # long poll until a final status
client.payments.verify(id)                   # re-check the status with Orange Money
client.payments.list(status: 'success', per_page: 50)
client.payments.status('TXN-1A2B3C4D-...') # by SOAMTAR id or reference
client.balance.retrieve

Send a payout ​

ruby
client.payouts.create(
  merchant_ref: 'PAYOUT-77',
  amount: '50.00',
  recipient: { phone: '+23276123456', name: 'Aminata Kamara' },
  description: 'Supplier payment'
)
client.payouts.retrieve(id)
client.payouts.list(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.

ruby
refund = client.refunds.create(
  { transaction_id: payment['data']['id'],   # SOAMTAR id (or reference) of the payment
    amount: '50.00',                         # omit for a full refund of what remains
    reason: 'customer_request',
    description: 'Item out of stock' },
  idempotency_key: 'refund-order-1042-1'
)

refund['data']['reference']   # "REF-…"
refund['data']['status']      # "success" (test key), "processing" (Orange Money) or "failed"

client.refunds.retrieve(refund['data']['reference'])   # id or reference
client.refunds.list(transaction_id: payment['data']['id'], 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.

Rails ​

ruby
class SoamtarWebhooksController < ActionController::API
  def create
    event = Soamtar::Webhook.construct_event(
      request.raw_post,                             # RAW body
      request.headers['X-Soamtar-Signature'],
      ENV.fetch('SOAMTAR_WEBHOOK_SECRET')
    )

    case event['event']
    when 'payment.success'
      Order.mark_paid!(event['data']['merchant_reference'])
    when 'payment.failed', 'payment.expired', 'payment.cancelled'
      Order.mark_failed!(event['data']['merchant_reference'])
    when 'refund.success'
      Order.mark_refunded!(event['data']['merchant_reference'], event['data']['amount'])
    end                                             # webhook.test and others: just 200
    head :ok
  rescue Soamtar::SignatureError
    head :bad_request
  end
end

Sinatra ​

ruby
require 'sinatra'
require 'soamtar'

post '/webhooks/soamtar' do
  event = Soamtar::Webhook.construct_event(
    request.body.read,
    request.env['HTTP_X_SOAMTAR_SIGNATURE'],
    ENV.fetch('SOAMTAR_WEBHOOK_SECRET')
  )
  # handle event['event'] / event['data']
  status 200
rescue Soamtar::SignatureError
  status 400
end

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 client.webhooks.send_test and the dashboard's test button whatever your subscriptions. See Webhooks.

Manage endpoints ​

ruby
client.webhooks.create('https://shop.example.sl/webhooks/soamtar')   # defaults: payment.success/failed, payout.success/failed
client.webhooks.create('https://shop.example.sl/webhooks/soamtar', %w[payment.success refund.success refund.failed])
client.webhooks.list
client.webhooks.lookup('https://shop.example.sl/webhooks/soamtar')
client.webhooks.secret(endpoint_id)    # signing secret
client.webhooks.send_test              # sends a webhook.test event

Deprecated aliases, still working: client.webhooks.test (use send_test), client.status.retrieve (use client.payments.status).

Errors ​

ruby
begin
  client.payments.create(...)
rescue Soamtar::ApiError => e
  e.status   # HTTP status
  e.code     # API error code, e.g. "validation_error"
  e.errors   # field messages on 422
rescue Soamtar::Error => e
  e.message  # configuration, network or invalid input
end

Security ​

  • Constant-time signature comparison.
  • The secret key is masked in inspect, to_s and marshal_dump.
  • Webhook payloads over 1 MB are rejected.

SOAMTAR Payment API Documentation.