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
post("/v2/shipping-rates", %{recipient: recipient, items: items})
post("/v2/shipping-rates", %{recipient: recipient, order_items: items})
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),
: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(
"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 =
target_countries()
|> Enum.with_index()
|> Enum.flat_map(fn {country_code, index} ->
if index > 0, do: Process.sleep(100)
fetch_rates_for_country(variant_ids, country_code, products)
end)
for {catalog_product_id, variant_id} <- product_items,
{country_code, _index} <- Enum.with_index(target_countries()),
reduce: [] do
acc ->
if acc != [], do: Process.sleep(100)
acc ++ fetch_rate_for_product(catalog_product_id, variant_id, country_code)
end
{:ok, rates}
else
@ -193,30 +196,23 @@ defmodule SimpleshopTheme.Providers.Printful do
["GB", "US", "DE", "FR", "CA", "AU", "IE", "NL", "AT", "BE"]
end
defp fetch_rates_for_country(variant_ids, country_code, products) do
items =
Enum.map(variant_ids, fn vid ->
%{source: "catalog", catalog_variant_id: vid, quantity: 1}
end)
defp fetch_rate_for_product(catalog_product_id, variant_id, country_code) do
items = [%{source: "catalog", catalog_variant_id: variant_id, quantity: 1}]
case Client.calculate_shipping(%{country_code: country_code}, items) do
{:ok, rates} when is_list(rates) ->
# Take the STANDARD rate (cheapest)
standard = Enum.find(rates, &(&1["id"] == "STANDARD")) || List.first(rates)
standard = Enum.find(rates, &(&1["shipping"] == "STANDARD")) || List.first(rates)
if standard do
catalog_product_id = extract_first_catalog_product_id(products)
rate_cents = parse_price(standard["rate"])
[
%{
blueprint_id: catalog_product_id,
print_provider_id: 0,
country_code: country_code,
first_item_cost: rate_cents,
first_item_cost: parse_price(standard["rate"]),
additional_item_cost: 0,
currency: String.upcase(standard["currency"] || "USD"),
handling_time_days: standard["maxDeliveryDays"]
handling_time_days: standard["max_delivery_days"]
}
]
else
@ -232,33 +228,24 @@ defmodule SimpleshopTheme.Providers.Printful do
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
|> Enum.flat_map(fn product ->
provider_data = product[:provider_data] || %{}
catalog_product_id =
provider_data[:catalog_product_id] || provider_data["catalog_product_id"]
catalog_variant_ids =
provider_data[:catalog_variant_ids] || provider_data["catalog_variant_ids"] || []
if catalog_variant_ids == [] do
# Fall back to extracting from raw data
raw = provider_data[:raw] || provider_data["raw"] || %{}
sync_variants = raw["sync_variants"] || raw[:sync_variants] || []
Enum.map(sync_variants, fn sv -> sv["variant_id"] || sv[:variant_id] end)
else
catalog_variant_ids
case {catalog_product_id, catalog_variant_ids} do
{nil, _} -> []
{_, []} -> []
{cpid, [vid | _]} -> [{cpid, vid}]
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
# =============================================================================
@ -310,6 +297,9 @@ defmodule SimpleshopTheme.Providers.Printful do
provider_data: %{
catalog_product_id: catalog_product_id,
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"],
options: build_option_types(sync_variants),
raw: %{sync_product: sync_product}

View File

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