# `PhoenixKitEcommerce.Policy`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ecommerce/blob/0.3.0/lib/phoenix_kit_ecommerce/policy.ex#L1)

Admin-controllable policy for the shop, in one place.

These are the knobs where a shop owner can legitimately want either
behaviour. Everything here is **secure by default** — the default value
is the safe one, and relaxing it is an explicit, logged admin decision
made on the E-Commerce settings page.

Genuine invariants are NOT here. "Only the owner of a billing profile
may attach it to an order" is not a preference, so it has no setting;
it is simply enforced.

## Keys

| Setting | Default | Effect of the default |
|---|---|---|
| `shop_order_lookup_policy` | `"strict"` | An order confirmation page requires the session that placed it |
| `shop_allow_raw_html_descriptions` | `"false"` | Product descriptions are HTML-sanitized before rendering |
| `shop_image_import_allow_private_networks` | `"false"` | CSV image import refuses loopback/private/link-local hosts |
| `shop_allow_svg_uploads` | `"false"` | SVG is rejected by the image importer |
| `shop_default_tax_country` | `""` | No fallback country; tax uses the address actually collected |
| `shop_import_cleanup_scope` | `"auto_created"` | Import cleanup only removes categories that import created |

Each reader tolerates a missing or malformed stored value by falling
back to the safe default rather than raising — a settings row that has
been hand-edited to nonsense must not take the storefront down, and
must not silently mean "permissive".

# `allow_raw_html_descriptions?`

```elixir
@spec allow_raw_html_descriptions?() :: boolean()
```

Whether product descriptions may contain raw, unsanitized HTML.

Defaults to `false`. When false the storefront and admin previews run
descriptions through the core HTML sanitizer.

Turning this on trusts **everyone who can write a product description**
with script execution in every shopper's browser — including the Owner's.
That is a wider blast radius than it looks: descriptions are also
populated by CSV import, so it extends trust to whoever supplies the
import file.

# `allow_svg_uploads?`

```elixir
@spec allow_svg_uploads?() :: boolean()
```

Whether SVG is accepted by the image importer.

Defaults to `false`. Stored files are served inline with their stored
MIME type, and an SVG can carry script — so an accepted SVG is a stored
XSS channel independent of the description sanitizer.

# `default_tax_country`

```elixir
@spec default_tax_country() :: String.t() | nil
```

Optional fallback country code used for tax when the cart has no
shipping or billing country yet.

Returns `nil` when unset (the default), meaning tax is computed only
from an address actually collected at checkout. Set it to e.g. `"EE"` if
your shop is single-jurisdiction and should charge tax even before the
customer supplies an address.

# `image_import_allow_private_networks?`

```elixir
@spec image_import_allow_private_networks?() :: boolean()
```

Whether the CSV image importer may fetch from private network ranges.

Defaults to `false`, which blocks loopback, private, link-local and
cloud metadata addresses. With it on, an admin-supplied CSV can make the
server fetch internal URLs and store the response — a working port
scanner and metadata-credential reader. Only enable it when importing
from a genuinely internal, trusted image host.

# `import_cleanup_scope`

```elixir
@spec import_cleanup_scope() :: :auto_created | :all_empty
```

Which categories the post-import cleanup step may delete.

* `:auto_created` (default) — only categories that the import itself
  created and left empty.
* `:all_empty` — every empty category in the catalog. This is what the
  code used to do regardless of the UI's promise, and it destroys
  deliberately-empty categories such as a "Coming Soon" placeholder.

# `legacy_cookie_window_open?`

```elixir
@spec legacy_cookie_window_open?(DateTime.t()) :: boolean()
```

Whether pre-signing shop-session cookies may still be adopted.

When the cookie became signed, every existing unsigned one stopped
verifying — so without a migration each guest silently lost their cart
on upgrade. `PhoenixKitEcommerce.Web.Plugs.ShopSession` therefore adopts
a legacy cookie once and re-issues it signed.

That path is a standing weakness: an unsigned value is replayable by
anyone who obtained it out-of-band, so it must not outlive the upgrade
it exists for. Set `shop_legacy_cookie_until` to an ISO8601 date (or
datetime) to close the window; after it passes, unsigned cookies are
refused outright and those visitors simply get a fresh cart.

Default: `nil`, meaning "still migrating". That is the compatible
default rather than the strict one — a shop that upgrades without
reading release notes must not lose customer carts — but it is the one
setting here where leaving the default forever is the wrong choice.
Adopted ids never authorize an order regardless of this setting.

# `order_lookup_policy`

```elixir
@spec order_lookup_policy() :: :strict | :link
```

How `/checkout/complete/:uuid` decides whether a visitor may see an order.

* `:strict` (default) — the visitor must own the order, or hold the shop
  session that placed it. A bare UUID is not enough.
* `:link` — knowing the UUID is enough. Only appropriate for a shop that
  deliberately mails "view your order" links and accepts that the URL is
  the credential: order URLs leak through Referer headers, browser
  history, and forwarded mail.

The old behaviour was `:link` for every guest order, without anyone
choosing it — and because guest checkout creates unconfirmed users, that
also covered every order belonging to a registered-but-unconfirmed user.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
