berrypod/docs/plans/legal-page-generator.md

238 lines
10 KiB
Markdown
Raw Normal View History

# Legal page generator
> Status: Planned
> Tasks: #8385 in PROGRESS.md
> Tier: 4 (Growth & content), Phase 1 can ship earlier
## Goal
Replace the static `PreviewData` placeholder content on the four policy/legal pages with generated content that's factually accurate for each shop — because Berrypod knows exactly what it does, who processes what data, and how fulfilment works.
Not a generic "fill in the blanks" template. Not LLM-generated waffle. A set of conditional paragraph functions that produce correct, shop-specific content from actual settings and provider data.
## The problem with current approach
All four content pages (`/privacy`, `/terms`, `/delivery`, `/about`) call `PreviewData.*_content()` which returns hardcoded placeholder text. Shop owners are expected to replace it manually — but most won't, or will copy-paste something generic that doesn't match how Berrypod actually works.
Berrypod already knows:
- Which providers are connected (Printify, Printful — each with different lead times)
- Which countries it ships to (from the `shipping_rates` table)
- Whether VAT is enabled and the shop country
- Whether abandoned cart recovery is enabled
- Whether a newsletter is enabled
- The shop name and contact email
This is enough to produce accurate, legally grounded content automatically.
## Pages covered
### 1. Privacy policy (`/privacy`)
**Always included:**
- What's collected: name, email, shipping address from orders (legal basis: contract performance, Article 6(1)(b) UK/EU GDPR)
- Payment: processed by Stripe, card data never touches the shop
- Analytics: privacy-first, no cookies, no personal data stored — server-side only, includes device type, country (derived from IP, not stored), referrer
- Cookies: session cookie for cart and auth (strictly necessary, no consent required); country preference cookie for shipping rates. No tracking cookies, no third-party analytics cookies.
- Sharing: shipping details shared with the connected provider(s) — names dynamically inserted
- Retention: order data kept for 7 years (UK statutory accounting requirement); analytics data kept for 2 years
- Contact: shop contact email from settings
- Rights: right of access, rectification, deletion (with caveat: statutory retention periods apply), right to object to marketing
**Conditional sections:**
- Abandoned cart recovery enabled → "If you enter your email on our checkout page but don't complete payment, we may send you a single follow-up email. This is the only email you'll receive. You can unsubscribe at any time using the link in the email. We delete this data after 30 days." (UK PECR soft opt-in / EU legitimate interests — depending on shop country)
- Newsletter enabled → email marketing section: subscription basis, how to unsubscribe, no third-party sharing
- Stripe Tax enabled → "Tax calculation is handled by Stripe, which processes transaction and location data to determine applicable rates."
**Shop country drives jurisdiction language:**
- UK → "under UK GDPR and PECR"
- EU country → "under the EU General Data Protection Regulation (GDPR)"
- US, AU, other → generic "applicable data protection laws"
---
### 2. Delivery & returns (`/delivery`)
This is the most data-rich page — Berrypod has real numbers from the DB.
**Production lead times** — driven by connected provider(s):
| Provider | Typical production |
|----------|--------------------|
| Printify | 27 business days |
| Printful | 25 business days |
If both providers are connected, show combined note ("production times vary by product").
**Shipping destinations** — derived from `shipping_rates` table:
- Query distinct countries with rates → group into regions → list with approximate delivery windows
- If no shipping data: generic placeholder (fallback)
**Returns — POD-specific and legally accurate:**
This is where generic templates get it wrong. The correct position for print-on-demand:
- Consumer Contracts Regulations Regulation 28(1)(b) — "goods made to the consumer's specifications or clearly personalised" are **exempt** from the 14-day statutory right to cancel. Every POD product qualifies. This is the legal exemption that applies, and most shops don't cite it correctly.
- Consumer Rights Act 2015 still applies to defective goods — if the item arrives damaged or with a printing defect, the customer is entitled to a repair, replacement, or refund.
- The generated policy states this clearly: no change-of-mind returns (citing the exemption), but reprints/refunds for defects (citing CRA).
**Contact and cancellation window:**
- Contact email from settings
- Cancellation window: ~2 hours after ordering (before production begins)
---
### 3. Terms of service (`/terms`)
**Always included:**
- Governing law: driven by shop country setting
- UK → "English law"
- Ireland → "Irish law and EU regulations"
- etc.
- Products: made to order, colour variance disclaimer, all sales final (with returns caveat)
- Payment: via Stripe, orders only confirmed on successful payment
- Intellectual property: designs are the property of the shop owner; customers receive a licence for personal use
- Limitations: we're not liable for delays caused by the print provider or postal service
- Changes: terms may be updated, current version always at this URL
**Conditional:**
- VAT enabled + registered → "prices include VAT where applicable"
- Newsletter → marketing communications clause
---
### 4. Cookie policy
Currently a section within the privacy policy. Can be a standalone page if desired, or remain embedded.
**Berrypod's actual cookies (exhaustive):**
| Cookie | Purpose | Duration | Consent required? |
|--------|---------|----------|-------------------|
| `_berrypod_session` | Session state: cart contents, auth | Session | No — strictly necessary |
| `country_code` | Remember shipping country preference | 1 year | No — strictly necessary for service |
That's it. No analytics cookies. No tracking. No third-party embeds. The generated cookie policy is short and accurate.
---
### 5. About page
Not generated — it's the shop owner's own story, Berrypod can't write that for them. But the existing placeholder template should be clearly labelled as placeholder and easy to replace. The page editor (task #19) handles this properly. No changes needed here for Phase 1.
---
## Content block format
The generator produces lists of `content_blocks` in the format already used by `<.rich_text>`:
```elixir
[
%{type: :lead, text: "..."},
%{type: :heading, text: "..."},
%{type: :paragraph, text: "..."},
%{type: :list, items: ["...", "..."]},
%{type: :closing, text: "..."}
]
```
No changes to the template layer — it already knows how to render these. The generator just produces better data.
---
## Generator module
`lib/berrypod/legal_pages.ex` — one public function per page:
```elixir
defmodule Berrypod.LegalPages do
alias Berrypod.{Settings, Shipping, Providers}
def privacy_content do
shop_name = Settings.get(:shop_name) || "this shop"
contact_email = Settings.get(:contact_email)
shop_country = Settings.get(:shop_country, "GB")
abandoned_cart_enabled = Settings.get(:abandoned_cart_enabled, false)
newsletter_enabled = Settings.get(:newsletter_enabled, false)
base_sections()
|> maybe_add_abandoned_cart(abandoned_cart_enabled)
|> maybe_add_newsletter(newsletter_enabled)
|> add_jurisdiction(shop_country)
|> add_contact(shop_name, contact_email)
end
def delivery_content do
providers = Providers.connected_providers()
shipping_countries = Shipping.list_countries_with_rates()
production_section(providers)
++ shipping_section(shipping_countries)
++ returns_section()
++ cancellation_section()
end
def terms_content do
shop_name = Settings.get(:shop_name) || "this shop"
shop_country = Settings.get(:shop_country, "GB")
vat_enabled = Settings.get(:vat_enabled, false)
base_terms(shop_name)
|> add_governing_law(shop_country)
|> maybe_add_vat_clause(vat_enabled)
end
end
```
---
## Two phases
### Phase 1 — replace PreviewData (no page editor needed)
Wire `LegalPages.*_content()` into the existing `Content` LiveView, replacing the three `PreviewData.*_content()` calls for privacy, delivery, and terms. The about page stays as-is (it's the shop owner's story).
The generated content shows in the live shop immediately. No admin UI needed yet — the content is always accurate because it reflects real settings.
### Phase 2 — page editor integration
When the page editor (task #19) ships, add:
- "Regenerate from settings" button per page — reruns the generator and replaces stored content
- Content marked as "auto-generated" vs "customised" — so the admin can tell what's been manually edited
- Generator runs automatically when relevant settings change (provider connected, VAT toggled, abandoned cart enabled) — a PubSub broadcast triggers regeneration
---
## What the generator is and isn't
**Is:**
- Factually accurate based on real Berrypod behaviour
- Legally grounded (cites correct UK statutes: PECR, Consumer Contracts Regulations, Consumer Rights Act)
- Useful as a starting point that's better than any generic template
**Isn't:**
- Legal advice. The generated pages include a brief footer note: "This policy was auto-generated based on how this shop is configured. You should review it and seek independent legal advice if you're unsure."
- Comprehensive for edge cases (international VAT registration, non-UK statutory frameworks beyond GDPR)
- A substitute for a solicitor if the shop does complex things
---
## Files to create/modify
- `lib/berrypod/legal_pages.ex` — new, generator functions for each page
- `lib/berrypod_web/live/shop/content.ex` — replace three `PreviewData.*_content()` calls with `LegalPages.*_content()`
- Phase 2: page editor admin UI for saved/regenerated page content
---
## Tasks
| # | Task | Est |
|---|------|-----|
| 83 | `LegalPages` module — generate accurate privacy, delivery, and terms content from settings + provider + shipping data | 2.5h |
| 84 | Wire `LegalPages` into `Content` LiveView — replace `PreviewData` calls, add tests | 45m |
| 85 | Page editor integration — "Regenerate" button, auto-regenerate on settings change, customised vs auto label | 1.5h (depends on task #19) |