README.md

# Zepto

Elixir client for the [Zepto Payments API](https://docs.zeptopayments.com/).

## Installation

Add `zepto` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:zepto, "~> 0.1.0"}
  ]
end
```

## Configuration

### 1. Add Zepto configuration

In your `config/config.exs` or environment-specific config:

```elixir
config :zepto,
  environments: [
    sandbox: [
      base_url: "https://api.sandbox.zeptopayments.com",
      oauth_url: "https://go.sandbox.zeptopayments.com",
      client_id: System.get_env("ZEPTO_SANDBOX_CLIENT_ID"),
      client_secret: System.get_env("ZEPTO_SANDBOX_CLIENT_SECRET"),
      redirect_uri: "https://yourapp.com/zepto/sandbox/callback"
    ],
    prod: [
      base_url: "https://api.zeptopayments.com",
      oauth_url: "https://go.zeptopayments.com",
      client_id: System.get_env("ZEPTO_CLIENT_ID"),
      client_secret: System.get_env("ZEPTO_CLIENT_SECRET"),
      redirect_uri: "https://yourapp.com/zepto/callback"
    ]
  ]
```

### 2. Set up the OAuth callback routes

Zepto uses OAuth2 Authorization Code flow. Add the callback plug to your Phoenix router:

```elixir
# In lib/my_app_web/router.ex

defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  # ... your other pipelines and routes ...

  scope "/zepto" do
    # Production callback
    forward "/callback", Zepto.Plug.OAuthCallback,
      environment: :prod,
      on_success: &MyAppWeb.ZeptoController.on_oauth_success/2,
      on_error: &MyAppWeb.ZeptoController.on_oauth_error/2

    # Sandbox callback
    forward "/sandbox/callback", Zepto.Plug.OAuthCallback,
      environment: :sandbox,
      on_success: &MyAppWeb.ZeptoController.on_oauth_success/2,
      on_error: &MyAppWeb.ZeptoController.on_oauth_error/2
  end
end
```

### 3. Create callback handlers

```elixir
# In lib/my_app_web/controllers/zepto_controller.ex

defmodule MyAppWeb.ZeptoController do
  use MyAppWeb, :controller

  def on_oauth_success(conn, _tokens) do
    conn
    |> put_flash(:info, "Successfully connected to Zepto!")
    |> redirect(to: "/settings")
  end

  def on_oauth_error(conn, error) do
    conn
    |> put_flash(:error, "Failed to connect to Zepto: #{inspect(error)}")
    |> redirect(to: "/settings")
  end
end
```

### 4. Initiate the OAuth flow

Create a link or button that redirects users to Zepto's authorization page:

```elixir
# Generate the authorization URL
def zepto_auth_url(environment \\ :sandbox) do
  config = Application.get_env(:zepto, :environments)[environment]
  client_id = config[:client_id]
  redirect_uri = URI.encode_www_form(config[:redirect_uri])
  scope = "public+agreements+bank_accounts+contacts+open_agreements+payments+payment_requests+refunds+transfers+transactions+offline_access+webhooks"

  oauth_url = config[:oauth_url]
  "#{oauth_url}/authorise?client_id=#{client_id}&redirect_uri=#{redirect_uri}&response_type=code&scope=#{scope}"
end
```

In your template:

```heex
<a href={zepto_auth_url(:sandbox)}>Connect to Zepto (Sandbox)</a>
```

## Usage

Once authenticated, you can use the API:

```elixir
# Create a client for the sandbox environment
client = Zepto.client(:sandbox)

# List contacts
{:ok, contacts} = Zepto.Contacts.list(client)

# Get a specific contact
{:ok, contact} = Zepto.Contacts.get(client, "contact_id")

# Create a payment
{:ok, payment} = Zepto.Payments.create(client, %{
  description: "Payment for invoice #123",
  matures_at: "2024-01-15T00:00:00Z",
  your_bank_account_id: "your_bank_account_id",
  payouts: [
    %{
      amount: 10000,
      description: "Invoice payment",
      recipient_contact_id: "contact_id"
    }
  ]
})

# List transactions
{:ok, transactions} = Zepto.Transactions.list(client)
```

## API Resources

The following API resources are available:

- `Zepto.Agreements` - Payment agreements
- `Zepto.BankAccounts` - Bank account management
- `Zepto.Contacts` - Contact management
- `Zepto.Payments` - Payment processing
- `Zepto.PaymentRequests` - Payment request management
- `Zepto.Refunds` - Refund processing
- `Zepto.Transactions` - Transaction history
- `Zepto.Transfers` - Transfer management
- `Zepto.User` - Current user info
- `Zepto.Webhooks` - Webhook management
- `Zepto.Sandbox` - Sandbox testing utilities

## Token Management

The library manages OAuth tokens automatically:

- Access tokens are refreshed automatically when they expire (every 2 hours)
- Refresh tokens are stored in the TokenManager process state
- Each configured environment gets its own TokenManager process

### Checking Authorization Status

```elixir
# Check if we have a refresh token
Zepto.Client.TokenManager.has_refresh_token?(Zepto.TokenManager.Sandbox)
# => true or false

# Get the current refresh token (for debugging)
{:ok, token} = Zepto.Client.TokenManager.get_refresh_token(Zepto.TokenManager.Sandbox)
```

### Persisting Tokens

By default, tokens are stored in memory and will be lost on application restart. To persist tokens across restarts, implement the `Zepto.TokenCallback` behaviour:

```elixir
defmodule MyApp.ZeptoTokenStore do
  @behaviour Zepto.TokenCallback

  alias MyApp.Repo
  alias MyApp.ZeptoToken

  @impl true
  def save_tokens(environment, tokens) do
    attrs = %{
      environment: to_string(environment),
      refresh_token: tokens.refresh_token,
      access_token: tokens[:access_token],
      expires_at: tokens[:expires_at]
    }

    %ZeptoToken{}
    |> ZeptoToken.changeset(attrs)
    |> Repo.insert(on_conflict: :replace_all, conflict_target: :environment)
    |> case do
      {:ok, _} -> :ok
      {:error, changeset} -> {:error, changeset}
    end
  end

  @impl true
  def load_tokens(environment) do
    case Repo.get_by(ZeptoToken, environment: to_string(environment)) do
      nil ->
        {:ok, nil}

      token ->
        {:ok, %{
          refresh_token: token.refresh_token,
          access_token: token.access_token,
          expires_at: token.expires_at
        }}
    end
  end
end
```

Then add it to your config:

```elixir
config :zepto,
  environments: [
    sandbox: [
      # ... other config ...
      token_callback: MyApp.ZeptoTokenStore
    ]
  ]
```

The callbacks are called:
- `load_tokens/1` - Lazily on first API call (to restore tokens)
- `save_tokens/2` - After each token refresh and after OAuth authorization

Note: Tokens are loaded lazily to avoid startup ordering issues with your Repo.

## License

MIT