An Elixir SDK for Airtel Money APIs, providing a clean and idiomatic interface for collections, disbursements, transaction queries, and webhooks.
Features
- Collections - Receive payments from customers (USSD Push)
- Disbursements - Send payments to customers with PIN encryption
- Transfer Status - Check disbursement transfer status
- Transaction Status - Query collection transaction status
- Balance Queries - Check account balance
- OAuth Token Management - Automatic token handling and refresh
- PIN Encryption - RSA encryption for disbursement PINs
- MSISDN Validation - Phone number format validation
- Webhook Verification - HMAC SHA256 signature verification
- Telemetry - Built-in telemetry events for monitoring
- OTP Supervision - Robust supervision tree for production use
- Sandbox & Production - Support for both environments
Installation
Add airtel_money to your list of dependencies in mix.exs:
def deps do
[
{:airtel_money, "~> 0.1.0"}
]
endRun:
mix deps.get
Configuration
Configure the SDK in your config/config.exs:
config :airtel_money,
client_id: "your_client_id",
client_secret: "your_client_secret",
country: "CD",
currency: "CDF",
environment: :sandbox,
webhook_secret: "your_webhook_secret" # Optional, for webhook verificationConfiguration Options
:client_id(required) - Your Airtel Money client ID:client_secret(required) - Your Airtel Money client secret:country(required) - Country code (e.g., "CD" for Democratic Republic of Congo):currency(required) - Currency code (e.g., "CDF" for Congolese Franc):environment(optional) -:sandboxor:production(default::sandbox):host(optional) - Custom API host (overrides default):timeout(optional) - HTTP request timeout in milliseconds (default: 15000):pool_size(optional) - Connection pool size (default: 10):webhook_secret(optional) - Webhook signature secret for verification:rsa_public_key(optional) - RSA public key for PIN encryption (required for disbursements in production)
Usage
Start the Application
The SDK uses OTP supervision. Ensure the application is started:
# In your application.ex
children = [
AirtelMoney.Application
]Collect a Payment
case AirtelMoney.collect(%{
amount: "1000",
msisdn: "2439xxxxxxx",
reference: "INV-001"
}) do
{:ok, result} ->
IO.inspect(result)
{:error, %AirtelMoney.Error{message: message}} ->
IO.puts("Error: #{message}")
endDisburse a Payment
# For production, you need to encrypt the PIN first
case AirtelMoney.Encryption.encrypt_pin("1234") do
{:ok, encrypted_pin} ->
case AirtelMoney.disburse(%{
amount: "5000",
msisdn: "2439xxxxxxx",
reference: "PAY-001",
pin: encrypted_pin
}) do
{:ok, result} ->
IO.inspect(result)
{:error, %AirtelMoney.Error{message: message}} ->
IO.puts("Error: #{message}")
end
{:error, reason} ->
IO.puts("PIN encryption failed: #{reason}")
endQuery Transaction Status
case AirtelMoney.transaction_status("TXN123") do
{:ok, status} ->
IO.inspect(status)
{:error, %AirtelMoney.Error{message: message}} ->
IO.puts("Error: #{message}")
endQuery Balance
case AirtelMoney.balance() do
{:ok, balance} ->
IO.inspect(balance)
{:error, %AirtelMoney.Error{message: message}} ->
IO.puts("Error: #{message}")
endValidate MSISDN
case AirtelMoney.Utils.validate_msisdn("243900000000") do
:ok ->
IO.puts("Valid MSISDN")
{:error, reason} ->
IO.puts("Invalid MSISDN: #{reason}")
endFetch RSA Public Key
case AirtelMoney.Encryption.fetch_public_key() do
{:ok, public_key} ->
IO.puts("Public key fetched successfully")
# Store this key in your config for future use
{:error, error} ->
IO.puts("Failed to fetch public key: #{error.message}")
endWebhooks
Verify Webhook Signature
# In your webhook controller
def handle(conn, params) do
signature = get_req_header(conn, "x-airtel-signature")
payload = conn.assigns[:raw_body]
case AirtelMoney.verify_webhook(payload, signature) do
:ok ->
# Signature is valid, process webhook
{:ok, webhook_data} = AirtelMoney.parse_webhook(payload)
# Handle webhook_data
send_resp(conn, 200, "OK")
{:error, :invalid_signature} ->
send_resp(conn, 401, "Invalid signature")
end
endUsing the Plug (Phoenix)
Add the plug to your router:
pipeline :webhooks do
plug AirtelMoney.WebhookPlug
end
scope "/webhooks" do
pipe_through :webhooks
post "/airtel", WebhookController, :handle
endThe plug will:
- Verify the webhook signature
- Parse the JSON payload
- Assign the parsed data to
conn.assigns[:airtel_webhook] - Return 401 if verification fails
Telemetry
The SDK emits telemetry events for monitoring:
[:airtel_money, :success]- Successful API request[:airtel_money, :failure]- Failed API request
Attach handlers to monitor events:
:telemetry.attach(
"airtel-money-handler",
[:airtel_money, :success],
&handle_event/4,
nil
)
def handle_event([:airtel_money, event], measurements, metadata, _config) do
IO.puts("Event: #{event}, Duration: #{measurements.duration}ms")
endError Handling
All API functions return {:ok, result} or {:error, %AirtelMoney.Error{}}.
%AirtelMoney.Error{
code: "ERR001",
message: "Invalid request",
status: 400
}Testing
Run tests:
mix test
Run tests with coverage:
mix test.ci
Development
Linting
mix lint
Setup
mix setup
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License.
Documentation
Full documentation is available at HexDocs.