Sponsor Auction System
Relevant source files
The following files were used as context for generating this wiki page:
The Sponsor Auction System manages a live, competitive bidding environment where users purchase advertising space on a 3D model of a Mini Cooper. The system handles real-time bidding, payment processing via Stripe, and instantaneous livery updates through WebSockets. Advertisers bid for specific "slots" on the vehicle; a new bid must exceed the current bid by at least US $1 to replace the existing artwork.
Sources: apps/website/README.md:104-106, apps/website/src/app/terms/page.tsx:16-19
System Architecture
The system utilizes a Cloudflare Durable Object named Auction to maintain a single source of truth for the auction state. It uses an internal SQLite database to store bids, placements, and upload metadata. The frontend communicates with this Durable Object via a Next.js API layer.
Core Components
- Durable Object (
Auction): Manages the SQLite state, handles WebSocket connections, and coordinates with Stripe. - Stripe Integration: Processes payments and provides webhooks to trigger bid publication.
- WebSocket Broadcaster: Pushes auction snapshots to connected clients whenever a revision occurs.
- R2 Storage: Stores immutable artwork and logo PNG files.
Sources: apps/website/worker/auction.ts:38-70, apps/website/README.md:4-5
Data Flow Diagram
The following diagram illustrates the lifecycle of a bid from initial creation to publication.
Auction Logic and Rules
The auction operates under strict financial and temporal rules to ensure consistency and prevent race conditions.
Bidding Requirements
- Minimum Bid: An unclaimed spot starts at 100 cents ($1).
- Increment: A replacement bid must be at least 100 cents ($1) higher than the current confirmed bid.
- Payment: The buyer pays the full bid amount, not the difference between bids.
- Refunds: If a payment is completed but the spot was already taken by a higher bid during the transaction, the system automatically triggers a Stripe refund.
Sources: apps/website/src/lib/auction.ts, apps/website/README.md:107-111, apps/website/src/app/terms/page.tsx:16-25
Bid Lifecycle States
| Status | Description |
|---|---|
pending | Initial state after Stripe session creation; awaiting payment. |
won | Payment confirmed and artwork is currently live on the car. |
outbid | Previously live ad that has been replaced by a higher bid. |
refund_pending | Payment received but the price increment was no longer met; awaiting Stripe refund. |
refunded | Stripe refund successfully processed. |
expired | Checkout session timed out without payment (default 31 minutes). |
Sources: apps/website/worker/auction.ts:40-52, apps/website/worker/auction.ts:413-426
Database Schema
The Durable Object manages several tables within its SQLite storage to track the auction state.
Sources: apps/website/worker/auction.ts:40-62
API Endpoints
The system exposes several internal endpoints for frontend interaction and webhook processing.
| Endpoint | Method | Purpose |
|---|---|---|
/api/auction | GET | Returns the current AuctionSnapshot including all placements and history. |
/api/artwork | POST | Uploads a PNG image to R2; returns a temporary token. |
/api/checkout | POST | Validates bid parameters and creates a Stripe Checkout session. |
/api/stripe/webhook | POST | Handles checkout.session.completed events from Stripe. |
/api/checkout/confirm | POST | Polling endpoint for clients to verify payment status after redirect. |
/api/live | GET | Establishes a WebSocket connection for real-time updates. |
Sources: apps/website/worker/auction.ts:168-195, apps/website/src/components/auction-site.tsx:112-135
Fulfillment and Race Conditions
The fulfill method in the Durable Object handles the transition of a bid from pending to won.
- Verification: Re-retrieves the session from Stripe to ensure the
amount_totalandpayment_statusmatch the internal record. - Transaction: Executes a synchronous SQL transaction to:
- Check if the bid still meets the $1 increment over the current placement.
- Mark the previous placement as
outbid. - Update the
placementstable with the newbid_id. - Increment the global
revisioncounter.
- Broadcasting: If the transaction succeeds, it pushes the new state to all WebSocket clients.
- Notifications: Enqueues an entry in
outbid_emailsto notify the displaced sponsor.
Sources: apps/website/worker/auction.ts:327-375, apps/website/README.md:113-116
Maintenance and Alarms
The Durable Object uses a 60-second recurring alarm to perform background cleanup and reconciliation:
- Abandoned Uploads: Deletes R2 objects for artwork that was never associated with a bid after 24 hours.
- Stripe Reconciliation: Checks
pendingbids against Stripe to catch missed webhooks. - Email Retries: Attempts to redeliver outbid notifications that failed due to temporary network issues.
- Refund Processing: Retries Stripe refund requests for bids marked as
refund_pending.
Sources: apps/website/worker/auction.ts:466-515, apps/website/README.md:118-120