The Three Apps · App #2

ThisOrThat — the game marketplace

ToT is the buyer-facing marketplace where Flip and Pull plays happen. Buyers pick cards, choose a game, and a provably-fair RNG decides their final price — but they always get a card. The operator-side is admin-only; everything below the buyer surface (listings, plays, settlement) is automated.

Public surfaces

PageForWhat's there
/AnyoneLanding page — sells the games to first-time visitors. Live Flip + Pull simulation panels.
/browseBuyersThe marketplace grid. Faceted filter rail, search combobox, card detail slide-overs, Lots/Singles toggle.
/listing/[id]BuyersIndividual listing detail. Flip game UI for solo singles; Pull-only banner for sub-$15.
/flip-cartBuyersMulti-card cart for combined flip plays
/pullBuyersPull stack assembly + commit page
/play/[id]BuyersResult page after a play — outcome reveal + provably-fair verification
/simulationAnyoneAuto-playing demo of Flip and Pull. Tabbed view, real RNG, fake cards.
/embed/simulationIframeChromeless version of /simulation for cross-site embedding

Operator surfaces (admin-only)

PageWhat's there
/collectionStaged cards from SlabTrack — CollectionCard rows. Operator can publish singles or lots manually (rarely needed since auto-publish landed).
/sellerSales dashboard — all orders, ship-label printing, refund trigger
/adminAdmin home — system health, listings, plays, orders
/admin/contentListings + ad attribution + simulation seeding
/admin/simulationDemo Theatre — run buyer + operator scenarios in real-time

The data model

TablePurpose
UserBuyers + the platform admin. Auth via JWT shared with SlabTrack.
CollectionCardCards staged from SlabTrack via the ingest webhook. Has bundle metadata (transferCode, bundleId, lotKind).
CardPure card record. Joined to CollectionCard via slabtrackId.
ListingAn active marketplace listing. Has type FLIP/PICK_ONE/GAME_DAY, status ACTIVE/COMPLETED, theme metadata, lotKind.
FlipConfigPer-listing pricing — cardValue, spread, winPrice, losePrice.
PullA buyer's pull commit — selectedListingIds, pullPrice, seedHash, solanaTx, status.
PlayGeneric play record (Flip or Pull) with outcome, payment intent, settlement info.
OrderFinal purchase record — buyer, payment intent, order total, ship address, tracking.
RefundRefund records, partial supported.
processedWebhookEventStripe webhook dedupe — idempotency at the event level.

The auto-publish flow

When SlabTrack's Strategist commits cards to ToT, the webhook fires POST /api/webhook/ingest with grouped card payloads. The webhook:

  1. Caches each card via prisma.card.upsert({ where: { slabtrackId } }) — Card rows are the canonical satellite-side card record
  2. Stages a CollectionCard row per card with the transfer code + bundle metadata
  3. For each group in the payload, builds a Listing directly (recent change — used to require manual publish)
  4. Each Listing gets its FlipConfig with cardValue + spread + winPrice + losePrice
  5. Theme metadata (theme_key, theme_label, theme_sports, theme_teams) flows through so city lots render with team chip pills
  6. CollectionCard rows flip to listed: true with their listingId
  7. Idempotent — group skipped if any of its cards already on a Listing

Stripe model

Direct charges on platform account. No Connect. All buyer money flows into the operator's single Stripe account; payouts handled by Stripe's normal payout schedule.

Shipping model

One global ship-from address (operator's address) configured in env vars (SHIP_FROM_*). Shippo SDK calls go through src/app/api/shipping/route.ts. Operator triggers label printing from /seller; tracking number stored on Order; buyer-shipped email fires.

Code references