Packages

A small Elixir client for Kopo Kopo OAuth, M-PESA STK Push prompts, payment verification, and callback signature validation.

Current section

Files

Jump to
kopokopo README.md
Raw

README.md

# KopoKopo
An Elixir client for [Kopo Kopo](https://developers.kopokopo.com/) 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.
[![Elixir](https://img.shields.io/badge/Elixir-1.14%2B-4B275F?style=flat-square&logo=elixir&logoColor=white)](https://elixir-lang.org/)
[![License](https://img.shields.io/badge/License-MIT-black?style=flat-square)](LICENSE)
## 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:
```elixir
def deps do
[
{:kopokopo, "~> 0.1.0"}
]
end
```
Then fetch dependencies:
```bash
mix deps.get
```
## Configuration
Configure the client in your application config:
```elixir
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:
```elixir
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
```elixir
{: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
```elixir
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}` for `Pending`, `Received`, or `Initiated` statuses
- `{: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:
```elixir
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:
```elixir
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:
```bash
mix kopokopo.auth_check
```
Send a small STK Push test prompt:
```bash
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:
```elixir
{: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:
```elixir
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:
```bash
mix deps.get
mix test
```
Generate documentation locally:
```bash
mix docs
```
Format code before submitting changes:
```bash
mix format
```
## Project Structure
```text
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_SECRET` and `KOPOKOPO_API_KEY` out of source control.
- Validate every callback with `KopoKopo.valid_signature?/2`.
- Use sandbox credentials while developing and testing.
- Double-check `:base_url` before running `mix kopokopo.test_payment`.
## License
KopoKopo is released under the [MIT License](LICENSE).