KopoKopo

An Elixir client for Kopo Kopo payments.

KopoKopo wraps the payment flows most Elixir and Phoenix applications need when working with Kopo Kopo: OAuth client credentials, M-PESA STK Push requests, payment status checks, and callback signature validation.

ElixirLicense

Features

Installation

Add :kopokopo to your dependencies:

def deps do
[
{:kopokopo, "~> 0.1.0"}
]
end

Then fetch dependencies:

mix deps.get

Configuration

Configure the client in your application config:

config :kopokopo,
base_url: "https://sandbox.kopokopo.com",
client_id: System.fetch_env!("KOPOKOPO_CLIENT_ID"),
client_secret: System.fetch_env!("KOPOKOPO_CLIENT_SECRET"),
api_key: System.fetch_env!("KOPOKOPO_API_KEY"),
till_number: System.fetch_env!("KOPOKOPO_TILL_NUMBER"),
callback_url: "https://example.com/api/payments/kopokopo/callback",
user_agent: "my_app/1.0"

For production, point :base_url at the live Kopo Kopo API:

config :kopokopo, base_url: "https://api.kopokopo.com"

Configuration Options

OptionRequiredDescription
:base_urlNoKopo Kopo API base URL. Defaults to https://sandbox.kopokopo.com.
:client_idYesOAuth client ID from Kopo Kopo.
:client_secretYesOAuth client secret from Kopo Kopo.
:api_keyYesAPI key used to validate callback signatures.
:till_numberYesKopo Kopo till number receiving the payment.
:callback_urlNoFull callback URL sent to Kopo Kopo.
:site_urlNoBase site URL used when :callback_url is not configured.
:callback_pathNoCallback path used with :site_url. Defaults to /api/payments/kopokopo/callback.
:user_agentNoUser agent sent with API requests. Defaults to kopokopo/0.1.0.

If :callback_url is not configured, the client builds one from :site_url and :callback_path.

Usage

Initiate an STK Push Payment

{:ok, %{location: location, reference: reference}} =
KopoKopo.initiate_payment(%{
reference: "ORDER-123",
amount: "1500.00",
phone: "0712345678",
email: "customer@example.com",
name: "Jane Customer"
})

The returned location is the Kopo Kopo payment status URL. Store it with your order or payment record so you can verify the payment later.

Accepted payment fields:

FieldDescription
:referenceYour internal order, invoice, or payment reference.
:amountAmount in KES. Strings, integers, and floats are accepted.
:phoneCustomer phone number. Local 07... and 254... numbers are normalized.
:emailCustomer email address.
:nameCustomer name. Used to populate subscriber first and last names.

Verify Payment Status

case KopoKopo.verify(location) do
{:ok, data} ->
# Payment succeeded.
{:pending, data} ->
# Payment is still being processed.
{:error, reason} ->
# Payment failed or the status response was unexpected.
end

verify/1 normalizes Kopo Kopo status responses into three outcomes:

Validate Callback Signatures

Kopo Kopo callbacks should be validated against the raw request body and the x-kopokopo-signature header:

signature =
conn
|> Plug.Conn.get_req_header("x-kopokopo-signature")
|> List.first()
if KopoKopo.valid_signature?(raw_body, signature) do
# Process the callback.
else
# Reject the callback.
end

Use the raw, unmodified request body for signature validation. If your Phoenix application parses the body before your callback action runs, capture the raw body with a Plug parser body reader.

Inspect a Prompt Payload

For local debugging, you can inspect the outgoing STK Push payload without making a network request:

KopoKopo.debug_prompt_payload("0712345678", "42.50")

This is useful when confirming your till number, callback URL, phone normalization, amount formatting, and request body before sending real prompts.

Mix Tasks

Check whether your configured OAuth credentials can obtain an access token:

mix kopokopo.auth_check

Send a small STK Push test prompt:

mix kopokopo.test_payment
mix kopokopo.test_payment 0740769596 10

The test prompt uses the configured environment. If :base_url points to production, it may trigger a real M-PESA prompt.

Error Handling

Most public functions return tagged tuples:

{:ok, result}
{:pending, result}
{:error, reason}

When the error comes from an HTTP response, reason may be a %KopoKopo{} struct containing the response status, URL, and normalized body. Use KopoKopo.error_text/1 when you need displayable text:

case KopoKopo.initiate_payment(params) do
{:ok, payment} -> payment
{:error, reason} -> KopoKopo.error_text(reason)
end

Development

Clone the repository, fetch dependencies, and run tests:

mix deps.get
mix test

Generate documentation locally:

mix docs

Format code before submitting changes:

mix format

Project Structure

lib/kopokopo.ex # Public client API
lib/mix/tasks/kopokopo.auth_check.ex # OAuth credential check task
lib/mix/tasks/kopokopo.test_payment.ex # STK Push test task
test/kopo_kopo_test.exs # Client behavior tests

Security Notes

License

KopoKopo is released under the MIT License.