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.
Features
- OAuth client credentials token flow
- M-PESA STK Push incoming payment prompts
- Payment status verification using the Kopo Kopo status URL
- HMAC callback signature validation
- Sandbox-first defaults for safer local development
- Mix tasks for credential checks and test payment prompts
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
| Option | Required | Description |
|---|---|---|
:base_url | No | Kopo Kopo API base URL. Defaults to https://sandbox.kopokopo.com. |
:client_id | Yes | OAuth client ID from Kopo Kopo. |
:client_secret | Yes | OAuth client secret from Kopo Kopo. |
:api_key | Yes | API key used to validate callback signatures. |
:till_number | Yes | Kopo Kopo till number receiving the payment. |
:callback_url | No | Full callback URL sent to Kopo Kopo. |
:site_url | No | Base site URL used when :callback_url is not configured. |
:callback_path | No | Callback path used with :site_url. Defaults to /api/payments/kopokopo/callback. |
:user_agent | No | User 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:
| Field | Description |
|---|---|
:reference | Your internal order, invoice, or payment reference. |
:amount | Amount in KES. Strings, integers, and floats are accepted. |
:phone | Customer phone number. Local 07... and 254... numbers are normalized. |
:email | Customer email address. |
:name | Customer 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:
{:ok, data}for successful payments{:pending, data}forPending,Received, orInitiatedstatuses{:error, reason}for failed or unexpected responses
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
- Keep
KOPOKOPO_CLIENT_SECRETandKOPOKOPO_API_KEYout of source control. - Validate every callback with
KopoKopo.valid_signature?/2. - Use sandbox credentials while developing and testing.
- Double-check
:base_urlbefore runningmix kopokopo.test_payment.
License
KopoKopo is released under the MIT License.