defmodule AccessGrid.Console do
@moduledoc """
Manages enterprise template and logging operations.
This module provides functions for card template management (create, update, read),
retrieving activity logs, and listing template pairs. These are enterprise-only
features requiring special account permissions.
## Examples
# Create a new template
{:ok, result} = AccessGrid.Console.create_template(%{
name: "Employee Badge",
platform: "apple",
use_case: "corporate_id",
protocol: "desfire"
})
# Read full template details
{:ok, template} = AccessGrid.Console.read_template(result.id)
# Get activity logs
{:ok, logs, pagination} = AccessGrid.Console.get_logs(template.id)
"""
alias AccessGrid.CardTemplate
alias AccessGrid.CardTemplatePair
alias AccessGrid.Client
alias AccessGrid.CredentialProfile
alias AccessGrid.Event
alias AccessGrid.HidOrg
alias AccessGrid.HttpFailure
alias AccessGrid.HttpResponse
alias AccessGrid.IosPreflight
alias AccessGrid.LandingPage
alias AccessGrid.LedgerItem
alias AccessGrid.Params
alias AccessGrid.SmartTap
alias AccessGrid.SmartTapReveal
alias AccessGrid.Types
alias AccessGrid.Webhook
@base_path "/v1/console"
@type result :: {:ok, CardTemplate.Result.t()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type template_result ::
{:ok, CardTemplate.t() | CardTemplatePair.t()}
| {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type logs_result ::
{:ok, [Event.t()], map()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type pairs_result ::
{:ok, [CardTemplatePair.Summary.t()], map()}
| {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type pair_summary_result ::
{:ok, CardTemplatePair.Summary.t()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type ios_preflight_result ::
{:ok, IosPreflight.t()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type landing_pages_result ::
{:ok, [LandingPage.t()]} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type landing_page_result ::
{:ok, LandingPage.t()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type credential_profiles_result ::
{:ok, [CredentialProfile.t()]} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type credential_profile_result ::
{:ok, CredentialProfile.t()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type webhooks_result ::
{:ok, [Webhook.t()], map()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type webhook_result ::
{:ok, Webhook.t()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type webhook_delete_result :: :ok | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type hid_orgs_result ::
{:ok, [HidOrg.t()]} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type hid_org_result ::
{:ok, HidOrg.t()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type ledger_items_result ::
{:ok, [LedgerItem.t()], map()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type publish_result ::
{:ok, CardTemplate.PublishResult.t()}
| {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@type smart_tap_reveal_result ::
{:ok, SmartTapReveal.t()} | {:error, Types.api_error_reason(), HttpFailure.t() | [atom(), ...]}
@doc """
Creates a new card template.
Params are passed straight through to the server — pass top-level keys using the
exact wire names (no nested `design:` / `support_info:` wrappers). Image fields
(`background`, `logo`, `icon`) accept base64-encoded strings; see
`AccessGrid.Utils.base64_file/1` for a helper.
## Parameters
* `params` - Template configuration:
* `:name` - Display name for the template (required)
* `:platform` - `"apple"` or `"android"` (required)
* `:use_case` - one of `"corporate_id"`, `"student_id"`, `"multi_family"`, `"hotel"` (required)
* `:protocol` - `"desfire"` (Apple), `"seos"`, or `"smart_tap"` (Android)
* `:allow_on_multiple_devices`, `:watch_count`, `:iphone_count` - Device limits
* `:background_color`, `:label_color`, `:label_secondary_color` - Style settings
* `:background`, `:logo`, `:icon` - Base64-encoded PNG/JPEG images (max 10MB decoded)
* `:support_url`, `:support_phone_number`, `:support_email` - Support contact info
* `:privacy_policy_url`, `:terms_and_conditions_url` - Legal URLs
* `:credential_profiles` - List of credential-profile ex_id strings to attach
* `:landing_pages` - List of landing-page ex_id strings to attach
* `:metadata` - Custom metadata map
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %CardTemplate.Result{}}` - Template created successfully
* `{:error, reason, %HttpFailure{}}` - Creation failed
"""
@spec create_template(map(), keyword()) :: result()
def create_template(params, opts \\ []) do
with :ok <- Params.require(params, [:name, :platform, :use_case, :protocol]) do
opts[:client]
|> Client.request(:post, "#{@base_path}/card-templates", body: params)
|> handle_result_response()
end
end
@doc """
Updates an existing card template.
## Parameters
* `template_id` - The template ID to update
* `params` - Fields to update (same options as create_template)
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %CardTemplate.Result{}}` - Template updated successfully
* `{:error, reason, %HttpFailure{}}` - Update failed
"""
@spec update_template(String.t(), map(), keyword()) :: result()
def update_template(template_id, params, opts \\ []) do
with :ok <- Params.require_present(template_id, :template_id) do
opts[:client]
|> Client.request(:put, "#{@base_path}/card-templates/#{template_id}", body: params)
|> handle_result_response()
end
end
@doc """
Retrieves a card template or card template pair by id.
The `/v1/console/card-templates/:id` endpoint serves both shapes: a single
template, or a pair containing two member templates. Match on the returned
struct to tell them apart.
## Parameters
* `template_id` - The template (or pair) id to retrieve
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %CardTemplate{}}` - Single template
* `{:ok, %CardTemplatePair{}}` - Pair, with member templates under `:templates`
* `{:error, reason, %HttpFailure{}}` - Retrieval failed
"""
@spec read_template(String.t(), keyword()) :: template_result()
def read_template(template_id, opts \\ []) do
with :ok <- Params.require_present(template_id, :template_id) do
opts[:client]
|> Client.request(:get, "#{@base_path}/card-templates/#{template_id}")
|> handle_template_response()
end
end
@doc """
Retrieves activity logs for a card template.
## Parameters
* `template_id` - The template ID to get logs for
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
* `:page` - Page number (default: 1)
* `:per_page` - Results per page (default: 50, max: 100)
* `:filters` - Map with optional filters:
* `:device` - "mobile" or "watch"
* `:start_date` - ISO8601 timestamp
* `:end_date` - ISO8601 timestamp
* `:event_type` - Event type string
## Returns
* `{:ok, [%Event{}], pagination}` - List of events and pagination info
* `{:error, reason, %HttpFailure{}}` - Retrieval failed
"""
@spec get_logs(String.t(), keyword()) :: logs_result()
def get_logs(template_id, opts \\ []) do
with :ok <- Params.require_present(template_id, :template_id) do
params =
%{}
|> maybe_add_param(:page, opts[:page])
|> maybe_add_param(:per_page, opts[:per_page])
|> maybe_add_param(:filters, opts[:filters])
opts[:client]
|> Client.request(:get, "#{@base_path}/card-templates/#{template_id}/logs", params: params)
|> handle_logs_response()
end
end
@doc """
Runs Apple Wallet In-App Provisioning preflight for an access pass.
Returns the identifiers needed to drive the iOS provisioning flow. Note that
the server returns these keys in camelCase (Apple convention); the resulting struct
uses snake_case Elixir-idiomatic field names.
## Parameters
* `template_id` - The card template ID containing the access pass
* `params` - Map with:
* `:access_pass_ex_id` - ex_id of the access pass to preflight (required)
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %IosPreflight{}}` - Preflight identifiers
* `{:error, reason, %HttpFailure{}}` - Preflight failed (404 if template or access pass missing)
"""
@spec ios_preflight(String.t(), map(), keyword()) :: ios_preflight_result()
def ios_preflight(template_id, params, opts \\ []) do
with :ok <- Params.require_present(template_id, :template_id),
:ok <- Params.require(params, [:access_pass_ex_id]) do
opts[:client]
|> Client.request(:post, "#{@base_path}/card-templates/#{template_id}/ios_preflight", body: params)
|> handle_ios_preflight_response()
end
end
@doc """
Lists all pass template pairs for the account.
Template pairs combine an iOS and Android template for cross-platform pass issuance.
## Parameters
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
* `:page` - Page number (default: 1)
* `:per_page` - Results per page (default: 50, max: 100)
## Returns
* `{:ok, [%CardTemplatePair.Summary{}], pagination}` - List of pairs and pagination info
* `{:error, reason, %HttpFailure{}}` - Retrieval failed
"""
@spec list_card_template_pairs(keyword()) :: pairs_result()
def list_card_template_pairs(opts \\ []) do
params =
%{}
|> maybe_add_param(:page, opts[:page])
|> maybe_add_param(:per_page, opts[:per_page])
opts[:client]
|> Client.request(:get, "#{@base_path}/card-template-pairs", params: params)
|> handle_pairs_response()
end
@doc """
Creates a card template pair from an existing Apple and Google template.
The the API enforces several validations before the pair is created:
* Both referenced templates must belong to the current account (404 otherwise).
* Apple template must have `platform == "apple"` and Google template `platform == "android"` (422).
* Protocol combination must be either both SEOS, or Apple DESFire + Google Smart Tap (422).
* Both templates must be in `status == "ready"` (i.e. published) (422).
## Parameters
* `params` - Map with:
* `:name` - Name for the new pair (required)
* `:apple_card_template_id` - ex_id of the published Apple template
* `:google_card_template_id` - ex_id of the published Google template
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %CardTemplatePair.Summary{}}` - Pair created. Same shape as items returned by
`list_card_template_pairs/1`.
* `{:error, reason, %HttpFailure{}}` - Creation failed.
"""
@spec create_card_template_pair(map(), keyword()) :: pair_summary_result()
def create_card_template_pair(params, opts \\ []) do
with :ok <-
Params.require(params, [
:name,
:apple_card_template_id,
:google_card_template_id
]) do
opts[:client]
|> Client.request(:post, "#{@base_path}/card-template-pairs", body: params)
|> handle_pair_summary_response()
end
end
@doc """
Lists all landing pages for the account.
The server returns a flat JSON array — there is no `landing_pages` wrapper and no
pagination, so the result is `{:ok, list}` rather than `{:ok, list, pagination}`.
## Parameters
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, [%LandingPage{}]}` - List of landing pages (may be empty)
* `{:error, reason, %HttpFailure{}}` - Retrieval failed
"""
@spec list_landing_pages(keyword()) :: landing_pages_result()
def list_landing_pages(opts \\ []) do
opts[:client]
|> Client.request(:get, "#{@base_path}/landing-pages")
|> handle_landing_pages_list_response()
end
@doc """
Creates a new landing page.
## Parameters
* `params` - Map with:
* `:name` - Display name (required)
* `:kind` - Landing page kind (required; immutable after creation)
* `:additional_text`, `:bg_color`, `:allow_immediate_download`,
`:password`, `:is_2fa_enabled` - Optional fields
* `:logo` - Base64-encoded PNG or JPEG image (optional)
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %LandingPage{}}` - Created
* `{:error, reason, %HttpFailure{}}` - Creation failed
"""
@spec create_landing_page(map(), keyword()) :: landing_page_result()
def create_landing_page(params, opts \\ []) do
with :ok <- Params.require(params, [:name, :kind]) do
opts[:client]
|> Client.request(:post, "#{@base_path}/landing-pages", body: params)
|> handle_landing_page_response()
end
end
@doc """
Updates an existing landing page.
The `:kind` field is immutable after creation. Sending a different value
returns 422.
## Parameters
* `landing_page_id` - The landing page ID to update
* `params` - Same fields as `create_landing_page/2` except `:kind`
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %LandingPage{}}` - Updated
* `{:error, reason, %HttpFailure{}}` - 404 if id missing, 422 on validation
"""
@spec update_landing_page(String.t(), map(), keyword()) :: landing_page_result()
def update_landing_page(landing_page_id, params, opts \\ []) do
with :ok <- Params.require_present(landing_page_id, :landing_page_id) do
opts[:client]
|> Client.request(:put, "#{@base_path}/landing-pages/#{landing_page_id}", body: params)
|> handle_landing_page_response()
end
end
@doc """
Lists all credential profiles for the account.
The server returns a flat JSON array — no wrapper, no pagination — so the result
is `{:ok, list}` rather than `{:ok, list, pagination}`.
## Parameters
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, [%CredentialProfile{}]}` - List of credential profiles (may be empty)
* `{:error, reason, %HttpFailure{}}` - Retrieval failed
"""
@spec list_credential_profiles(keyword()) :: credential_profiles_result()
def list_credential_profiles(opts \\ []) do
opts[:client]
|> Client.request(:get, "#{@base_path}/credential-profiles")
|> handle_credential_profiles_list_response()
end
@doc """
Creates a new credential profile.
## Parameters
* `params` - Map with:
* `:name` - Display name (required)
* `:app_name` - Reader app name (optional, defaults to `"KEY-ID-main"`)
* `:keys` - List of `%{value, keys_diversified?, source_key_index?}` maps;
length must match the app's required key count
* `:file_id` - Hex file id string (optional, defaults to `"00"`)
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %CredentialProfile{}}` - Created
* `{:error, reason, %HttpFailure{}}` - 422 on invalid `app_name`, wrong key count, or validation failure
"""
@spec create_credential_profile(map(), keyword()) :: credential_profile_result()
def create_credential_profile(params, opts \\ []) do
with :ok <- Params.require(params, [:name, :keys]) do
opts[:client]
|> Client.request(:post, "#{@base_path}/credential-profiles", body: params)
|> handle_credential_profile_response()
end
end
@doc """
Lists webhook subscriptions for the account.
## Parameters
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
* `:page` - Page number (default: 1)
* `:per_page` - Results per page (default: 50, max: 100)
## Returns
* `{:ok, [%Webhook{}], pagination}` - List + pagination map
* `{:error, reason, %HttpFailure{}}` - Retrieval failed
"""
@spec list_webhooks(keyword()) :: webhooks_result()
def list_webhooks(opts \\ []) do
params =
%{}
|> maybe_add_param(:page, opts[:page])
|> maybe_add_param(:per_page, opts[:per_page])
opts[:client]
|> Client.request(:get, "#{@base_path}/webhooks", params: params)
|> handle_webhooks_list_response()
end
@doc """
Creates a new webhook subscription.
## Parameters
* `params` - Map with:
* `:name` - Display name
* `:url` - HTTPS endpoint to receive events
* `:subscribed_events` - List of event names (e.g. `["ag.access_pass.issued"]`)
* `:auth_method` - `"bearer_token"` (default) or `"mtls"`
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %Webhook{}}` - Created. For `bearer_token`, the struct includes
`private_key` (sensitive — store on receipt, the server does not return it
again). For `mtls`, the struct includes `client_cert` and `cert_expires_at`.
* `{:error, reason, %HttpFailure{}}` - 422 on empty or invalid `subscribed_events`.
"""
@spec create_webhook(map(), keyword()) :: webhook_result()
def create_webhook(params, opts \\ []) do
with :ok <- Params.require(params, [:name, :url, :subscribed_events]) do
opts[:client]
|> Client.request(:post, "#{@base_path}/webhooks", body: params)
|> handle_webhook_response()
end
end
@doc """
Deletes a webhook subscription.
## Parameters
* `webhook_id` - The webhook ID to delete
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `:ok` - Deleted (the server returns 204 No Content; there is no body)
* `{:error, reason, %HttpFailure{}}` - 404 if id missing
"""
@spec delete_webhook(String.t(), keyword()) :: webhook_delete_result()
def delete_webhook(webhook_id, opts \\ []) do
with :ok <- Params.require_present(webhook_id, :webhook_id) do
opts[:client]
|> Client.request(:delete, "#{@base_path}/webhooks/#{webhook_id}")
|> handle_webhook_delete_response()
end
end
@doc """
Lists HID Origo organizations registered to the account.
The server returns a flat JSON array — no wrapper, no pagination.
## Parameters
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, [%HidOrg{}]}` - List of HID orgs (may be empty)
* `{:error, reason, %HttpFailure{}}` - Retrieval failed
"""
@spec list_hid_orgs(keyword()) :: hid_orgs_result()
def list_hid_orgs(opts \\ []) do
opts[:client]
|> Client.request(:get, "#{@base_path}/hid/orgs")
|> handle_hid_orgs_list_response()
end
@doc """
Registers a new HID Origo organization for the account.
Idempotent on `name` → `slug`: if an org with the derived slug already exists,
the server returns the existing record with status 200 instead of creating a new one.
## Parameters
* `params` - Map with:
* `:name` - Display name (required)
* `:full_address` - Full mailing address
* `:phone` - Contact phone number
* `:first_name` - Primary contact first name
* `:last_name` - Primary contact last name
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %HidOrg{}}` - Created (201) or existing (200)
* `{:error, reason, %HttpFailure{}}` - 422 on validation failure
"""
@spec create_hid_org(map(), keyword()) :: hid_org_result()
def create_hid_org(params, opts \\ []) do
with :ok <-
Params.require(params, [:name, :full_address, :phone, :first_name, :last_name]) do
opts[:client]
|> Client.request(:post, "#{@base_path}/hid/orgs", body: params)
|> handle_hid_org_response()
end
end
@doc """
Completes registration for an HID Origo organization (activate).
The server may return extra fields on the response (`already_completed: true` when
the org is already activated, `job_queued: true` when a registration job is
in flight). Those flags are not surfaced on the returned struct — inspect
`org.status` to determine activation state.
## Parameters
* `params` - Map with:
* `:email` - Email used to register the org
* `:password` - HID portal password
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %HidOrg{}}` - 200 OK (fresh, already-complete, or no-op)
* `{:error, reason, %HttpFailure{}}` - 404 if no org matches the email
"""
@spec activate_hid_org(map(), keyword()) :: hid_org_result()
def activate_hid_org(params, opts \\ []) do
with :ok <- Params.require(params, [:email, :password]) do
opts[:client]
|> Client.request(:post, "#{@base_path}/hid/orgs/activate", body: params)
|> handle_hid_org_response()
end
end
@doc """
Lists ledger items for the account, paginated and optionally date-filtered.
## Parameters
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
* `:page` - Page number (default: 1)
* `:per_page` - Results per page (default: 50, max: 100)
* `:start_date` - ISO8601 timestamp; filters items created on/after
* `:end_date` - ISO8601 timestamp; filters items created on/before
## Returns
* `{:ok, [%LedgerItem{}], pagination}` - List + pagination map
* `{:error, reason, %HttpFailure{}}` - 422 on bad date format
"""
@spec list_ledger_items(keyword()) :: ledger_items_result()
def list_ledger_items(opts \\ []) do
params =
%{}
|> maybe_add_param(:page, opts[:page])
|> maybe_add_param(:per_page, opts[:per_page])
|> maybe_add_param(:start_date, opts[:start_date])
|> maybe_add_param(:end_date, opts[:end_date])
opts[:client]
|> Client.request(:get, "#{@base_path}/ledger-items", params: params)
|> handle_ledger_items_list_response()
end
@doc """
Publishes a card template, moving it out of `draft` toward `ready` or `in-review`.
For Android+SEOS templates, the server also syncs the template to the HID portal as
part of publish. If that sync fails, the template is rolled back to `draft`
and this call returns `{:error, :validation_failed, failure}` with a
field-tagged error message in `failure.body_decoded["message"]`.
## Parameters
* `template_id` - The card template id to publish
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %CardTemplate.PublishResult{}}` - 200 OK with `{id, status}`. `status` is
`"publishing"` (already in flight), `"in-review"` (Apple queued), or `"ready"`
(Android, immediate).
* `{:error, reason, %HttpFailure{}}` - 404 if template missing, 422 on validation
failure or HID-sync failure (for Android+SEOS).
"""
@spec publish_template(String.t(), keyword()) :: publish_result()
def publish_template(template_id, opts \\ []) do
with :ok <- Params.require_present(template_id, :template_id) do
opts[:client]
|> Client.request(:post, "#{@base_path}/card-templates/#{template_id}/publish")
|> handle_publish_response()
end
end
@doc """
Reveals the SmartTap private key for a card template.
The SDK generates a fresh ephemeral P-256 keypair per call, submits the
public half, and decrypts the server's response client-side. The returned
`%SmartTapReveal{}` carries the decrypted PEM in `:private_key`; the
encrypted envelope is consumed internally and not exposed.
Each call uses a fresh keypair internally, so the server's single-use
enforcement on pubkey fingerprint is satisfied automatically.
## Parameters
* `template_id` - The card template id (must be SmartTap protocol with a
`smart_tap_key`)
* `opts` - Options:
* `:client` - Client struct (optional, defaults to config)
## Returns
* `{:ok, %SmartTapReveal{}}` - Reveal succeeded; `:private_key` holds the
plaintext SmartTap PEM
* `{:error, :decrypt_failed, body}` - HTTP succeeded but the returned
envelope didn't decrypt against the SDK-generated keypair (server-side
crypto drift or SDK bug)
* `{:error, :not_found, %HttpFailure{}}` - Template missing, not SmartTap,
or no `smart_tap_key`
* `{:error, :validation_failed, %HttpFailure{}}` - Server-side validation
failure
"""
@spec reveal_smart_tap(String.t(), keyword()) :: smart_tap_reveal_result()
def reveal_smart_tap(template_id, opts \\ []) do
with :ok <- Params.require_present(template_id, :template_id) do
{ec_priv, pub_pem} = Keyword.get_lazy(opts, :keypair, &SmartTap.generate_keypair/0)
opts[:client]
|> Client.request(
:post,
"#{@base_path}/card-templates/#{template_id}/smart-tap/reveal",
body: %{client_public_key: pub_pem}
)
|> handle_smart_tap_reveal_response(ec_priv)
end
end
# --- Private Helpers ---
defp handle_result_response({:ok, %HttpResponse{body_decoded: body}}) do
{:ok, CardTemplate.Result.from_response(body)}
end
defp handle_result_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_template_response({:ok, %HttpResponse{body_decoded: %{"is_pair" => true} = body}}) do
{:ok, CardTemplatePair.from_response(body)}
end
defp handle_template_response({:ok, %HttpResponse{body_decoded: body}}) do
{:ok, CardTemplate.from_response(body)}
end
defp handle_template_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_logs_response({:ok, %HttpResponse{body_decoded: body}}) do
events =
body
|> Map.get("logs", [])
|> Enum.map(&Event.from_response/1)
pagination = Map.get(body, "pagination", %{})
{:ok, events, pagination}
end
defp handle_logs_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_pairs_response({:ok, %HttpResponse{body_decoded: body}}) do
pairs =
body
|> Map.get("card_template_pairs", [])
|> Enum.map(&CardTemplatePair.Summary.from_response/1)
pagination = Map.get(body, "pagination", %{})
{:ok, pairs, pagination}
end
defp handle_pairs_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_pair_summary_response({:ok, %HttpResponse{body_decoded: body}}) do
{:ok, CardTemplatePair.Summary.from_response(body)}
end
defp handle_pair_summary_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_ios_preflight_response({:ok, %HttpResponse{body_decoded: body}}) do
{:ok, IosPreflight.from_response(body)}
end
defp handle_ios_preflight_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_landing_pages_list_response({:ok, %HttpResponse{body_decoded: pages}})
when is_list(pages) do
{:ok, Enum.map(pages, &LandingPage.from_response/1)}
end
defp handle_landing_pages_list_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_landing_page_response({:ok, %HttpResponse{body_decoded: body}}) do
{:ok, LandingPage.from_response(body)}
end
defp handle_landing_page_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_credential_profiles_list_response({:ok, %HttpResponse{body_decoded: profiles}})
when is_list(profiles) do
{:ok, Enum.map(profiles, &CredentialProfile.from_response/1)}
end
defp handle_credential_profiles_list_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_credential_profile_response({:ok, %HttpResponse{body_decoded: body}}) do
{:ok, CredentialProfile.from_response(body)}
end
defp handle_credential_profile_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_webhooks_list_response({:ok, %HttpResponse{body_decoded: body}}) do
webhooks =
body
|> Map.get("webhooks", [])
|> Enum.map(&Webhook.from_response/1)
pagination = Map.get(body, "pagination", %{})
{:ok, webhooks, pagination}
end
defp handle_webhooks_list_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_webhook_response({:ok, %HttpResponse{body_decoded: body}}) do
{:ok, Webhook.from_response(body)}
end
defp handle_webhook_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_webhook_delete_response({:ok, %HttpResponse{}}), do: :ok
defp handle_webhook_delete_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_hid_orgs_list_response({:ok, %HttpResponse{body_decoded: orgs}})
when is_list(orgs) do
{:ok, Enum.map(orgs, &HidOrg.from_response/1)}
end
defp handle_hid_orgs_list_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_hid_org_response({:ok, %HttpResponse{body_decoded: body}}) do
{:ok, HidOrg.from_response(body)}
end
defp handle_hid_org_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_ledger_items_list_response({:ok, %HttpResponse{body_decoded: body}}) do
items =
body
|> Map.get("ledger_items", [])
|> Enum.map(&LedgerItem.from_response/1)
pagination = Map.get(body, "pagination", %{})
{:ok, items, pagination}
end
defp handle_ledger_items_list_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_publish_response({:ok, %HttpResponse{body_decoded: body}}) do
{:ok, CardTemplate.PublishResult.from_response(body)}
end
defp handle_publish_response({:error, %HttpFailure{} = failure}) do
{:error, reason_from_failure(failure), failure}
end
defp handle_smart_tap_reveal_response({:ok, %HttpResponse{body_decoded: body}}, ec_priv) do
case SmartTap.decrypt_envelope(body["encrypted_private_key"], ec_priv) do
{:ok, plaintext} ->
{:ok, %{SmartTapReveal.from_response(body) | private_key: plaintext}}
{:error, reason} ->
{:error, reason, body}
end
end
defp handle_smart_tap_reveal_response({:error, %HttpFailure{} = failure}, _ec_priv) do
{:error, reason_from_failure(failure), failure}
end
defp reason_from_failure(%HttpFailure{reason: :unauthorized}), do: :unauthorized
defp reason_from_failure(%HttpFailure{reason: :forbidden}), do: :forbidden
defp reason_from_failure(%HttpFailure{reason: :not_found}), do: :not_found
defp reason_from_failure(%HttpFailure{reason: :conflict}), do: :conflict
defp reason_from_failure(%HttpFailure{reason: :unprocessable_entity}), do: :validation_failed
defp reason_from_failure(%HttpFailure{reason: :too_many_requests}), do: :rate_limited
defp reason_from_failure(%HttpFailure{reason: :timeout}), do: :timeout
defp reason_from_failure(%HttpFailure{reason: reason})
when reason in [:internal_server_error, :bad_gateway, :service_unavailable, :gateway_timeout, :server_error],
do: :server_error
defp reason_from_failure(%HttpFailure{}), do: :request_failed
defp maybe_add_param(params, _key, nil), do: params
defp maybe_add_param(params, key, value), do: Map.put(params, key, value)
end