A comprehensive Elixir client library for the MTN Mobile Money (MoMo) API. Easily integrate mobile money payments, transfers, and account management into your Elixir applications.
MTN Mobile Money is a digital financial service that allows users to store, send, and receive money using their mobile phones. This library provides a simple interface to:
- π³ Collections - Request payments and withdrawals from customers
- πΈ Disbursements - Send money and make deposits to recipients
- π€ Account Management - Get user info, validate accounts, check balances
- π Transaction Tracking - Monitor transaction status and history
- π Secure - Production-ready with proper authentication
- π Multi-environment - Supports both sandbox and production
This library now supports the complete MTN Mobile Money API including:
- Withdrawal Requests - Request money from customer accounts via Collections API
- Direct Deposits - Make instant deposits via Disbursements API
- User Information - Get basic user details for both Collections and Disbursements
- Account Validation - Check if accounts are active before transactions
- Default Parameters - MSISDN defaults for easier phone number handling
- Enhanced Error Handling - Proper Elixir error tuples instead of exceptions
Add momoapi_elixir to your list of dependencies in mix.exs:
def deps do
[
{:momoapi_elixir, "~> 0.1.1"}
]
endFor testing, generate sandbox credentials using the built-in Mix task:
mix provision YOUR_SUBSCRIPTION_KEY https://your-callback-url.comThis will output your sandbox user_id and api_key.
export MOMO_SUBSCRIPTION_KEY="your_subscription_key"
export MOMO_USER_ID="your_user_id"
export MOMO_API_KEY="your_api_key"
export MOMO_TARGET_ENVIRONMENT="sandbox" # or "production"# config/config.exs
config :momoapi_elixir,
subscription_key: "your_subscription_key",
user_id: "your_user_id",
api_key: "your_api_key",
target_environment: "sandbox"# Load configuration
{:ok, config} = MomoapiElixir.Config.from_env()
# Request a payment
payment = %{
amount: "1000",
currency: "UGX",
externalId: "payment_#{System.system_time()}",
payer: %{
partyIdType: "MSISDN",
partyId: "256784123456"
},
payerMessage: "Payment for goods",
payeeNote: "Thank you for your business"
}
case MomoapiElixir.Collections.request_to_pay(config, payment) do
{:ok, reference_id} ->
IO.puts("Payment initiated! Reference ID: #{reference_id}")
{:error, reason} ->
IO.puts("Payment failed: #{inspect(reason)}")
endRequest payments from customers, withdraw money, and manage account information.
payment = %{
amount: "5000", # Amount in string format
currency: "UGX", # ISO 4217 currency code
externalId: "unique_payment_id", # Your unique transaction ID
payer: %{
partyIdType: "MSISDN", # Phone number type
partyId: "256784123456" # Customer's phone number
},
payerMessage: "Payment description",
payeeNote: "Internal note"
}
{:ok, reference_id} = MomoapiElixir.Collections.request_to_pay(config, payment)Request withdrawal from a consumer's account (requires user authorization):
withdrawal = %{
amount: "1000",
currency: "UGX",
externalId: "withdrawal_#{System.system_time()}",
payer: %{
partyIdType: "MSISDN",
partyId: "256784123456"
},
payerMessage: "Cash withdrawal",
payeeNote: "ATM withdrawal"
}
{:ok, reference_id} = MomoapiElixir.Collections.request_to_withdraw(config, withdrawal)case MomoapiElixir.Collections.get_transaction_status(config, reference_id) do
{:ok, %{"status" => "SUCCESSFUL", "amount" => amount}} ->
IO.puts("Payment of #{amount} completed successfully!")
{:ok, %{"status" => "PENDING"}} ->
IO.puts("Payment is still processing...")
{:ok, %{"status" => "FAILED", "reason" => reason}} ->
IO.puts("Payment failed: #{reason}")
{:error, reason} ->
IO.puts("Error checking status: #{inspect(reason)}")
endGet basic user information for account validation:
# Using default MSISDN type
case MomoapiElixir.Collections.get_basic_user_info(config, "256784123456") do
{:ok, %{"given_name" => first_name, "family_name" => last_name}} ->
IO.puts("User: #{first_name} #{last_name}")
{:error, %{status_code: 404}} ->
IO.puts("User not found")
{:error, reason} ->
IO.puts("Error: #{inspect(reason)}")
end
# Explicitly specify account type
{:ok, user_info} = MomoapiElixir.Collections.get_basic_user_info(config, "EMAIL", "user@example.com")Check if an account holder is active before initiating transactions:
case MomoapiElixir.Collections.validate_account_holder_status(config, "256784123456") do
{:ok, %{"result" => true}} ->
IO.puts("Account is active and ready for transactions")
{:ok, %{"result" => false}} ->
IO.puts("Account is inactive")
{:error, reason} ->
IO.puts("Validation failed: #{inspect(reason)}")
endcase MomoapiElixir.Collections.get_balance(config) do
{:ok, %{"availableBalance" => balance, "currency" => currency}} ->
IO.puts("Available balance: #{balance} #{currency}")
{:error, reason} ->
IO.puts("Error getting balance: #{inspect(reason)}")
endSend money to recipients, make deposits, and manage account information.
Send money to recipients (requires authorization from the recipient):
transfer = %{
amount: "2500",
currency: "UGX",
externalId: "transfer_#{System.system_time()}",
payee: %{
partyIdType: "MSISDN",
partyId: "256784987654"
},
payerMessage: "Salary payment",
payeeNote: "Monthly salary"
}
case MomoapiElixir.Disbursements.transfer(config, transfer) do
{:ok, reference_id} ->
IO.puts("Transfer initiated! Reference ID: #{reference_id}")
{:error, reason} ->
IO.puts("Transfer failed: #{inspect(reason)}")
endDirect deposit into a recipient's account (no authorization required):
deposit = %{
amount: "1500",
currency: "UGX",
externalId: "deposit_#{System.system_time()}",
payee: %{
partyIdType: "MSISDN",
partyId: "256784987654"
},
payerMessage: "Bonus payment",
payeeNote: "Performance bonus"
}
case MomoapiElixir.Disbursements.deposit(config, deposit) do
{:ok, reference_id} ->
IO.puts("Deposit completed! Reference ID: #{reference_id}")
{:error, reason} ->
IO.puts("Deposit failed: #{inspect(reason)}")
endcase MomoapiElixir.Disbursements.get_transaction_status(config, reference_id) do
{:ok, %{"status" => "SUCCESSFUL", "amount" => amount}} ->
IO.puts("Transaction of #{amount} completed successfully!")
{:ok, %{"status" => "PENDING"}} ->
IO.puts("Transaction is still processing...")
{:error, reason} ->
IO.puts("Error checking status: #{inspect(reason)}")
endGet basic user information via the Disbursements API:
# Using default MSISDN type
case MomoapiElixir.Disbursements.get_basic_user_info(config, "256784987654") do
{:ok, %{"given_name" => first_name, "family_name" => last_name}} ->
IO.puts("Recipient: #{first_name} #{last_name}")
{:error, reason} ->
IO.puts("Error: #{inspect(reason)}")
endCheck if a recipient account is active before making transfers:
case MomoapiElixir.Disbursements.validate_account_holder_status(config, "256784987654") do
{:ok, %{"result" => true}} ->
IO.puts("Recipient account is active")
{:ok, %{"result" => false}} ->
IO.puts("Recipient account is inactive")
{:error, reason} ->
IO.puts("Validation failed: #{inspect(reason)}")
endcase MomoapiElixir.Disbursements.get_balance(config) do
{:ok, %{"availableBalance" => balance, "currency" => currency}} ->
IO.puts("Available balance: #{balance} #{currency}")
{:error, reason} ->
IO.puts("Error getting balance: #{inspect(reason)}")
endThe library automatically uses the correct API endpoints based on your environment:
- Sandbox:
https://sandbox.momodeveloper.mtn.com - Production:
https://momoapi.mtn.com
# All available configuration options
config = %{
subscription_key: "your_subscription_key", # Required
user_id: "your_user_id", # Required
api_key: "your_api_key", # Required
target_environment: "sandbox" # "sandbox" or "production"
}# From environment variables
{:ok, config} = MomoapiElixir.Config.from_env()
# From application config
{:ok, config} = MomoapiElixir.Config.from_app_config()
# Manual configuration
{:ok, config} = MomoapiElixir.Config.new(
"subscription_key",
"user_id",
"api_key",
"sandbox"
)The library uses consistent error handling patterns:
invalid_payment = %{
amount: "0", # Invalid: must be positive
currency: "INVALID", # Invalid: must be 3-letter ISO code
# missing required fields
}
case MomoapiElixir.Collections.request_to_pay(config, invalid_payment) do
{:ok, reference_id} ->
# Success
{:error, validation_errors} when is_list(validation_errors) ->
# Multiple validation errors
Enum.each(validation_errors, fn error ->
IO.puts("Field #{error.field}: #{error.message}")
end)
{:error, reason} ->
# Other errors (network, auth, etc.)
IO.puts("Error: #{inspect(reason)}")
endcase MomoapiElixir.Collections.request_to_pay(config, payment) do
{:ok, reference_id} ->
# Success case
{:error, validation_errors} when is_list(validation_errors) ->
# Validation failed - fix your request data
{:error, {:auth_failed, status_code, body}} ->
# Authentication failed - check credentials
{:error, {:http_error, reason}} ->
# Network error - retry or check connectivity
{:error, %{status_code: 500}} ->
# Server error - try again later
{:error, reason} ->
# Other errors
end
- Complete KYC requirements with MTN
- Get production credentials from MTN OVA dashboard
- Set environment variables:
export MOMO_SUBSCRIPTION_KEY="your_production_subscription_key"
export MOMO_USER_ID="your_production_user_id"
export MOMO_API_KEY="your_production_api_key"
export MOMO_TARGET_ENVIRONMENT="production"MIX_ENV=prod mix release
MIX_ENV=prod _build/prod/rel/your_app/bin/your_app startThe library will automatically use production endpoints when target_environment is set to "production".
This library includes comprehensive test coverage with proper mocking:
# Run tests
mix test
# Run with coverage
mix test --cover
# Run specific test file
mix test test/collections_test.exsWe welcome contributions! Please see our Contributing Guide for details.
git clone https://github.com/oryono/momoapi-elixir.git
cd momoapi-elixir
mix deps.get
mix testPlease report bugs and feature requests on our GitHub Issues page.
See CHANGELOG.md for version history and changes.
This project is licensed under the MIT License - see the LICENSE file for details.
- π Documentation: HexDocs
- π Issues: GitHub Issues
- π¬ Discussions: GitHub Discussions
- MTN Group for providing the Mobile Money API
- The Elixir community for excellent tooling and libraries
Made with β€οΈ for the Elixir community