wire Printful shipping rates into cart calculation

Add blueprint_id and print_provider_id to Printful provider_data so the
generic shipping calculator can look up rates. Fix v2 API request format
(order_items key) and response field names. Fetch one representative
variant per product to get accurate per-item rates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey 2026-02-15 09:15:47 +00:00
parent 3c788bff78
commit 0cfcb2448e
3 changed files with 33 additions and 39 deletions

View File

@ -187,7 +187,7 @@ defmodule SimpleshopTheme.Clients.Printful do
) )
""" """
def calculate_shipping(recipient, items) do def calculate_shipping(recipient, items) do
post("/v2/shipping-rates", %{recipient: recipient, items: items}) post("/v2/shipping-rates", %{recipient: recipient, order_items: items})
end end
# ============================================================================= # =============================================================================

View File

@ -168,19 +168,22 @@ defmodule SimpleshopTheme.Providers.Printful do
with api_key when is_binary(api_key) <- ProviderConnection.get_api_key(conn), with api_key when is_binary(api_key) <- ProviderConnection.get_api_key(conn),
:ok <- set_credentials(api_key, store_id) do :ok <- set_credentials(api_key, store_id) do
variant_ids = extract_catalog_variant_ids(products) # Build per-product items: one representative variant per product
product_items = extract_per_product_items(products)
Logger.info( Logger.info(
"Fetching Printful shipping rates for #{length(variant_ids)} catalogue variants" "Fetching Printful shipping rates for #{length(product_items)} product(s), " <>
"#{length(target_countries())} countries"
) )
rates = rates =
target_countries() for {catalog_product_id, variant_id} <- product_items,
|> Enum.with_index() {country_code, _index} <- Enum.with_index(target_countries()),
|> Enum.flat_map(fn {country_code, index} -> reduce: [] do
if index > 0, do: Process.sleep(100) acc ->
fetch_rates_for_country(variant_ids, country_code, products) if acc != [], do: Process.sleep(100)
end) acc ++ fetch_rate_for_product(catalog_product_id, variant_id, country_code)
end
{:ok, rates} {:ok, rates}
else else
@ -193,30 +196,23 @@ defmodule SimpleshopTheme.Providers.Printful do
["GB", "US", "DE", "FR", "CA", "AU", "IE", "NL", "AT", "BE"] ["GB", "US", "DE", "FR", "CA", "AU", "IE", "NL", "AT", "BE"]
end end
defp fetch_rates_for_country(variant_ids, country_code, products) do defp fetch_rate_for_product(catalog_product_id, variant_id, country_code) do
items = items = [%{source: "catalog", catalog_variant_id: variant_id, quantity: 1}]
Enum.map(variant_ids, fn vid ->
%{source: "catalog", catalog_variant_id: vid, quantity: 1}
end)
case Client.calculate_shipping(%{country_code: country_code}, items) do case Client.calculate_shipping(%{country_code: country_code}, items) do
{:ok, rates} when is_list(rates) -> {:ok, rates} when is_list(rates) ->
# Take the STANDARD rate (cheapest) standard = Enum.find(rates, &(&1["shipping"] == "STANDARD")) || List.first(rates)
standard = Enum.find(rates, &(&1["id"] == "STANDARD")) || List.first(rates)
if standard do if standard do
catalog_product_id = extract_first_catalog_product_id(products)
rate_cents = parse_price(standard["rate"])
[ [
%{ %{
blueprint_id: catalog_product_id, blueprint_id: catalog_product_id,
print_provider_id: 0, print_provider_id: 0,
country_code: country_code, country_code: country_code,
first_item_cost: rate_cents, first_item_cost: parse_price(standard["rate"]),
additional_item_cost: 0, additional_item_cost: 0,
currency: String.upcase(standard["currency"] || "USD"), currency: String.upcase(standard["currency"] || "USD"),
handling_time_days: standard["maxDeliveryDays"] handling_time_days: standard["max_delivery_days"]
} }
] ]
else else
@ -232,33 +228,24 @@ defmodule SimpleshopTheme.Providers.Printful do
end end
end end
defp extract_catalog_variant_ids(products) do # Returns {catalog_product_id, first_catalog_variant_id} per product
defp extract_per_product_items(products) do
products products
|> Enum.flat_map(fn product -> |> Enum.flat_map(fn product ->
provider_data = product[:provider_data] || %{} provider_data = product[:provider_data] || %{}
catalog_product_id =
provider_data[:catalog_product_id] || provider_data["catalog_product_id"]
catalog_variant_ids = catalog_variant_ids =
provider_data[:catalog_variant_ids] || provider_data["catalog_variant_ids"] || [] provider_data[:catalog_variant_ids] || provider_data["catalog_variant_ids"] || []
if catalog_variant_ids == [] do case {catalog_product_id, catalog_variant_ids} do
# Fall back to extracting from raw data {nil, _} -> []
raw = provider_data[:raw] || provider_data["raw"] || %{} {_, []} -> []
sync_variants = raw["sync_variants"] || raw[:sync_variants] || [] {cpid, [vid | _]} -> [{cpid, vid}]
Enum.map(sync_variants, fn sv -> sv["variant_id"] || sv[:variant_id] end)
else
catalog_variant_ids
end end
end) end)
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
end
defp extract_first_catalog_product_id(products) do
products
|> Enum.find_value(fn product ->
provider_data = product[:provider_data] || %{}
provider_data[:catalog_product_id] || provider_data["catalog_product_id"]
end) || 0
end end
# ============================================================================= # =============================================================================
@ -310,6 +297,9 @@ defmodule SimpleshopTheme.Providers.Printful do
provider_data: %{ provider_data: %{
catalog_product_id: catalog_product_id, catalog_product_id: catalog_product_id,
catalog_variant_ids: catalog_variant_ids, catalog_variant_ids: catalog_variant_ids,
# Shipping calc uses these generic keys (shared with Printify)
blueprint_id: catalog_product_id,
print_provider_id: 0,
thumbnail_url: sync_product["thumbnail_url"], thumbnail_url: sync_product["thumbnail_url"],
options: build_option_types(sync_variants), options: build_option_types(sync_variants),
raw: %{sync_product: sync_product} raw: %{sync_product: sync_product}

View File

@ -178,6 +178,8 @@ defmodule SimpleshopTheme.Providers.PrintfulTest do
# Provider data # Provider data
assert normalized.provider_data.catalog_product_id == 71 assert normalized.provider_data.catalog_product_id == 71
assert normalized.provider_data.blueprint_id == 71
assert normalized.provider_data.print_provider_id == 0
assert is_list(normalized.provider_data.options) assert is_list(normalized.provider_data.options)
assert length(normalized.provider_data.catalog_variant_ids) == 4 assert length(normalized.provider_data.catalog_variant_ids) == 4
end end
@ -357,6 +359,8 @@ defmodule SimpleshopTheme.Providers.PrintfulTest do
provider_data: %{ provider_data: %{
catalog_product_id: catalog_product_id, catalog_product_id: catalog_product_id,
catalog_variant_ids: catalog_variant_ids, catalog_variant_ids: catalog_variant_ids,
blueprint_id: catalog_product_id,
print_provider_id: 0,
thumbnail_url: sync_product["thumbnail_url"], thumbnail_url: sync_product["thumbnail_url"],
options: build_option_types(sync_variants), options: build_option_types(sync_variants),
raw: %{sync_product: sync_product} raw: %{sync_product: sync_product}