Gelato setup — story-as-merch posters (Phase 0)
Story-as-merch (spec) prints posters of a story's cover art via Gelato (print-on-demand, no inventory). Phase 0 is author-at-cost self-orders of a single 13×19″ poster from the season page.
Until GELATO_API_KEY is set the feature is safe but inert: an order still takes payment
via Stripe and parks the row at awaiting_fulfillment (no print is placed). Set the env to
activate fulfillment.
Environment variables
| Var | Required | What it is |
|---|---|---|
GELATO_API_KEY | to fulfill | Gelato API key (dashboard → API Keys). Without it, paid orders park at awaiting_fulfillment. |
GELATO_POSTER_13X19_UID | recommended | Gelato product UID for the 13×19″ poster SKU. The code default is a placeholder — confirm the real UID from Gelato's product catalog. |
GELATO_POSTER_13X19_BASE_CENTS | optional | POD base cost snapshot in cents (default 1200). Phase 1 replaces this with live quoting. |
GELATO_POSTER_SHIPPING_CENTS | optional | Flat shipping snapshot in cents (default 800). Phase 1 quotes live shipping per address. |
MERCH_MARGIN_PERCENT | optional | Fan-storefront markup over POD base cost, percent (default 40). |
MERCH_MARGIN_MIN_CENTS | optional | Absolute floor on the markup so cheap items still clear (default 500 = $5). |
MERCH_DEFAULT_CREATOR_PCT | optional | Creator's share of the markup, percent (default 50). Per-creator override: creator_revenue_splits.merch_creator_pct. |
GELATO_{TEE,HOODIE,MUG,HAT}_UID | recommended | Gelato product UIDs for the apparel/drinkware SKUs. Code defaults are placeholders — confirm each from Gelato's catalog. |
GELATO_{TEE,HOODIE,MUG,HAT}_BASE_CENTS | optional | POD base cost per SKU (defaults: tee 1500, hoodie 3000, mug 900, cap 1800). |
GELATO_{TEE,HOODIE,MUG,HAT}_SHIPPING_CENTS | optional | Flat shipping per SKU (defaults: tee 500, hoodie 700, mug 600, cap 600). |
Reuses already-configured infra: REPLICATE_API_TOKEN (the 4× cover upscale —
print-asset.ts) and
STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET (checkout + the merch_order webhook dispatch).
Activation steps
- Apply migration 426 (
merch_orders+creator_revenue_splits.merch_creator_pct) to prod. - Create a Gelato account; generate an API key → set
GELATO_API_KEYon the backend (Railway). - Find the 13×19″ poster product UID in Gelato's catalog → set
GELATO_POSTER_13X19_UID. Set the cost/shipping env vars to match what Gelato charges. - As a story owner, open your season page → Make it real → Order a poster → complete
Stripe Checkout. Verify the
merch_ordersrow reaches a Gelato status and the upscaled print asset looks clean at poster size (the print-resolution quality gate — spec §2).
How it flows
Author at-cost (POST /api/merch/:seasonId/self-checkout, owner only) and fan storefront
(POST /api/merch/:seasonId/checkout, anyone incl. guests, public seasons only) both →
Stripe Checkout (collects shipping address) → checkout.session.completed webhook →
fulfillMerchOrder() upscales the cover to print resolution, then places the Gelato order (or
parks it if GELATO_API_KEY is unset). Fan orders also book the creator's margin share to
creator_earnings and pass a pre-sale cover-moderation gate (gpt-4o-mini vision).