defmodule Daraja do @moduledoc """ 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. """ @doc false def http_client(ensure_loaded \\ &Code.ensure_loaded?/1) do cache = ensure_loaded == (&Code.ensure_loaded?/1) Daraja.Runtime.http_client_module(ensure_loaded, cache) end end