# 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).
