# `AshBilling.Deduplication`
[🔗](https://github.com/sfelixalejandro/ash_billing/blob/v0.1.3/lib/ash_billing/deduplication.ex#L1)

Deduplication engine for billing webhook events.

Supports two strategies:
* `:memory` (default) - High-performance in-memory ETS table with hourly TTL cleanup.
  Best for single-node deployments and local development.
* `:resource` - Database-backed Ash resource deduplication with unique constraint enforcement.
  Guarantees cross-node deduplication across clustered environments (e.g. Fly.io, Kubernetes, ECS).

## Configuration

Via application config:

    config :ash_billing,
      deduplication_strategy: :resource,
      deduplication_resource: MyApp.Billing.WebhookEvent

Or per-Plug in Phoenix Router:

    forward "/webhooks/stripe", AshBilling.Plug,
      deduplication_resource: MyApp.Billing.WebhookEvent,
      domain: MyApp.Billing

# `acquire`

```elixir
@spec acquire(String.t() | nil, keyword()) ::
  {:ok, :acquired}
  | {:error, :already_processed}
  | {:error, :already_processing}
  | {:error, term()}
```

Atomically acquires an exclusive processing lease for an event ID.

Returns:
* `{:ok, :acquired}` - Lease secured; this process is the exclusive worker for the event.
* `{:error, :already_processed}` - The event was already processed.
* `{:error, :already_processing}` - The event is currently being processed by another worker.
* `{:error, reason}` - Persistence error during acquisition.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `clear`

```elixir
@spec clear() :: :ok
```

Clears all tracked events in memory (useful in tests).

# `complete`

```elixir
@spec complete(String.t() | nil, keyword()) :: :ok
```

Completes processing for an acquired event lease.

# `mark_processed`

```elixir
@spec mark_processed(String.t() | nil, keyword()) :: :ok | {:error, term()}
```

Marks an event ID as processed directly.

Returns `:ok` on success or when already recorded, or `{:error, reason}` if persistence fails.

# `processed?`

```elixir
@spec processed?(String.t() | nil, keyword()) :: boolean()
```

Checks if an event ID has already been processed.

# `release`

```elixir
@spec release(String.t() | nil, keyword()) :: :ok
```

Releases an acquired event lease on failure so retries can be processed.

# `start_link`

Starts the deduplication server and initializes the ETS table for memory strategy.

---

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