Current section
Files
Jump to
Current section
Files
lib/ex_mpesa/generate_session_key.ex
defmodule ElixirMpesa.GenerateSessionKey do
alias ElixirMpesa.HttpRequest
@moduledoc """
Handles the session key generation and management for M-Pesa OpenAPI.
Before integrating with the M-Pesa OpenAPI solution, you must exchange your
Application Key for a Session Key. The API Key is created with the creation of a new application.
The Session Key acts as an access token that authorizes the rest of your REST API calls to the system.
A valid Session Key is needed to transact on M-Pesa using OpenAPI.
## Process Flow
1. Encrypt API key using the public key provided by M-Pesa
2. Send the encrypted API key to the Session API endpoint
3. Receive a session key in response
4. Encrypt the session key for use in subsequent API calls
## Example
# Encrypt the API key
{:ok, encrypted_api_key} = ElixirMpesa.GenerateSessionKey.encrypt_api_key()
# Generate a session ID using the encrypted API key
{:ok, session_data} = ElixirMpesa.GenerateSessionKey.generate_output_session_id(encrypted_api_key)
session_key = session_data["output_SessionID"]
# Encrypt the session key for use in transactions
{:ok, encrypted_session_key} = ElixirMpesa.GenerateSessionKey.encrypt_session_id(session_key)
"""
@doc """
Encrypts the API key using the configured public key.
This function takes the API key and encrypts it using the RSA public key provided by M-Pesa.
The encrypted API key is then used to obtain a session key.
## Options
* `:public_key` - Override the configured public key
* `:api_key` - Override the configured API key
* `:api_type` - Override the configured API type ("sandbox" or "openapi")
* `:url_context` - Override the configured URL context
## Returns
* `{:ok, encrypted_api_key}` - The successfully encrypted API key
* `{:error, reason}` - If encryption fails
## Examples
iex> ElixirMpesa.GenerateSessionKey.encrypt_api_key()
{:ok, "encrypted_api_key_string"}
iex> ElixirMpesa.GenerateSessionKey.encrypt_api_key([public_key: "custom_public_key"])
{:ok, "encrypted_api_key_string"}
"""
def encrypt_api_key(options \\ []) do
{public_key, api_key, _api_type, _url_context} = extract_options(options)
encrypt(public_key, api_key)
end
@doc """
Encrypts a session ID using the configured public key.
After obtaining a session ID from the M-Pesa API, it needs to be encrypted
before it can be used in subsequent API calls.
## Parameters
* `session_id` - The session ID to encrypt
* `options` - A keyword list of options (see below)
## Options
* `:public_key` - Override the configured public key
## Returns
* `{:ok, encrypted_session_id}` - The successfully encrypted session ID
* `{:error, reason}` - If encryption fails
## Examples
iex> ElixirMpesa.GenerateSessionKey.encrypt_session_id("session_id_string")
{:ok, "encrypted_session_id_string"}
"""
def encrypt_session_id(session_id, options \\ []) do
{public_key, _api_key, _api_type, _url_context} = extract_options(options)
encrypt(public_key, session_id)
end
@doc """
Generates a session ID by sending the encrypted API key to the M-Pesa API.
## Parameters
* `encrypted_api_key` - The encrypted API key (optional, will use the configured key if nil)
* `options` - A keyword list of options (see below)
## Options
* `:api_type` - Override the configured API type ("sandbox" or "openapi")
* `:url_context` - Override the configured URL context
## Returns
* `{:ok, response}` - The API response containing the session ID
* `{:error, reason}` - If the API call fails
## Examples
iex> {:ok, encrypted_key} = ElixirMpesa.GenerateSessionKey.encrypt_api_key()
iex> ElixirMpesa.GenerateSessionKey.generate_output_session_id(encrypted_key)
{:ok, %{"output_ResponseCode" => "INS-0", "output_ResponseDesc" => "Request processed successfully", "output_SessionID" => "session_id_string"}}
"""
def generate_output_session_id(encrypted_api_key, options \\ []) do
{_public_key, _api_key, api_type, url_context} = extract_options(options)
auth(api_type, url_context, encrypted_api_key || encrypt_api_key())
end
@doc false
defp encrypt(public_key_b64, api_key) when is_binary(public_key_b64) and is_binary(api_key) do
try do
# Decode Base64 public key
{:ok, key_der} = Base.decode64(public_key_b64)
# Decode SubjectPublicKeyInfo (DER)
{:SubjectPublicKeyInfo, _, rsa_key_bin} = :public_key.der_decode(:SubjectPublicKeyInfo, key_der)
# Decode the raw RSA public key
{:"RSAPublicKey", modulus, exponent} = :public_key.der_decode(:RSAPublicKey, rsa_key_bin)
# Encrypt using :public_key.encrypt_public
encrypted = :public_key.encrypt_public(api_key, {:"RSAPublicKey", modulus, exponent}, [:rsa_pkcs1_padding])
{:ok, Base.encode64(encrypted)}
rescue
e in RuntimeError ->
{:error, "Encryption failed: #{Exception.message(e)}"}
e ->
{:error, "Encryption failed: #{inspect(e)}"}
end
end
@doc false
defp auth(context, url_context, {:ok, encrypt_api_key}) do
uri = "https://openapi.m-pesa.com/#{context}/ipg/v2/#{url_context}/getSession/"
HttpRequest.get(uri, %{}, HttpRequest.header(encrypt_api_key))
|> HttpRequest.handle_response()
end
@doc false
defp auth(_context, _url_context, {:error, message}), do: {:error, message}
@doc false
defp auth(context, url_context, encrypt_api_key) do
uri = "https://openapi.m-pesa.com/#{context}/ipg/v2/#{url_context}/getSession/"
HttpRequest.get(uri, %{}, HttpRequest.header(encrypt_api_key))
|> HttpRequest.handle_response()
end
@doc false
defp extract_options(options \\ []) do
public_key = Keyword.get(options, :public_key, Application.get_env(:elixir_mpesa, :public_key))
api_key = Keyword.get(options, :api_key, Application.get_env(:elixir_mpesa, :api_key))
api_type = Keyword.get(options, :api_type, Application.get_env(:elixir_mpesa, :api_type))
url_context = Keyword.get(options, :url_context, Application.get_env(:elixir_mpesa, :url_context))
{public_key, api_key, api_type, url_context}
end
end