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_stripe_config() {: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_stripe_config() {: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_stripe_config() 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