-
Requires login + emailVerified (M.10). Order matters and each step undoes the previous one on failure: moderate (T26) before anything else exists, so a rejected image leaves nothing behind; create the garments row (source: user_paid) before spending, so the ledger entry can carry relatedAssetId — a failed spend deletes it back out; generation submission failure refunds and deletes it too. Reuses T8/ T9's submitGeneration whole, same M.7 dedupe cache, then rides the same danceQueue/ensureDanceSchedule machinery /api/dance/pick (mode 1) already uses — a paid entry only differs by carrying userId/ amountPaid, which mode 1's free entries don't set. Async generation failure (job fails after this handler already responded) is refunded from ensureDanceSchedule's existing "generating -> failed" tick, gated on a findByRelatedAssetAndReason lookup so a crash between that write and saveDanceState() persisting it can't double-refund — a duplicate here is real money, not a cosmetic bug. dance-mode2.test.js covers the full chain against a mock H3 Console: login/verification/balance gates, a non-image upload rejected before anything is spent, the happy path (spend -> auto-tag -> generate -> lands in dance schedule, garments row has user_paid/ownerUserId/ appearanceDescriptor), and — via a second server pointed at a mock H3 that always fails submission — the async refund path, redelivered twice to prove it's exactly-once. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DUiYs6UjxDMkfvnjtsKFo