# `Daraja.C2B.Callback`
[🔗](https://github.com/okothkongo/daraja/blob/v0.1.1/lib/daraja/c2b/callback.ex#L1)

Helpers for parsing inbound C2B callback payloads posted by M-PESA to
the merchant's registered validation and confirmation URLs.

Daraja does **not** sign callbacks. Verify requests with
`Daraja.Callback.Security`, deduplicate on `trans_id` with
`Daraja.Callback.Guard`, and use `parse_validation/1` or
`parse_confirmation/1` on untrusted input from the matching route.

## Validation flow

When External Validation is enabled, M-PESA posts a validation request to
your `ValidationURL` before completing a transaction. You must respond within
~8 seconds using `accept/0` or `reject/1`.

    with :ok <- Daraja.Callback.Security.verify(ip: conn.remote_ip, check_ip: true, ...),
         {:ok, callback} <- Daraja.C2B.Callback.parse_validation(payload) do
      response =
        if valid_account?(callback.bill_ref_number) do
          Daraja.C2B.Callback.accept()
        else
          Daraja.C2B.Callback.reject("C2B00012")
        end

      json(conn, response)
    end

## Confirmation flow

After a successful transaction, M-PESA posts a confirmation to your
`ConfirmationURL`. No response action is required — M-PESA has already
completed the transaction.

## Result codes for rejection

| Code       | Meaning                  |
|------------|--------------------------|
| `C2B00011` | Invalid MSISDN           |
| `C2B00012` | Invalid Account Number   |
| `C2B00013` | Invalid Amount           |
| `C2B00014` | Invalid KYC Details      |
| `C2B00015` | Invalid Short Code       |
| `C2B00016` | Other Error              |

# `accept`

```elixir
@spec accept() :: %{required(String.t()) =&gt; String.t()}
```

Builds the JSON response body to accept a validation request.

    Daraja.C2B.Callback.accept()
    #=> %{"ResultCode" => "0", "ResultDesc" => "Accepted"}

# `from_confirmation_map`

```elixir
@spec from_confirmation_map(map()) :: Daraja.C2B.Callback.Confirmation.t()
```

Parses a C2B confirmation callback map into a `%Confirmation{}` struct.

Use on your `ConfirmationURL` route.

# `from_map`

```elixir
@spec from_map(map(), :validation | :confirmation) ::
  Daraja.C2B.Callback.Validation.t() | Daraja.C2B.Callback.Confirmation.t()
```

Parses a raw callback map into a `%Validation{}` or `%Confirmation{}` struct.

Pass `:validation` or `:confirmation` to match the HTTP route that received
the callback. Do not infer message type from payload fields such as
`OrgAccountBalance`.

# `from_validation_map`

```elixir
@spec from_validation_map(map()) :: Daraja.C2B.Callback.Validation.t()
```

Parses a C2B validation callback map into a `%Validation{}` struct.

Use on your `ValidationURL` route; do not infer validation vs confirmation
from payload fields alone.

# `kind`

```elixir
@spec kind(Daraja.C2B.Callback.Validation.t() | Daraja.C2B.Callback.Confirmation.t()) ::
  :validation | :confirmation
```

Returns `:validation` or `:confirmation` for a parsed C2B callback.

# `parse`

```elixir
@spec parse(map()) ::
  {:error, :ambiguous_callback, String.t()}
  | {:error, :invalid_callback, String.t()}
```

Parses a C2B callback map from an untrusted HTTP request.

Returns `{:error, :ambiguous_callback, reason}` because validation and
confirmation payloads share the same shape. Use `parse_validation/1` on your
validation URL and `parse_confirmation/1` on your confirmation URL instead.

# `parse_confirmation`

```elixir
@spec parse_confirmation(map()) ::
  {:ok, Daraja.C2B.Callback.Confirmation.t()}
  | {:error, :invalid_callback, String.t()}
```

Parses a C2B confirmation callback from an untrusted HTTP request.

Call from your `ConfirmationURL` route handler.

# `parse_validation`

```elixir
@spec parse_validation(map()) ::
  {:ok, Daraja.C2B.Callback.Validation.t()}
  | {:error, :invalid_callback, String.t()}
```

Parses a C2B validation callback from an untrusted HTTP request.

Call from your `ValidationURL` route handler.

# `reject`

```elixir
@spec reject(String.t()) :: %{required(String.t()) =&gt; String.t()}
```

Builds the JSON response body to reject a validation request.

`result_code` must be one of: `"C2B00011"`, `"C2B00012"`, `"C2B00013"`,
`"C2B00014"`, `"C2B00015"`, `"C2B00016"`. Defaults to `"C2B00016"` ("Other
Error") if an unrecognised code is supplied.

    Daraja.C2B.Callback.reject("C2B00012")
    #=> %{"ResultCode" => "C2B00012", "ResultDesc" => "Invalid Account Number"}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
