# Lithic

Production-grade Elixir client for the [Lithic API](https://docs.lithic.com) — card issuing, fintech infrastructure, and embedded finance.

[![Hex.pm](https://img.shields.io/hexpm/v/lithic.svg)](https://hex.pm/packages/lithic)
[![Docs](https://img.shields.io/badge/hex-docs-blue)](https://hexdocs.pm/lithic)

## Features

- **Complete API coverage** — all 150+ endpoints across Cards, Payments, ACH, Tokenization, 3DS, Auth Rules, Events, Credit, Transaction Monitoring, and more
- **Cursor-based pagination** — `Page.stream/1` for lazy enumeration across all list endpoints
- **Automatic retries** — exponential backoff with jitter on 5xx and network errors
- **Idempotency keys** — auto-generated on every mutating request
- **Webhook verification** — HMAC-SHA256 for all webhook types (Events, ASA, Tokenization Decisioning, 3DS Decisioning)
- **Telemetry** — `[:lithic, :request_start/stop/exception]` events with duration
- **Zero dependencies** — only `req`, `jason`, and `telemetry`
- **Dialyzer + Credo** — full typespec coverage and strict linting

## Installation

```elixir
def deps do
  [
    {:lithic, "~> 1.0"}
  ]
end
```

## Configuration

```elixir
# config/config.exs
config :lithic,
  api_key: System.get_env("LITHIC_API_KEY"),
  environment: :production   # :production | :sandbox
```

Or pass a client per-request:

```elixir
client = Lithic.Client.new(api_key: "my_key", environment: :sandbox)
{:ok, card} = Lithic.Cards.create(%{type: "VIRTUAL"}, client: client)
```

## Quick Start

```elixir
# Create a virtual card
{:ok, card} = Lithic.Cards.create(%{type: "VIRTUAL"})

# Pause it
{:ok, _} = Lithic.Cards.update(card["token"], %{state: "PAUSED"})

# List cards (paginated)
{:ok, page} = Lithic.Cards.list(page_size: 25, state: "OPEN")
page.data      #=> [%{"token" => ..., "type" => "VIRTUAL", ...}, ...]
page.has_more  #=> true

# Lazily stream all cards
Lithic.Cards.stream()
|> Stream.filter(&(&1["state"] == "OPEN"))
|> Enum.take(100)

# Collect all (use carefully on large datasets)
{:ok, all} = Lithic.Cards.list() |> then(fn {:ok, p} -> Lithic.Page.collect_all(p) end)
```

## Payments (ACH)

```elixir
# Link an external bank account via micro-deposit
{:ok, eba} = Lithic.ExternalBankAccounts.create(%{
  routing_number: "021000021",
  account_number: "1234567890",
  account_type: "CHECKING",
  owner: "Jane Doe",
  owner_type: "INDIVIDUAL",
  verification_method: "MICRO_DEPOSIT"
})

# After deposits arrive (1-3 business days):
{:ok, _} = Lithic.ExternalBankAccounts.verify(eba["token"], %{micro_deposits: [12, 34]})

# Send a payment
{:ok, payment} = Lithic.Payments.create(%{
  financial_account_token: "fa_token",
  external_bank_account_token: eba["token"],
  amount: 5000,        # $50.00
  direction: "DEBIT",
  method: "ACH_NEXT_DAY",
  method_attributes: %{sec_code: "PPD"},
  type: "PAYMENT"
})
```

## Sandbox Simulation

```elixir
# Full transaction lifecycle
{:ok, txn} = Lithic.Transactions.simulate_authorization(%{
  card_token: "card_token",
  amount: 1000,
  descriptor: "STARBUCKS",
  mcc: "5812"
})

{:ok, _} = Lithic.Transactions.simulate_clearing(txn["token"])
# or
{:ok, _} = Lithic.Transactions.simulate_void(txn["token"])
```

## Auth Rules (V2)

```elixir
# Create a velocity limit rule
{:ok, rule} = Lithic.AuthRules.create(%{
  name: "Daily spend limit",
  parameters: %{scope: "CARD", limits: [%{limit: 100_00, period: "DAY"}]}
})

# Draft → backtest → promote
{:ok, _} = Lithic.AuthRules.draft(rule["token"], %{parameters: updated_params})
{:ok, bt} = Lithic.AuthRules.request_backtest(rule["token"], %{
  start: "2024-01-01T00:00:00Z",
  end: "2024-03-01T00:00:00Z"
})
{:ok, _} = Lithic.AuthRules.promote(rule["token"])
```

## Webhook Verification

```elixir
# In your Phoenix controller:
def handle(conn, _params) do
  raw_body    = conn.assigns.raw_body
  signature   = get_req_header(conn, "webhook-signature") |> List.first()
  timestamp   = get_req_header(conn, "webhook-timestamp") |> List.first()
  {:ok, sub}  = Lithic.Events.get_subscription_secret("sub_token")

  case Lithic.Webhook.verify(raw_body, signature, timestamp, secret: sub["secret"]) do
    {:ok, payload} ->
      handle_event(payload["type"], payload)
      send_resp(conn, 200, "ok")
    {:error, reason} ->
      send_resp(conn, 400, reason)
  end
end
```

## Telemetry

```elixir
# Attach the default structured logger
Lithic.Telemetry.attach_default_logger(level: :info)

# Or write your own handler
:telemetry.attach(
  "my-handler",
  [:lithic, :request_stop],
  fn _event, %{duration: d}, %{method: m, path: p}, _cfg ->
    ms = System.convert_time_unit(d, :native, :millisecond)
    Logger.info("[lithic] #{m} #{p} #{ms}ms")
  end,
  nil
)
```

## Resource Modules

| Module | Description |
|--------|-------------|
| Lithic.Cards | Card lifecycle, balances, provisioning |
| Lithic.Accounts | Account management |
| Lithic.AccountHolders | KYC/KYB |
| Lithic.AuthRules | V2 rules engine with backtesting |
| Lithic.AuthStreamAccess | Real-time ASA webhook |
| Lithic.Transactions | Card transactions + sandbox simulation |
| Lithic.Payments | ACH payments |
| Lithic.BookTransfers | Internal transfers |
| Lithic.ExternalBankAccounts | ACH counterparty accounts |
| Lithic.ExternalPayments | External payment lifecycle |
| Lithic.FinancialAccounts | Ledger, balances, credit config |
| Lithic.Balances | Balance queries |
| Lithic.Holds | Financial holds |
| Lithic.ManagementOperations | Manual ledger adjustments |
| Lithic.CardBulkOrders | Bulk physical card orders |
| Lithic.ThreeDS | 3DS auth and decisioning |
| Lithic.Tokenization | Digital wallet tokenization |
| Lithic.Events | Webhooks and event subscriptions |
| Lithic.Chargebacks | Chargeback/dispute (legacy) |
| Lithic.Disputes | Disputes V2 |
| Lithic.FraudReports | Fraud reporting |
| Lithic.Credit | Credit products, statements, loan tapes |
| Lithic.Settlement | Settlement summaries |
| Lithic.Network | Network programs and totals |
| Lithic.FundingEvents | Program-level funding |
| Lithic.TransactionMonitoring | Cases and queues |

## License

MIT
