# `MPP.Challenge`
[🔗](https://github.com/ZenHive/mpp/blob/v0.10.0/lib/mpp/challenge.ex#L1)

Payment challenge — the 402 response that tells a client what to pay.

A challenge is returned in the `WWW-Authenticate: Payment` header when a
request lacks a valid payment credential. The challenge ID is HMAC-SHA256
bound to all challenge parameters, making it tamper-proof without requiring
server-side state.

## HMAC Binding

The challenge ID is computed as:

    base64url(HMAC-SHA256(secret_key, realm|method|intent|request|expires|digest|opaque))

Seven fixed positional slots joined by `|`. Optional fields use empty string
when absent, preserving slot positions. The `request` and `opaque` fields are
used as their raw base64url-encoded strings (never re-serialized).

## Fields

  * `id` — HMAC-SHA256 challenge ID (computed by `create/2`)
  * `realm` — server protection space (e.g., `"api.example.com"`)
  * `method` — payment method name (e.g., `"stripe"`, `"tempo"`)
  * `intent` — intent type (e.g., `"charge"`)
  * `request` — base64url-encoded JSON request payload (pre-encoded)
  * `description` — (optional) human-readable description
  * `digest` — (optional) content digest per RFC 9530
  * `expires` — (optional) RFC 3339 expiration timestamp
  * `opaque` — (optional) base64url-encoded JSON server correlation data

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `valid_method_name?` | 1 | Check whether a string is a valid MPP payment-method name per the spec ABNF `payment-method-id = 1*LOWERALPHA` (non-empty, lowercase ASCII letters only). | `method: value` |
| `validate_fields` | 1 | Validate the field shapes of a parsed challenge (id non-empty, method `1*LOWERALPHA`, request base64url-JSON object, digest `sha-256=…`). Returns distinct error atoms so a malformed field is rejected at parse time rather than deferring to a downstream mismatch. | `challenge: value` |
| `verify_server_binding` | 3 | Verify challenge HMAC using the server's configured realm (Tier-1 server binding). Recomputes the ID with `server_realm` instead of the echoed realm, matching mpp-rs `verify_hmac_and_expiry`. | `challenge: value`, `secret_key: value`, `server_realm: value` |
| `verify` | 2 | Verify a challenge's HMAC-bound ID against a secret key using constant-time comparison. | `challenge: value`, `secret_key: value` |
| `create` | 2 | Create a new challenge with an HMAC-SHA256 bound ID. | `params: value`, `secret_key: value` |

# `t`

```elixir
@type t() :: %MPP.Challenge{
  description: String.t() | nil,
  digest: String.t() | nil,
  expires: String.t() | nil,
  id: String.t() | nil,
  intent: String.t(),
  method: String.t(),
  opaque: String.t() | nil,
  realm: String.t(),
  request: String.t()
}
```

# `create`

```elixir
@spec create(
  keyword(),
  String.t()
) :: t()
```

Create a new challenge with an HMAC-SHA256 bound ID.

## Parameters

  * `params` - Keyword list with `:realm`, `:method`, `:intent`, `:request` (required) and `:description`, `:digest`, `:expires`, `:opaque` (optional) (value)
  * `secret_key` - HMAC-SHA256 secret key for challenge binding (value)

## Returns

Challenge struct with computed `id` (`struct`)

## Composes With

  * `verify`

```elixir
# descripex:contract
%{
  params: %{
    params: %{
      description: "Keyword list with `:realm`, `:method`, `:intent`, `:request` (required) and `:description`, `:digest`, `:expires`, `:opaque` (optional)",
      kind: :value
    },
    secret_key: %{
      description: "HMAC-SHA256 secret key for challenge binding",
      kind: :value
    }
  },
  returns: %{type: :struct, description: "Challenge struct with computed `id`"},
  composes_with: [:verify]
}
```

# `valid_method_name?`

```elixir
@spec valid_method_name?(String.t()) :: boolean()
```

Check whether a string is a valid MPP payment-method name per the spec ABNF `payment-method-id = 1*LOWERALPHA` (non-empty, lowercase ASCII letters only).

## Parameters

  * `method` - Candidate payment-method name (value)

## Returns

true when the name conforms to the spec ABNF (`boolean`)

```elixir
# descripex:contract
%{
  params: %{
    method: %{description: "Candidate payment-method name", kind: :value}
  },
  returns: %{
    type: :boolean,
    description: "true when the name conforms to the spec ABNF"
  }
}
```

# `validate_fields`

```elixir
@spec validate_fields(t()) ::
  :ok
  | {:error, :empty_id | :invalid_method | :invalid_request | :invalid_digest}
```

Validate the field shapes of a parsed challenge (id non-empty, method `1*LOWERALPHA`, request base64url-JSON object, digest `sha-256=…`). Returns distinct error atoms so a malformed field is rejected at parse time rather than deferring to a downstream mismatch.

## Parameters

  * `challenge` - Challenge struct built from a parsed WWW-Authenticate header or echoed credential (value)

## Returns

`:ok` if all fields are well-formed, `{:error, reason}` otherwise (`tagged`)

## Errors

  * `:empty_id`
  * `:invalid_method`
  * `:invalid_request`
  * `:invalid_digest`

## Composes With

  * `create`
  * `verify`

```elixir
# descripex:contract
%{
  params: %{
    challenge: %{
      description: "Challenge struct built from a parsed WWW-Authenticate header or echoed credential",
      kind: :value
    }
  },
  errors: [:empty_id, :invalid_method, :invalid_request, :invalid_digest],
  returns: %{
    type: :tagged,
    description: "`:ok` if all fields are well-formed, `{:error, reason}` otherwise"
  },
  composes_with: [:create, :verify]
}
```

# `verify`

```elixir
@spec verify(t(), String.t()) :: :ok | {:error, :invalid_challenge}
@spec verify(t(), String.t()) :: {:error, :invalid_challenge}
```

Verify a challenge's HMAC-bound ID against a secret key using constant-time comparison.

## Parameters

  * `challenge` - Challenge struct to verify (value)
  * `secret_key` - HMAC-SHA256 secret key used when challenge was created (value)

## Returns

`:ok` if valid, `{:error, :invalid_challenge}` if tampered (`tagged`)

## Errors

  * `:invalid_challenge`

## Composes With

  * `create`

```elixir
# descripex:contract
%{
  params: %{
    secret_key: %{
      description: "HMAC-SHA256 secret key used when challenge was created",
      kind: :value
    },
    challenge: %{description: "Challenge struct to verify", kind: :value}
  },
  errors: [:invalid_challenge],
  returns: %{
    type: :tagged,
    description: "`:ok` if valid, `{:error, :invalid_challenge}` if tampered"
  },
  composes_with: [:create]
}
```

# `verify_server_binding`

```elixir
@spec verify_server_binding(t(), String.t(), String.t()) ::
  :ok | {:error, :invalid_challenge}
@spec verify_server_binding(t(), String.t(), String.t()) ::
  {:error, :invalid_challenge}
```

Verify challenge HMAC using the server's configured realm (Tier-1 server binding). Recomputes the ID with `server_realm` instead of the echoed realm, matching mpp-rs `verify_hmac_and_expiry`.

## Parameters

  * `challenge` - Challenge struct with echoed fields (value)
  * `secret_key` - HMAC-SHA256 secret key (value)
  * `server_realm` - Server's configured protection space realm (value)

## Returns

`:ok` if valid, `{:error, :invalid_challenge}` if tampered (`tagged`)

## Errors

  * `:invalid_challenge`

## Composes With

  * `create`
  * `verify`

```elixir
# descripex:contract
%{
  params: %{
    secret_key: %{description: "HMAC-SHA256 secret key", kind: :value},
    challenge: %{
      description: "Challenge struct with echoed fields",
      kind: :value
    },
    server_realm: %{
      description: "Server's configured protection space realm",
      kind: :value
    }
  },
  errors: [:invalid_challenge],
  returns: %{
    type: :tagged,
    description: "`:ok` if valid, `{:error, :invalid_challenge}` if tampered"
  },
  composes_with: [:create, :verify]
}
```

---

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