Daraja.C2B.Callback (daraja v0.1.0)

Copy Markdown View Source

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

CodeMeaning
C2B00011Invalid MSISDN
C2B00012Invalid Account Number
C2B00013Invalid Amount
C2B00014Invalid KYC Details
C2B00015Invalid Short Code
C2B00016Other Error

Summary

Functions

Builds the JSON response body to accept a validation request.

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

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

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

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

Parses a C2B callback map from an untrusted HTTP request.

Parses a C2B confirmation callback from an untrusted HTTP request.

Parses a C2B validation callback from an untrusted HTTP request.

Builds the JSON response body to reject a validation request.

Functions

accept()

@spec accept() :: %{required(String.t()) => String.t()}

Builds the JSON response body to accept a validation request.

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

from_confirmation_map(map)

@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(map, atom)

@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(map)

@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(arg1)

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

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

parse(map)

@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(map)

@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(map)

@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(result_code)

@spec reject(String.t()) :: %{required(String.t()) => 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"}