Packages
phoenix_kit
1.7.21
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/billing/schemas/webhook_event.ex
defmodule PhoenixKit.Modules.Billing.WebhookEvent do
@moduledoc """
Schema for webhook event logging and idempotency.
Every webhook received from payment providers is logged here to:
- Ensure idempotency (same event_id is never processed twice)
- Track processing status and errors
- Enable debugging and auditing
- Support retry logic for failed events
## Idempotency
Before processing a webhook, we check if an event with the same
`provider` + `event_id` combination exists. If it does, we skip
processing and return success to prevent retries from provider.
## Retry Logic
Failed events can be retried:
1. Provider sends retry (we check idempotency, process if not done)
2. Manual retry via admin interface
3. Background worker for events stuck in processing
"""
use Ecto.Schema
import Ecto.Changeset
schema "phoenix_kit_webhook_events" do
field :uuid, Ecto.UUID
field :provider, :string
field :event_id, :string
field :event_type, :string
field :payload, :map, default: %{}
field :processed, :boolean, default: false
field :processed_at, :utc_datetime
field :error_message, :string
field :retry_count, :integer, default: 0
timestamps(type: :utc_datetime)
end
@doc """
Creates a changeset for a webhook event.
"""
def changeset(event, attrs) do
event
|> cast(attrs, [
:provider,
:event_id,
:event_type,
:payload,
:processed,
:processed_at,
:error_message,
:retry_count
])
|> validate_required([:provider, :event_id, :event_type])
|> unique_constraint([:provider, :event_id],
name: :phoenix_kit_webhook_events_provider_event_id_index
)
|> maybe_generate_uuid()
end
defp maybe_generate_uuid(changeset) do
case get_field(changeset, :uuid) do
nil -> put_change(changeset, :uuid, UUIDv7.generate())
_ -> changeset
end
end
@doc """
Changeset for marking an event as processed.
"""
def processed_changeset(event) do
event
|> change(%{
processed: true,
processed_at: DateTime.utc_now(),
error_message: nil
})
end
@doc """
Changeset for marking an event as failed.
"""
def failed_changeset(event, error_message) do
event
|> change(%{
processed: false,
error_message: error_message,
retry_count: event.retry_count + 1
})
end
# ============================================
# Status Helpers
# ============================================
@doc """
Returns true if the event was successfully processed.
"""
def processed?(%__MODULE__{processed: true}), do: true
def processed?(_), do: false
@doc """
Returns true if the event has failed and can be retried.
"""
def retriable?(%__MODULE__{processed: false, retry_count: count}) when count < 5 do
true
end
def retriable?(_), do: false
@doc """
Returns true if the event has exceeded max retries.
"""
def max_retries_exceeded?(%__MODULE__{retry_count: count}) when count >= 5 do
true
end
def max_retries_exceeded?(_), do: false
end