berrypod/lib/simpleshop_theme/providers/provider.ex

107 lines
3.5 KiB
Elixir
Raw Normal View History

defmodule SimpleshopTheme.Providers.Provider do
@moduledoc """
Behaviour for POD provider integrations.
Each provider (Printify, Gelato, Prodigi, etc.) implements this behaviour
to provide a consistent interface for:
- Testing connections
- Fetching products
- Submitting orders
- Tracking order status
## Data Normalization
Providers return normalized data structures:
- Products are maps with keys: `title`, `description`, `provider_product_id`,
`images`, `variants`, `category`, `provider_data`
- Variants are maps with keys: `provider_variant_id`, `title`, `sku`, `price`,
`cost`, `options`, `is_enabled`, `is_available`
- Images are maps with keys: `src`, `position`, `alt`
"""
alias SimpleshopTheme.Products.ProviderConnection
@doc """
Returns the provider type identifier (e.g., "printify", "gelato").
"""
@callback provider_type() :: String.t()
@doc """
Tests the connection to the provider.
Returns `{:ok, info}` with provider-specific info (e.g., shop name)
or `{:error, reason}` if the connection fails.
"""
@callback test_connection(ProviderConnection.t()) :: {:ok, map()} | {:error, term()}
@doc """
Fetches all products from the provider.
Returns a list of normalized product maps.
"""
@callback fetch_products(ProviderConnection.t()) :: {:ok, [map()]} | {:error, term()}
@doc """
Submits an order to the provider for fulfillment.
Returns `{:ok, %{provider_order_id: String.t()}}` on success.
"""
@callback submit_order(ProviderConnection.t(), order :: map()) ::
{:ok, %{provider_order_id: String.t()}} | {:error, term()}
@doc """
Gets the current status of an order from the provider.
"""
@callback get_order_status(ProviderConnection.t(), provider_order_id :: String.t()) ::
{:ok, map()} | {:error, term()}
@doc """
Fetches shipping rates from the provider for the given products.
Takes the connection and the already-fetched product list (from fetch_products).
Returns normalized rate maps with keys: blueprint_id, print_provider_id,
country_code, first_item_cost, additional_item_cost, currency, handling_time_days.
Optional providers that don't support shipping rate lookup can skip this.
The sync worker checks `function_exported?/3` before calling.
"""
@callback fetch_shipping_rates(ProviderConnection.t(), products :: [map()]) ::
{:ok, [map()]} | {:error, term()}
@optional_callbacks [fetch_shipping_rates: 2]
@doc """
Returns the provider module for a given provider type.
Checks `:provider_modules` application config first, allowing test
overrides via Mox. Falls back to hardcoded dispatch.
"""
def for_type(type) do
case Application.get_env(:simpleshop_theme, :provider_modules, %{}) do
modules when is_map(modules) ->
case Map.get(modules, type) do
nil -> default_for_type(type)
module -> {:ok, module}
end
_ ->
default_for_type(type)
end
end
defp default_for_type("printify"), do: {:ok, SimpleshopTheme.Providers.Printify}
defp default_for_type("gelato"), do: {:error, :not_implemented}
defp default_for_type("prodigi"), do: {:error, :not_implemented}
defp default_for_type("printful"), do: {:error, :not_implemented}
defp default_for_type(type), do: {:error, {:unknown_provider, type}}
@doc """
Returns the provider module for a provider connection.
"""
def for_connection(%ProviderConnection{provider_type: type}) do
for_type(type)
end
end