Daraja (daraja v0.1.0)

Copy Markdown View Source

Elixir client for the Safaricom Daraja API.

All API calls take a Daraja.Client as their first argument. Build a client once and reuse it; per-request options on Daraja.Client.new/1 override values read from the application environment, which is handy for multi-tenant callers.

client = Daraja.Client.new()

M-Pesa Express (STK Push)

Initiate a payment prompt on a customer's phone:

Daraja.Express.request(client, %{
  amount: 100,
  phone_number: "254712345678",
  account_reference: "Order-001"
})

Customer to Business (C2B)

Register callback URLs for payment notifications:

Daraja.C2B.register_url(client, %{
  short_code: "600984",
  response_type: "Completed",
  confirmation_url: "https://example.com/c2b/confirmation",
  validation_url: "https://example.com/c2b/validation"
})

Simulate a payment in the sandbox environment:

Daraja.C2B.simulate(client, %{
  short_code: "600984",
  command_id: "CustomerPayBillOnline",
  amount: 100,
  msisdn: "254708374149",
  bill_ref_number: "INV-001"
})

Business to Customer (B2C)

Pass the initiator password and certificate as a tuple — PaymentRequest.new/1 encrypts it internally (the tuple form is sugar over calling Daraja.SecurityCredential.encrypt/2 yourself):

Daraja.B2C.payment(client, %{
  originator_conversation_id: "my-unique-id-001",
  initiator_name: "testapi",
  security_credential: {"your-initiator-password", File.read!("sandbox-cert.cer")},
  command_id: "BusinessPayment",
  amount: 10,
  party_a: "600997",
  party_b: "254705912645",
  remarks: "Payout",
  queue_timeout_url: "https://example.com/b2c/timeout",
  result_url: "https://example.com/b2c/result",
  occasion: "Promo"
})

For production, pre-encrypt with Daraja.SecurityCredential.encrypt/2 at deploy time and store only the resulting Base64 string so plaintext passwords never live in application state:

{:ok, security_credential} =
  Daraja.SecurityCredential.encrypt(
    "your-initiator-password",
    File.read!("cert.cer")
  )

Daraja.B2C.payment(client, %{security_credential: security_credential, ...})

Business to Business (B2B)

Same credential options as B2C — tuple for convenience, pre-encrypted string for production:

Daraja.B2B.request(client, %{
  initiator: "testapi",
  security_credential: {"your-initiator-password", File.read!("sandbox-cert.cer")},
  command_id: "BusinessPayBill",
  sender_identifier_type: 4,
  receiver_identifier_type: 4,
  amount: 10_500,
  party_a: "600992",
  party_b: "600000",
  account_reference: "INV-001",
  remarks: "B2B Payment",
  queue_timeout_url: "https://example.com/b2b/timeout",
  result_url: "https://example.com/b2b/result"
})

Configuration

Configure default credentials in your application config:

config :daraja,
  consumer_key: "...",
  consumer_secret: "...",
  business_short_code: "174379",
  passkey: "bfb279...",
  callback_url: "https://example.com/callback",
  environment: :sandbox,
  b2b_initiator: "testapi",
  b2b_security_credential: "base64-credential",
  b2b_queue_timeout_url: "https://example.com/b2b/timeout",
  b2b_result_url: "https://example.com/b2b/result",
  b2c_initiator_name: "testapi",
  b2c_security_credential: "base64-credential",
  b2c_queue_timeout_url: "https://example.com/b2c/timeout",
  b2c_result_url: "https://example.com/b2c/result"

The b2b_* and b2c_* keys are optional defaults for Daraja.B2B.request/2 and Daraja.B2C.payment/2. Per-call params always take precedence over env values, which is handy for multi-tenant callers that need to override defaults per request.

Per-call overrides for multi-tenant callers:

client =
  Daraja.Client.new(
    consumer_key: merchant.consumer_key,
    consumer_secret: merchant.consumer_secret
  )

Callback security

Daraja does not sign inbound webhooks. Use Daraja.Callback.Security for IP allowlisting and shared-secret checks, Daraja.Callback.Guard for idempotency, and parse/1 on the product callback modules before fulfilling orders.

Default callback IP allowlists are community-documented Safaricom ranges—not fetched from the live Daraja API. Override them for production:

config :daraja,
  callback_cidrs: ["196.201.212.0/24", "196.201.213.0/24", "196.201.214.0/24"],
  callback_hosts: ["196.201.214.200", "196.201.212.127"]

Token Caching

Production deployments must start both Finch and Daraja.Supervisor. Without the supervisor, every API call performs a separate OAuth round-trip (logged once as a warning when :warn_uncached_token is enabled, which is the default outside :test).

Add both to your application's supervision tree:

children = [
  {Finch, name: Daraja.Finch},
  {Daraja.Supervisor, []}
]

In umbrella apps, start one supervisor per credential set with distinct names and point the library at the right cache via config:

# supervision tree
{Daraja.Supervisor, name: :billing_sup, cache_name: :billing_cache}

# config/config.exs (or the umbrella child's config)
config :daraja, token_cache: :billing_cache

The :token_cache config value must be an atom. Omit it to use the default Daraja.TokenCache. See Daraja.Auth.get_token/1 for details.

HTTP timeouts and retries

The default Finch adapter uses a 10 second receive timeout. Override in config:

config :daraja, :http_receive_timeout, 10_000

OAuth token fetches can opt into retry with exponential backoff (disabled by default):

config :daraja, :http_retry,
  enabled: true,
  max_attempts: 3,
  base_ms: 100,
  max_ms: 2_000

Payment POST calls are never retried automatically.

Custom HTTP Client

Implement Daraja.HTTPClient and configure it:

config :daraja, :http_client, MyApp.CustomHTTPClient

Custom adapters must verify TLS peers, avoid logging credentials or request bodies, use bounded timeouts, and not follow redirects to unintended hosts. See Daraja.HTTPClient.Compliance for a review checklist.