Accrue.Config (accrue v1.5.0)

Copy Markdown View Source

Runtime configuration schema for Accrue, backed by NimbleOptions.

This module is the single source of truth for supported :accrue application keys. Host code reads validated values via get!/1 or Application.get_env/3; extend behaviour through adapters, not by editing this schema from application code.

Compile-time vs runtime

Adapter atoms (:processor, :mailer, :mailer_adapter, :invoice_pdf_adapter, :pdf_adapter, :auth_adapter) are stable per-deploy and fine at compile time via Application.compile_env!/2.

Secrets (:stripe_secret_key) and host-owned fields (:default_currency, :from_email, brand colors) MUST be read at runtime. See CLAUDE.md §Config Boundaries.

Options

  • :repo (atom/0) - Required. Host Ecto.Repo module that Accrue writes to (event ledger, webhook events, billing tables).

  • :billing_schema - PostgreSQL schema where Accrue-owned billing tables are stored. Defaults to billing; set to public explicitly to keep Accrue tables in the default schema. This is compile-time configuration for Ecto schemas and migration generation. The default value is "billing".

  • :processor (atom/0) - Processor adapter implementing Accrue.Processor behaviour. The default value is Accrue.Processor.Fake.

  • :rails (keyword/0) - Additive entitlement rail registrations. Omit to retain legacy single-processor behaviour. The default value is [].

    • :source (atom/0) - Required.

    • :processor - The default value is nil.

    • :environments (list of atom/0) - The default value is [].

    • :default_environment - The default value is nil.

  • :default_rail - The registered controllable rail that agrees with the legacy :processor alias. The default value is nil.

  • :mailer (atom/0) - Mailer pipeline module implementing Accrue.Mailer behaviour. The default value is Accrue.Mailer.Default.

  • :mailer_adapter (atom/0) - Swoosh-backed mailer delivery module. The default value is Accrue.Mailer.Swoosh.

  • :invoice_pdf_adapter (atom/0) - Invoice PDF adapter implementing Accrue.InvoiceRenderer behaviour. Defaults to the native Rendro-backed invoice renderer. The default value is Accrue.InvoiceRenderer.Rendro.

  • :pdf_adapter (atom/0) - Legacy HTML-to-PDF adapter implementing Accrue.PDF behaviour. Used by the Chromic invoice renderer path and by custom HTML callers. The default value is Accrue.PDF.ChromicPDF.

  • :auth_adapter (atom/0) - Auth adapter implementing Accrue.Auth behaviour. The default value is Accrue.Auth.Default.

  • :plan_resolver - Optional host-owned resolver module implementing Accrue.PlanResolver. Required when a processor needs more than a bare price_id to perform plan changes through the shared facade (for example Braintree swap-plan flows). The default value is nil.

  • :storage_adapter (atom/0) - Storage adapter implementing Accrue.Storage behaviour. v1.0 ships Accrue.Storage.Null only; hosts supply a custom adapter (e.g., S3) to enable persisted asset storage. Accrue.Storage.Filesystem ships in v1.1. The default value is Accrue.Storage.Null.

  • :stripe_secret_key (String.t/0) - Runtime Stripe secret key. MUST be read at runtime only; never via Application.compile_env!/2. Validated at boot when processor == Accrue.Processor.Stripe.

  • :stripe_api_version (String.t/0) - Stripe API version pinned by the :lattice_stripe wrapper. The default value is "2026-03-25.dahlia".

  • :emails (keyword/0) - Per-email-type switches. Keys are email type atoms; values are boolean or {Mod, :fun, args} MFA callbacks. The default value is [].

  • :email_overrides (keyword/0) - Per-email-type template module overrides (third rung of the override ladder; see guides/email.md). Keys are email type atoms; values are module names. The default value is [].

  • :attach_invoice_pdf (boolean/0) - Auto-attach invoice PDF to the receipt email. The default value is true.

  • :enforce_immutability (boolean/0) - When true, Accrue.Application boot raises if the current PG role has UPDATE/DELETE on accrue_events. The default value is false.

  • :business_name (String.t/0) - Business name shown in email headers, PDFs, and admin UI. The default value is "Accrue".

  • :business_address (String.t/0) - Business postal address shown in invoice footers. The default value is "".

  • :logo_url (String.t/0) - Absolute URL to the brand logo used in email + PDF headers. The default value is "".

  • :support_email (String.t/0) - Reply-to support email address for transactional mail. The default value is "support@example.com".

  • :from_email (String.t/0) - Default From: address for transactional mail. The default value is "noreply@example.com".

  • :from_name (String.t/0) - Default From: name for transactional mail. The default value is "Accrue".

  • :default_currency (atom/0) - Default currency when one is not explicitly supplied. The default value is :usd.

  • :webhook_signing_secrets (term/0) - Map of processor atom to signing secret(s). Each value is a string or list of strings for rotation. Example: %{stripe: ["whsec_old", "whsec_new"]}. The default value is %{}.

  • :succeeded_retention_days - Number of days to retain :succeeded webhook events before the Pruner deletes them. Set to :infinity to disable pruning. Default: 14. The default value is 14.

  • :dead_retention_days - Number of days to retain :dead webhook events before the Pruner deletes them. Set to :infinity to disable pruning. Default: 90. The default value is 90.

  • :webhook_handlers (list of atom/0) - List of modules implementing Accrue.Webhook.Handler behaviour. Called sequentially after the default handler on each webhook event. Example: [MyApp.BillingHandler, MyApp.AnalyticsHandler]. The default value is [].

  • :portal_mount_path (String.t/0) - Mount path for the first-party Accrue.Portal routes when a processor uses local portal semantics. Returned checkout / billing-portal URLs append to this normalized path after :portal_base_url supplies the absolute host. The default value is "/billing".

  • :portal_base_url - Absolute base URL (for example https://app.example.com) used to generate returned local portal checkout and billing-portal URLs. Leave unset only when those local portal URLs are not in use; portal_url/1 raises instead of falling back to a relative path. The default value is nil.

  • :braintree_client_token_generator (atom/0) - Module used to generate Braintree client tokens for Hosted Fields. Must export generate/1. The default value is Braintree.ClientToken.

  • :expiring_card_thresholds - Strictly-descending list of day thresholds at which the expiring-card reminder email fires ahead of a stored card's expiration. Default: [30, 7, 1] — 30, 7, and 1 days out. The default value is [30, 7, 1].

  • :idempotency_mode - How Accrue.Actor.current_operation_id!/0 behaves when the process dict has no operation_id. :strict raises Accrue.ConfigError; :warn (the default) generates a random UUID and logs a warning. Set to :strict in production to ensure every outbound processor call carries a deterministic idempotency key. The default value is :warn.

  • :succeeded_refund_retention_days (pos_integer/0) - Number of days to retain :succeeded refund records before pruning Default: 90. The default value is 90.

  • :dunning (keyword/0) - Dunning grace-period overlay config. :mode is :stripe_smart_retries or :disabled; :terminal_action is :unpaid or :canceled; :grace_days adds N days past Stripe's last retry before Accrue asks the processor facade to move the subscription to the terminal action. :campaign is the multi-step dunning cadence (see its own doc). The default value is [mode: :stripe_smart_retries, grace_days: 14, terminal_action: :unpaid, telemetry_prefix: [:accrue, :ops], campaign: [enabled: true, steps: [[after_days: 0, key: :reminder, template: Accrue.Emails.InvoicePaymentFailed], [after_days: 5, key: :action_required, template: Accrue.Emails.DunningActionRequired], [after_days: 12, key: :final_notice, template: Accrue.Emails.DunningFinalNotice]]], engine: Accrue.Dunning.Engine.Oban].

    • :mode - The default value is :stripe_smart_retries.

    • :grace_days (pos_integer/0) - The default value is 14.

    • :terminal_action - The default value is :unpaid.

    • :telemetry_prefix (list of atom/0) - The default value is [:accrue, :ops].

    • :campaign - Multi-step dunning cadence (D-04). A keyword list of [enabled: boolean, steps: [...]] where each step is [after_days: non_neg_integer, key: atom, template: atom]. after_days is ABSOLUTE from campaign start, strictly increasing and unique across the list; key is required and unique (it becomes the Oban-unique step identity). Ships a default journey ON by default (offsets [0, 5, 12]); set campaign: false (normalized to [enabled: false, steps: []]) to opt out. steps: [] while enabled: true is a loud error. The last step's after_days MUST be <= grace_days (validated loud at boot) so the final notice precedes the sweeper's terminal action. The default value is [enabled: true, steps: [[after_days: 0, key: :reminder, template: Accrue.Emails.InvoicePaymentFailed], [after_days: 5, key: :action_required, template: Accrue.Emails.DunningActionRequired], [after_days: 12, key: :final_notice, template: Accrue.Emails.DunningFinalNotice]]].

    • :engine (atom/0) - Module implementing Accrue.Dunning.Engine. Default: Accrue.Dunning.Engine.Oban (built-in Oban campaign). Set to Accrue.Integrations.Chimeway to delegate orchestration to Chimeway. The default value is Accrue.Dunning.Engine.Oban.

  • :webhook_endpoints (keyword/0) - Map of endpoint name to [secret:, mode:] for multi-endpoint webhooks. Example: [primary: [secret: "whsec_..."], connect: [secret: "whsec_...", mode: :connect]]. The default value is [].

  • :dlq_replay_batch_size (pos_integer/0) - Number of rows per chunk in Accrue.Webhooks.DLQ.requeue_where/2 bulk replay. The default value is 100.

  • :dlq_replay_stagger_ms (non_neg_integer/0) - Milliseconds to sleep between chunks during DLQ bulk replay (protects downstream). Default: 1_000. The default value is 1000.

  • :dlq_replay_max_rows (pos_integer/0) - Hard cap on bulk replay. Returns {:error, :replay_too_large} unless force: true is passed. Default: 10_000. The default value is 10000.

  • :branding (keyword/0) - Branding config. Single source of truth for email + PDF brand. :from_email and :support_email are required for any real deploy. See guides/branding.md. The default value is [].

    • :business_name (String.t/0) - The default value is "Accrue".

    • :from_name (String.t/0) - The default value is "Accrue".

    • :from_email (String.t/0) - Required.

    • :support_email (String.t/0) - Required.

    • :reply_to_email - The default value is nil.

    • :logo_url - The default value is nil.

    • :logo_dark_url - The default value is nil.

    • :accent_color - The default value is "#1F6FEB".

    • :secondary_color - The default value is "#6B7280".

    • :font_stack (String.t/0) - The default value is "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif".

    • :company_address - The default value is nil.

    • :support_url - The default value is nil.

    • :social_links (keyword/0) - The default value is [].

    • :list_unsubscribe_url - The default value is nil.

    • :theme - Customer portal color-mode policy. :system (default) offers light + dark with a picker (follows the OS, user-switchable via the accrue_theme cookie). :light or :dark locks the portal to that mode and hides the picker. The default value is :system.

  • :admin_branding (keyword/0) - Optional Accrue Admin chrome branding. When unset, admin chrome keeps the legacy behavior and derives its app name, logo, and accent from :branding. Set this when customer-facing billing should use one brand while the mounted operator UI keeps an Accrue/admin identity. The default value is [].

    • :app_name (String.t/0) - The default value is "Billing".

    • :logo_url - The default value is nil.

    • :accent_color - The default value is "#5D79F6".

  • :default_locale (String.t/0) - Application-wide default locale for email + PDF rendering. Third rung of the locale precedence ladder (after assigns[:locale] and customer.preferred_locale). Bad locales fall back to "en". The default value is "en".

  • :default_timezone (String.t/0) - Application-wide default IANA timezone for datetime rendering. Third rung of the timezone precedence ladder (after assigns[:timezone] and customer.preferred_timezone). Bad zones fall back to "Etc/UTC". The default value is "Etc/UTC".

  • :cldr_backend (atom/0) - Cldr backend module used by Accrue.Workers.Mailer.enrich/2 to validate locale strings. Defaults to Accrue.Cldr. The default value is Accrue.Cldr.

  • :connect (keyword/0) - Stripe Connect configuration. :default_stripe_account is the fallback connected account id used when no per-call override or pdict scope is active (three-level precedence chain). :platform_fee configures the default flat-rate fee consumed by Accrue.Connect.platform_fee/2: :percent is a Decimal percentage (e.g. Decimal.new("2.9") for 2.9%), :fixed is an Accrue.Money fee in minor units added after the percentage, and :min/:max optionally clamp the result. The default value is [default_stripe_account: nil, platform_fee: [percent: Decimal.new("2.9"), fixed: nil, min: nil, max: nil]].

  • :entitlements (keyword/0) - Plan->feature/quota entitlement catalog (host-owned, runtime). Boot-validated and the same price_id may not map to two plans (raises Accrue.ConfigError at boot). See guides/entitlements.md. The default value is [].

    • :plans (keyword/0) - Logical plan name (atom) -> entitlement entry (features/limits/price_ids) The default value is [].

      • :features (list of atom/0) - The default value is [].

      • :limits (keyword/0) - The default value is [].

      • :price_ids (list of String.t/0) - The default value is [].

      • :products (keyword/0) - The default value is [].

    • :resolver (atom/0) - Resolver module (Accrue.Entitlements.Resolver behaviour). Default LocalMap. The default value is Accrue.Entitlements.Resolver.LocalMap.

    • :unmapped_action - Behaviour when an active price_id is unmapped. :deny fails closed; never silent-allow. The default value is :deny.

    • :billable - Global billable resolver: a 1-arity fn (conn | socket -> billable | nil). When unset, the default probes current_scope.user -> current_user -> nil. Must never raise — fail closed (resolve to nil) on any miss. The default value is nil.

    • :on_deny - Global deny handler. Default is a content-negotiated opaque 403. Accepts :forbidden | {:redirect, path} | {status, body} | fun/2 | {m,f,a}. A per-guard on_deny: opt overrides this; this overrides the built-in 403. A malformed value fails loud at boot (never silently fails open). The default value is :forbidden.

    • :deny_path (String.t/0) - LiveView fallback redirect target for :forbidden / non-redirectable denies. The default value is "/".

    • :past_due_grace - Entitlement access for :past_due subscriptions. :none (default) fails closed immediately. :dunning honors the dunning grace window (reuses Accrue.Config.dunning()[:grace_days]). A positive integer N honors an entitlement-specific N-day window. Grace grants are affirmative, resolved, configured decisions — never a fail-open. The default value is :none.

    • :stripe_native_sync - Optional Stripe-native entitlement-summary sync. :disabled (default) is fully inert — no webhook reducer runs, no cache table is read or written. :advisory records Stripe entitlement summaries to an advisory cache for audit / telemetry / the admin read-seam; it does NOT change entitled?/has_active_plan? decisions in v1.x (local mapping stays canonical). The enum (not a boolean) reserves room for future additive modes without a breaking config change. The default value is :disabled.

    • :multi_rail (keyword/0) - Compatibility authority lane for canonical entitlement projection. Omit it (or use :disabled) to retain LocalMap authority; :shadow and :enabled require an explicit approved-account/MFA cohort. The default value is [].

      • :mode - The default value is :disabled.

      • :cohort (term/0) - The default value is nil.

      • :clean_window (term/0) - The default value is nil.

Summary

Functions

Returns branding for the mounted Accrue Admin operator chrome.

Returns a single admin branding key. Raises if the key is unknown.

Runtime billing schema prefix used by migration and raw SQL helpers.

Returns the branding config keyword list.

Returns a single branding key. Raises if the key is unknown.

Returns the configured Cldr backend module used by Accrue.Workers.Mailer.enrich/2 to validate locale strings.

Compile-time billing schema prefix used by Accrue Ecto schemas.

Returns the Connect config keyword list.

Returns the number of days to retain :dead webhook events.

Returns the application default locale string.

Returns the configured controllable entitlement rail, when rails are configured.

Returns the application default IANA timezone string.

Returns the DLQ bulk-replay chunk size.

Returns the hard cap on DLQ bulk-replay rows.

Returns the DLQ bulk-replay inter-chunk stagger in milliseconds.

Returns the dunning grace-period overlay config.

Returns the dunning campaign cadence config (D-07).

Returns whether the multi-step dunning campaign is enabled (D-07).

Returns the configured dunning campaign steps (D-07).

Returns the configured dunning engine module (D-03).

Returns logical plans indexed by {rail, environment, product_id}.

Returns the runtime :entitlements config keyword list (the plan->feature/ quota catalog), or the schema default [] when unset.

Reads a config key from Application.get_env/3, falling back to the schema default. Raises Accrue.ConfigError if the key is not in the schema at all (prevents silent typos in downstream code).

Returns the validated compatibility authority configuration.

Returns the past-due entitlement grace policy: :none (default, fail-closed), :dunning (reuse the dunning grace window), or a positive integer of days.

Returns configured entitlement rails, or [] when a host uses the legacy processor path.

Returns the NimbleOptions schema keyword list. Used by boot-time validation to iterate keys.

Returns the configured Stripe API version string.

Returns the optional Stripe-native entitlement-sync mode: :disabled (default, fully inert) or :advisory (record summaries to the advisory cache for audit / telemetry / the admin read-seam — does NOT change entitled?/has_active_plan? decisions; local mapping stays canonical).

Ergonomic predicate for the Stripe-native sync gate: true when sync is enabled in any non-:disabled mode, false otherwise. The webhook reducer dispatch checks this FIRST so the off lane early-returns before any Repo call.

Returns the number of days to retain :succeeded webhook events.

Validates a keyword list against the Accrue config schema and returns the normalized form. Raises NimbleOptions.ValidationError on failure.

Reads the current :accrue application env at boot time, filters it to the schema-known keys, and validates via NimbleOptions.validate!/2.

NimbleOptions :custom validator for :expiring_card_thresholds.

NimbleOptions :custom validator for the :dunning.campaign cadence (DUN-01, D-04/D-05).

NimbleOptions :custom validator for :branding.accent_color / :branding.secondary_color. Accepts #rgb, #rrggbb, and #rrggbbaa hex color strings; rejects anything else.

NimbleOptions :custom validator for the global :entitlements on_deny handler (Phase 124, ENT-06).

Returns the multi-endpoint webhook config.

Returns the list of user-registered webhook handler modules.

Returns the signing secret(s) for the given processor.

Functions

admin_branding()

@spec admin_branding() :: keyword()

Returns branding for the mounted Accrue Admin operator chrome.

Backwards compatible default: when :admin_branding is unset, derive the admin label, logo, and accent from the customer billing :branding config. Hosts that need a split identity can set :admin_branding explicitly.

admin_branding(key)

@spec admin_branding(atom()) :: term()

Returns a single admin branding key. Raises if the key is unknown.

billing_schema()

@spec billing_schema() :: String.t()

Runtime billing schema prefix used by migration and raw SQL helpers.

branding()

@spec branding() :: keyword()

Returns the branding config keyword list.

Reads the nested :branding keyword list from app env, merging any unset keys with their schema defaults so callers can Keyword.fetch! any valid key without re-running the full NimbleOptions validator on every call site.

branding(key)

@spec branding(atom()) :: term()

Returns a single branding key. Raises if the key is unknown.

cldr_backend()

@spec cldr_backend() :: module()

Returns the configured Cldr backend module used by Accrue.Workers.Mailer.enrich/2 to validate locale strings.

compile_time_billing_schema()

@spec compile_time_billing_schema() :: String.t()

Compile-time billing schema prefix used by Accrue Ecto schemas.

connect()

@spec connect() :: keyword()

Returns the Connect config keyword list.

Shape: `[default_stripe_account: String.t() | nil,

      platform_fee: [percent: Decimal.t(), fixed: Accrue.Money.t() | nil,
                     min: Accrue.Money.t() | nil, max: Accrue.Money.t() | nil]]`.

dead_retention_days()

@spec dead_retention_days() :: pos_integer() | :infinity

Returns the number of days to retain :dead webhook events.

default_locale()

@spec default_locale() :: String.t()

Returns the application default locale string.

default_rail()

@spec default_rail() :: atom() | nil

Returns the configured controllable entitlement rail, when rails are configured.

default_timezone()

@spec default_timezone() :: String.t()

Returns the application default IANA timezone string.

dlq_replay_batch_size()

@spec dlq_replay_batch_size() :: pos_integer()

Returns the DLQ bulk-replay chunk size.

dlq_replay_max_rows()

@spec dlq_replay_max_rows() :: pos_integer()

Returns the hard cap on DLQ bulk-replay rows.

dlq_replay_stagger_ms()

@spec dlq_replay_stagger_ms() :: non_neg_integer()

Returns the DLQ bulk-replay inter-chunk stagger in milliseconds.

dunning()

@spec dunning() :: keyword()

Returns the dunning grace-period overlay config.

dunning_campaign()

@spec dunning_campaign() :: keyword()

Returns the dunning campaign cadence config (D-07).

Raw read with its own nested default — dunning/0 does not normalize the nested :campaign key (identical constraint to past_due_grace/0), so the default journey is supplied here when a host overrides :dunning without re-stating :campaign. Ships ON by default.

dunning_campaign_enabled?()

@spec dunning_campaign_enabled?() :: boolean()

Returns whether the multi-step dunning campaign is enabled (D-07).

false when the campaign is opted out (campaign: false / enabled: false). Fail-closed: an absent :enabled key reads as false.

dunning_campaign_steps()

@spec dunning_campaign_steps() :: [keyword()]

Returns the configured dunning campaign steps (D-07).

Returns [] when the campaign is disabled (so downstream consumers never fire steps for an opted-out host), else the configured step list.

dunning_engine()

@spec dunning_engine() :: module()

Returns the configured dunning engine module (D-03).

Defaults to Accrue.Dunning.Engine.Oban (built-in Oban campaign). Set dunning: [engine: Accrue.Integrations.Chimeway] to opt into Chimeway orchestration.

The atom is returned as-is without loading the module — resolution happens at dispatch time in the calling code.

entitlement_product_catalog()

@spec entitlement_product_catalog() :: %{
  optional({atom(), atom(), String.t()}) => atom()
}

Returns logical plans indexed by {rail, environment, product_id}.

entitlements()

@spec entitlements() :: keyword()

Returns the runtime :entitlements config keyword list (the plan->feature/ quota catalog), or the schema default [] when unset.

This is a thin runtime accessor (get!/1, NOT compile_env!) because :entitlements is host-owned catalog data that legitimately differs per environment (e.g. test-mode vs live-mode price_ids), like :branding and :dunning. Unlike branding/0 (which merges schema defaults), this is a raw runtime read — it does NOT apply the nested per-plan defaults (features: [], limits: [], price_ids: []). validate_at_boot!/0 validates the config but discards the normalized result and never writes it back to app env. The resolver tolerates missing nested keys via Keyword.get/3 defaults, so it reads this raw value without re-running the full validator.

get!(key)

@spec get!(atom()) :: term()

Reads a config key from Application.get_env/3, falling back to the schema default. Raises Accrue.ConfigError if the key is not in the schema at all (prevents silent typos in downstream code).

multi_rail()

@spec multi_rail() :: keyword()

Returns the validated compatibility authority configuration.

normalize_mount_path(path)

@spec normalize_mount_path(String.t()) :: String.t()

past_due_grace()

@spec past_due_grace() :: :none | :dunning | pos_integer()

Returns the past-due entitlement grace policy: :none (default, fail-closed), :dunning (reuse the dunning grace window), or a positive integer of days.

The :none default is applied here because entitlements/0 does a raw read without normalizing nested :entitlements defaults. The :dunning -> grace_days resolution is done by the resolver when it widens the fold, not by this accessor.

plan_resolver()

@spec plan_resolver() :: module() | nil

portal_base_url()

@spec portal_base_url() :: String.t() | nil

portal_mount_path()

@spec portal_mount_path() :: String.t()

portal_url(path)

@spec portal_url(String.t()) :: String.t()

rails()

@spec rails() :: keyword()

Returns configured entitlement rails, or [] when a host uses the legacy processor path.

schema()

@spec schema() :: keyword()

Returns the NimbleOptions schema keyword list. Used by boot-time validation to iterate keys.

stripe_api_version()

@spec stripe_api_version() :: String.t()

Returns the configured Stripe API version string.

stripe_native_sync()

@spec stripe_native_sync() :: :disabled | :advisory

Returns the optional Stripe-native entitlement-sync mode: :disabled (default, fully inert) or :advisory (record summaries to the advisory cache for audit / telemetry / the admin read-seam — does NOT change entitled?/has_active_plan? decisions; local mapping stays canonical).

The :disabled default is supplied here because entitlements/0 does a raw read without normalizing nested :entitlements defaults (identical constraint to past_due_grace/0).

stripe_native_sync?()

@spec stripe_native_sync?() :: boolean()

Ergonomic predicate for the Stripe-native sync gate: true when sync is enabled in any non-:disabled mode, false otherwise. The webhook reducer dispatch checks this FIRST so the off lane early-returns before any Repo call.

succeeded_retention_days()

@spec succeeded_retention_days() :: pos_integer() | :infinity

Returns the number of days to retain :succeeded webhook events.

validate!(opts)

@spec validate!(keyword()) :: keyword()

Validates a keyword list against the Accrue config schema and returns the normalized form. Raises NimbleOptions.ValidationError on failure.

validate_at_boot!()

@spec validate_at_boot!() :: :ok

Reads the current :accrue application env at boot time, filters it to the schema-known keys, and validates via NimbleOptions.validate!/2.

Called by Accrue.Application.start/2 before the supervision tree starts. Raises NimbleOptions.ValidationError on misconfig — fail loud rather than limp into production with silently-broken config.

Only schema-known keys are validated. Extra keys in the :accrue env (e.g., per-module adapter configs like Accrue.Mailer.Swoosh) are ignored here — they belong to their own libraries and would otherwise produce spurious unknown option errors.

validate_descending(list)

@spec validate_descending(term()) :: {:ok, [pos_integer()]} | {:error, String.t()}

NimbleOptions :custom validator for :expiring_card_thresholds.

Accepts a non-empty list of positive integers that is strictly descending (each element strictly less than the previous). Returns {:ok, list} on success, {:error, message} on failure.

validate_dunning_campaign(campaign)

@spec validate_dunning_campaign(term()) :: {:ok, keyword()} | {:error, String.t()}

NimbleOptions :custom validator for the :dunning.campaign cadence (DUN-01, D-04/D-05).

Accepts:

  • false — normalized to [enabled: false, steps: []] (opt-out, D-05).
  • a keyword list [enabled: boolean, steps: [...]] where each step is a keyword list [after_days: non_neg_integer, key: atom, template: atom] validated against @step_schema.

Intra-list invariants (the cross-field last_step.after_days <= grace_days invariant is checked separately at boot by validate_dunning_campaign_grace!/1, since a {:custom} validator cannot read the sibling :grace_days key):

  • after_days strictly increasing AND unique across the list.
  • key unique across the list.
  • when enabled: true, steps MUST be non-empty — steps: [] while enabled is a LOUD {:error, _} (never a silent disable, D-05).

Returns {:ok, normalized_keyword} on success, {:error, message} on failure (so boot validation fails loud rather than silently mis-firing).

validate_hex(full)

@spec validate_hex(term()) :: {:ok, String.t()} | {:error, String.t()}

NimbleOptions :custom validator for :branding.accent_color / :branding.secondary_color. Accepts #rgb, #rrggbb, and #rrggbbaa hex color strings; rejects anything else.

validate_on_deny(value)

@spec validate_on_deny(term()) :: {:ok, term()} | {:error, String.t()}

NimbleOptions :custom validator for the global :entitlements on_deny handler (Phase 124, ENT-06).

Accepts any of the supported deny-handler shapes and returns {:ok, value}; a malformed value returns {:error, message} so boot validation (validate_at_boot!/0) fails loud rather than letting a broken deny path silently fail open (T-124-01).

Supported shapes:

  • :forbidden — built-in opaque 403 / LiveView redirect to deny_path.
  • {:redirect, path} when path is a binary.
  • {status, body} when status is an integer and body is a binary.
  • a 2-arity fun (container, ctx -> result).
  • an MFA {m, f, a} when m/f are atoms and a is a list.

webhook_endpoints()

@spec webhook_endpoints() :: keyword()

Returns the multi-endpoint webhook config.

webhook_handlers()

@spec webhook_handlers() :: [module()]

Returns the list of user-registered webhook handler modules.

webhook_signing_secrets(processor)

@spec webhook_signing_secrets(atom()) :: String.t() | [String.t()]

Returns the signing secret(s) for the given processor.

Looks up webhook_signing_secrets in the :accrue application env and extracts the value for the given processor atom. Returns a list of strings (for multi-secret rotation support). Raises Accrue.ConfigError if no secrets are configured for the processor.