# Unit

Production-grade Elixir hex package for the [Unit embedded banking API](https://www.unit.co/docs/api/).

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

## Features

- ✅ Full Unit API coverage: applications, customers, accounts, cards, payments, transactions, webhooks, and more
- ✅ JSON:API envelope handling — resources decoded into typed Elixir structs
- ✅ Three-tier token model: Org, Customer, Cardholder
- ✅ Automatic retry with exponential back-off and jitter
- ✅ Idempotency key support on all mutating requests
- ✅ Telemetry instrumentation (`[:unit, :request, :start/stop/exception]`)
- ✅ Runtime config resolution (`{:system, "ENV_VAR"}` and MFA tuples)
- ✅ Sandbox simulation endpoints for every resource type
- ✅ Zero-surprise error types via `Unit.Error`

## Installation

```elixir
# mix.exs
def deps do
  [
    {:unit, "~> 1.0"}
  ]
end
```

## Configuration

```elixir
# config/runtime.exs
config :unit,
  api_token: System.get_env("UNIT_API_TOKEN"),
  environment: :sandbox    # :sandbox | :production
```

## Quick start

```elixir
# 1. Onboard an individual customer
{:ok, app} = Unit.API.Applications.create_individual(%{
  full_name: %{first: "Jane", last: "Doe"},
  ssn: "000000002",
  date_of_birth: ~D[1990-01-15],
  address: %{street: "123 Main St", city: "SF", state: "CA", postal_code: "94105", country: "US"},
  email: "jane@example.com",
  phone: %{country_code: "1", number: "5555550100"},
  ip: "127.0.0.1"
})

# 2. Open a checking account
{:ok, account} = Unit.API.Accounts.create_deposit(app.customer_id, "checking")

# 3. Issue a virtual debit card
{:ok, card} = Unit.API.Cards.create_individual_virtual_debit(%{
  customer_id: app.customer_id,
  account_id: account.id
})

# 4. Send an ACH credit
{:ok, payment} = Unit.API.Payments.create_ach(%{
  account_id: account.id,
  amount: 10_000,            # $100.00 in cents
  direction: "Credit",
  description: "Payroll",
  counterparty: %{
    routing_number: "812345678",
    account_number: "1000000002",
    account_type: "Checking",
    name: "Jane Doe"
  }
})

# 5. Listen for events via webhooks
{:ok, _webhook} = Unit.API.Webhooks.create(%{
  label: "my-app",
  url: "https://myapp.com/webhooks/unit",
  token: "my-secret-token",
  delivery_mode: "AtLeastOnce"
})
```

## Error handling

All functions return `{:ok, result}` or `{:error, %Unit.Error{}}`:

```elixir
case Unit.API.Payments.create_ach(params) do
  {:ok, payment} ->
    Logger.info("Payment created: #{payment.id}")

  {:error, %Unit.Error{type: :validation_error, errors: errors}} ->
    Logger.warning("Validation failed: #{inspect(errors)}")

  {:error, %Unit.Error{type: :auth_error}} ->
    Logger.error("Invalid or expired API token")

  {:error, %Unit.Error{type: :network_error, message: msg}} ->
    Logger.error("Network failure: #{msg}")
end
```

## Per-request options

Every API function accepts opts that override global config:

```elixir
# Use a customer-scoped token for front-end requests
Unit.API.Accounts.list(api_token: customer_token)

# Adjust timeout for a slow operation
Unit.API.Statements.get_pdf(id, timeout: 60_000)

# Use a different environment
Unit.API.Sandbox.receive_ach(params, environment: :sandbox)
```

## Telemetry

Attach to standard telemetry events:

```elixir
:telemetry.attach(
  "unit-logger",
  [:unit, :request, :stop],
  fn _event, %{duration: d}, %{resource: r, action: a, status: s}, _config ->
    Logger.info("[Unit] #{r}.#{a} → HTTP #{s} (#{div(d, 1_000_000)}ms)")
  end,
  nil
)
```

## Sandbox simulations

```elixir
# Simulate an incoming ACH credit (sandbox only)
Unit.API.Sandbox.receive_ach(%{
  account_id: account.id,
  amount: 50_000,
  direction: "Credit",
  description: "Test deposit"
})

# Simulate a card purchase
Unit.API.Sandbox.purchase_card(%{
  card_id: card.id,
  amount: 1_500,
  merchant: %{name: "Coffee Shop", type: 5812, category: "Restaurants", location: "SF"}
})
```

## API coverage

| Module | Endpoints |
|--------|-----------|
| `Unit.API.Applications` | create individual/business, get, list, update, cancel, documents upload |
| `Unit.API.Customers` | get, list, update, archive, authorized users |
| `Unit.API.Accounts` | create deposit, get, list, update, close, freeze/unfreeze, limits, balance history |
| `Unit.API.Cards` | create (all types), get, list, update, freeze/unfreeze, report lost/stolen, close, activate |
| `Unit.API.Payments` | ACH, Wire, Book, Bulk, received ACH, return received ACH, cancel |
| `Unit.API.Transactions` | get, list, update tags |
| `Unit.API.Counterparties` | create, get, list, update, delete |
| `Unit.API.Repayments` | book, ACH, capital partner variants, get, list |
| `Unit.API.CheckDeposits` | create, upload front/back, get, list, update |
| `Unit.API.CheckPayments` | create, get, list, cancel, return, get images |
| `Unit.API.StopPayments` | ACH, check, get, list, disable |
| `Unit.API.Chargebacks` | create, list |
| `Unit.API.Statements` | list, HTML, PDF, bank PDF |
| `Unit.API.Webhooks` | create, get, list, update, delete, enable/disable |
| `Unit.API.Events` | get, list, fire |
| `Unit.API.Tokens` | customer token, token verification, cardholder token, org token |
| `Unit.API.Institutions` | lookup by routing number |
| `Unit.API.Sandbox` | approve/deny app, receive ACH/wire, authorize/purchase card, ATM, check deposit, interest |

## License

MIT
