25 service files in packages/merch/srv/src/ (package @zooly/merch-srv) covering the full backend logic. Payment integration is handled by @zooly/srv-stripe-payment.
Sequential generation with character consistency scoring and user-driven regeneration:
generateAIArt() (FAL.AI)downloadAndProcessAIImage(), upload to S3, then score with validateCharacterConsistency() (Gemini vision)overall_likeness_score ≥ threshold (default 60), return immediatelyMAX_GENERATION_ATTEMPTS (default 2), always keeping the best resultaiArtKey and all attempts as candidatesaiArtCandidatesThe client shows all accumulated candidates (newest first) and lets the fan select their preferred art via POST /api/merch/ai/select, which updates the session's aiArtKey to the chosen candidate.
7 modules for post-processing AI output:
| Module | Function |
|---|---|
flood-fill-mask.ts | BFS flood fill for background detection |
gaussian-blur-mask.ts | Separable Gaussian blur (OpenCV-compatible) |
apply-edge-feather.ts | Smoothstep edge fade |
remove-background-corners.ts | Corner flood-fill background removal with feathering |
composite-overlay.ts | Square crop + overlay compositing |
flatten-alpha.ts | Flatten transparent PNGs to black background |
process-ai-image.ts | Full pipeline: download, BG removal, crop, overlay |
| File | Purpose |
|---|---|
fal-retry.ts | Client config + retry with exponential backoff |
ai-image-service.ts | Calls fal.ai to generate AI art from selfie + template |
Gemini vision likeness scoring with optional SAM segmentation. Returns overall_likeness_score (0-1).
Mode selected by MERCH_MOCKUP_MODE env var:
Composite mode (default):
apparelPrintBlend(): Gaussian blur, edge-feather mask, brightness/saturation adjustmentcompositeOnGarment(): Template from garment templates, print area ratios (tshirt: 50% width centered, hoodie: 50% width centered slightly lower)AI try-on mode:
5-layer Sharp composition:
.composite() all layers to PNG10-step idempotent pipeline:
resolvePlaquePricing(), non-plaque uses product.priceYour Order Confirmation - {orderNumber} (DEMO/TEST prefix for non-live)support@zooly.aimerch-orders@zooly.ai (customer orders only)Sent when AI generation takes >90s and fan provides email.
isOpen = status === "LIVE" AND shutdownMode === "NONE"
isSoftClosing = shutdownMode === "SOFT_CLOSE" AND NOT graceExpired
isSoftCloseGraceExpired = shutdownMode === "SOFT_CLOSE" AND shutdownEndsAt < now()
isEmergencyClosed = shutdownMode === "EMERGENCY_CLOSE"
isEnded = status === "ENDED"
isCheckoutBlocked = isEnded OR isEmergencyClosed OR isSoftCloseGraceExpired
| Action | Required State | Result |
|---|---|---|
openStore | DRAFT | LIVE, isActive=true |
reopenStore | ENDED or EMERGENCY_CLOSE | LIVE, isActive=true |
startSoftClose | LIVE, NONE | SOFT_CLOSE + 10min grace |
cancelSoftClose | LIVE, SOFT_CLOSE | NONE |
emergencyClose | LIVE | EMERGENCY_CLOSE |
endActivation | LIVE | ENDED, isActive=false |
SOFT_CLOSE_GRACE_MINUTES = 10
Gemini 2.5 Flash via Vercel AI SDK generateObject(). Checks:
personDetected: Is there a clearly visible human face?isBlurry: Is the image too blurry/poorly lit for merchandise?isNudity: Is the image inappropriate? (includes swimwear, shirtless, bare midriff)isCelebrity: Can you identify this person as a specific public figure?Step order map:
studio/review/upload-verify/camera = 1
merch = 2, size = 3, loading = 4, result = 5
shipping/payment = 6, confirm = 7
Updates maxStepReached when current step exceeds previous max. Appends to stepTimeline JSON array. Tracks milestones: generationStartedAt, generationCompletedAt, checkoutStartedAt, completedAt, abandonedAt.
| Function | Purpose |
|---|---|
getShippingCost(country, flatRate, intlRate) | US/CA get flat rate, others get intl rate |
formatCurrency(amount, currency) | Intl.NumberFormat formatting |
generateOrderNumber() | ORD-{timestamp_base36}-{random_base36} |
resolvePlaquePricing(config, sizeLabel) | Standard/deluxe pricing lookup with fallbacks |
| Constant | Value |
|---|---|
DEFAULT_SIZES | S, M, L, XL, XXL, XXXL |
DEFAULT_SHIPPING_RATE | $6.95 |
DEFAULT_SHIPPING_INTL_RATE | $15.99 |
| Default product prices | tshirt: $69, hoodie: $109, plaque: $39 |
DOMESTIC_SHIPPING_COUNTRIES | US, CA |
DEFAULT_PAYMENT_METHODS | card, apple_pay, google_pay |
On This Page
OverviewAI Art GenerationgenerateConsistentAIArt (generate-ai-art.ts)Image Processing Pipeline (img-processing/)FAL.AI Integration (fal/)Character Consistency (validate-character-consistency.ts)Mockup GenerationgenerateMockup (generate-mockup.ts)Plaque RenderingrenderPlaqueComposite (render-plaque.ts)Order CreationcreateOrGetMerchOrderFromSession (create-order.ts)EmailsendMerchOrderConfirmationEmail (send-email.ts)sendMerchDelayedReadyEmail (send-email.ts)Campaign Lifecycle FSMState Derivation (activation-lifecycle.ts)Valid TransitionsImage VerificationverifyImageWithGemini (verify-image.ts)AnalyticsSession Analytics (analytics.ts)Utility Functions (utils.ts)Constants (constants.ts)