Plain text emails via Swoosh OrderNotifier module. Order confirmation triggered from Stripe webhook after payment, shipping notification from Printify shipment webhook with polling fallback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
120 lines
3.4 KiB
Elixir
120 lines
3.4 KiB
Elixir
defmodule SimpleshopTheme.Webhooks do
|
|
@moduledoc """
|
|
Handles incoming webhook events from POD providers.
|
|
"""
|
|
|
|
alias SimpleshopTheme.Orders
|
|
alias SimpleshopTheme.Orders.OrderNotifier
|
|
alias SimpleshopTheme.Products
|
|
alias SimpleshopTheme.Sync.ProductSyncWorker
|
|
alias SimpleshopTheme.Webhooks.ProductDeleteWorker
|
|
|
|
require Logger
|
|
|
|
@doc """
|
|
Handles a Printify webhook event.
|
|
|
|
Returns :ok or {:ok, job} on success, {:error, reason} on failure.
|
|
"""
|
|
|
|
# --- Product events ---
|
|
|
|
def handle_printify_event("product:updated", %{"id" => _product_id}) do
|
|
enqueue_product_sync()
|
|
end
|
|
|
|
def handle_printify_event("product:publish:started", %{"id" => _product_id}) do
|
|
enqueue_product_sync()
|
|
end
|
|
|
|
def handle_printify_event("product:deleted", %{"id" => product_id}) do
|
|
ProductDeleteWorker.enqueue(product_id)
|
|
end
|
|
|
|
# --- Order events ---
|
|
|
|
def handle_printify_event("order:sent-to-production", resource) do
|
|
with {:ok, order} <- find_order_from_resource(resource) do
|
|
Orders.update_fulfilment(order, %{
|
|
fulfilment_status: "processing",
|
|
provider_status: "in-production"
|
|
})
|
|
end
|
|
end
|
|
|
|
def handle_printify_event("order:shipment:created", resource) do
|
|
shipment = extract_shipment(resource)
|
|
|
|
with {:ok, order} <- find_order_from_resource(resource),
|
|
{:ok, updated_order} <-
|
|
Orders.update_fulfilment(order, %{
|
|
fulfilment_status: "shipped",
|
|
provider_status: "shipped",
|
|
tracking_number: shipment.tracking_number,
|
|
tracking_url: shipment.tracking_url,
|
|
shipped_at: DateTime.utc_now() |> DateTime.truncate(:second)
|
|
}) do
|
|
OrderNotifier.deliver_shipping_notification(updated_order)
|
|
{:ok, updated_order}
|
|
end
|
|
end
|
|
|
|
def handle_printify_event("order:shipment:delivered", resource) do
|
|
with {:ok, order} <- find_order_from_resource(resource) do
|
|
Orders.update_fulfilment(order, %{
|
|
fulfilment_status: "delivered",
|
|
provider_status: "delivered",
|
|
delivered_at: DateTime.utc_now() |> DateTime.truncate(:second)
|
|
})
|
|
end
|
|
end
|
|
|
|
# --- Catch-all ---
|
|
|
|
def handle_printify_event("shop:disconnected", _resource) do
|
|
Logger.warning("Printify shop disconnected - manual intervention needed")
|
|
:ok
|
|
end
|
|
|
|
def handle_printify_event(event_type, _resource) do
|
|
Logger.info("Ignoring unhandled Printify event: #{event_type}")
|
|
:ok
|
|
end
|
|
|
|
# --- Private helpers ---
|
|
|
|
defp enqueue_product_sync do
|
|
case Products.get_provider_connection_by_type("printify") do
|
|
nil -> {:error, :no_connection}
|
|
conn -> ProductSyncWorker.enqueue(conn.id)
|
|
end
|
|
end
|
|
|
|
# Printify order webhooks include external_id (our order_number) in the resource
|
|
defp find_order_from_resource(%{"external_id" => external_id}) when is_binary(external_id) do
|
|
case Orders.get_order_by_number(external_id) do
|
|
nil ->
|
|
Logger.warning("Order webhook: no order found for external_id=#{external_id}")
|
|
{:error, :order_not_found}
|
|
|
|
order ->
|
|
{:ok, order}
|
|
end
|
|
end
|
|
|
|
defp find_order_from_resource(resource) do
|
|
Logger.warning("Order webhook: missing external_id in resource #{inspect(resource)}")
|
|
{:error, :missing_external_id}
|
|
end
|
|
|
|
defp extract_shipment(resource) do
|
|
shipments = resource["shipments"] || []
|
|
shipment = List.last(shipments) || %{}
|
|
|
|
%{
|
|
tracking_number: shipment["tracking_number"],
|
|
tracking_url: shipment["tracking_url"]
|
|
}
|
|
end
|
|
end
|