- Extract fetch_setting/1 in Settings (4 callsites → 1 repo lookup) - Replace hardcoded load_stripe_config with registry-driven load_all - Adding new secrets is now a one-line @secret_registry entry - Mark DRY refactor plan as complete (all 8 items done) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
136 lines
3.9 KiB
Elixir
136 lines
3.9 KiB
Elixir
defmodule SimpleshopTheme.Stripe.Setup do
|
|
@moduledoc """
|
|
Handles Stripe account setup: key verification, automatic webhook
|
|
endpoint creation, and teardown.
|
|
"""
|
|
|
|
alias SimpleshopTheme.Settings
|
|
alias SimpleshopTheme.Secrets
|
|
|
|
require Logger
|
|
|
|
@webhook_events ["checkout.session.completed", "checkout.session.expired"]
|
|
|
|
@doc """
|
|
Verifies a Stripe API key by making a lightweight Balance API call.
|
|
"""
|
|
def verify_api_key(api_key) do
|
|
case Stripe.Balance.retrieve(%{}, api_key: api_key) do
|
|
{:ok, _balance} -> :ok
|
|
{:error, %Stripe.Error{message: message}} -> {:error, message}
|
|
{:error, _} -> {:error, "Could not connect to Stripe"}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Full setup flow: verify key, store it, create webhook endpoint if possible.
|
|
|
|
Returns:
|
|
- `{:ok, :webhook_created}` — key valid, webhook auto-created (production)
|
|
- `{:ok, :localhost}` — key valid, but URL is localhost so webhook skipped
|
|
- `{:error, message}` — key invalid or setup failed
|
|
"""
|
|
def connect(api_key) do
|
|
with :ok <- verify_api_key(api_key) do
|
|
Settings.put_secret("stripe_api_key", api_key)
|
|
|
|
case maybe_create_webhook(api_key) do
|
|
{:ok, result} ->
|
|
Secrets.load_all()
|
|
{:ok, result}
|
|
|
|
{:error, reason} ->
|
|
# Key is valid and stored, but webhook creation failed.
|
|
# Still load the key so checkout works (webhooks can be set up manually).
|
|
Secrets.load_all()
|
|
{:error, reason}
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Removes Stripe configuration and deletes the webhook endpoint from Stripe.
|
|
"""
|
|
def disconnect do
|
|
delete_existing_webhook()
|
|
|
|
for key <- ["stripe_api_key", "stripe_signing_secret", "stripe_webhook_endpoint_id"] do
|
|
Settings.delete_setting(key)
|
|
end
|
|
|
|
Application.delete_env(:stripity_stripe, :api_key)
|
|
Application.delete_env(:stripity_stripe, :signing_secret)
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Saves a manually-provided webhook signing secret (for dev mode / Stripe CLI).
|
|
"""
|
|
def save_signing_secret(signing_secret) do
|
|
Settings.put_secret("stripe_signing_secret", signing_secret)
|
|
Secrets.load_all()
|
|
end
|
|
|
|
@doc """
|
|
Returns the webhook URL for this app.
|
|
"""
|
|
def webhook_url do
|
|
"#{SimpleshopThemeWeb.Endpoint.url()}/webhooks/stripe"
|
|
end
|
|
|
|
@doc """
|
|
Returns true if the app is running on localhost (Stripe can't reach it).
|
|
"""
|
|
def localhost? do
|
|
url = SimpleshopThemeWeb.Endpoint.url()
|
|
uri = URI.parse(url)
|
|
uri.host in ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
|
|
end
|
|
|
|
defp maybe_create_webhook(api_key) do
|
|
if localhost?() do
|
|
{:ok, :localhost}
|
|
else
|
|
delete_existing_webhook()
|
|
create_webhook(api_key)
|
|
end
|
|
end
|
|
|
|
defp create_webhook(api_key) do
|
|
params = %{
|
|
url: webhook_url(),
|
|
enabled_events: @webhook_events
|
|
}
|
|
|
|
case Stripe.WebhookEndpoint.create(params, api_key: api_key) do
|
|
{:ok, endpoint} ->
|
|
Settings.put_secret("stripe_signing_secret", endpoint.secret)
|
|
Settings.put_setting("stripe_webhook_endpoint_id", endpoint.id, "string")
|
|
Logger.info("Stripe webhook endpoint created: #{endpoint.id}")
|
|
{:ok, :webhook_created}
|
|
|
|
{:error, %Stripe.Error{message: message}} ->
|
|
Logger.warning("Failed to create Stripe webhook: #{message}")
|
|
{:error, message}
|
|
|
|
{:error, _} ->
|
|
{:error, "Failed to create webhook endpoint"}
|
|
end
|
|
end
|
|
|
|
defp delete_existing_webhook do
|
|
endpoint_id = Settings.get_setting("stripe_webhook_endpoint_id")
|
|
api_key = Settings.get_secret("stripe_api_key")
|
|
|
|
if endpoint_id && api_key do
|
|
case Stripe.WebhookEndpoint.delete(endpoint_id, api_key: api_key) do
|
|
{:ok, _} ->
|
|
Logger.info("Deleted Stripe webhook endpoint: #{endpoint_id}")
|
|
|
|
{:error, reason} ->
|
|
Logger.warning("Failed to delete webhook endpoint #{endpoint_id}: #{inspect(reason)}")
|
|
end
|
|
end
|
|
end
|
|
end
|