Happy path baseline — confirms the normal case works and available count increments.
7
Counts price_changed toward canCheckout
Price changes are informational, not blocking. Parent should see the new price and still be able to proceed. If we blocked on price changes, stale carts would be permanently stuck.
8
Allows checkout when registration_opens_at is null
Null means "always open". Must not accidentally treat null as a falsy date and block.
These test the fairness algorithm when a parent books multiple children into the same session.
Session S-1: seats_remaining = 1Cart: line-1 → S-1 (child Alice) ← gets the seat line-2 → S-1 (child Bob) ← sold_outOrder matters: lines are processed in created_at order.
#
Test
Why it exists
18
2 children, 1 seat → first gets seat, second sold out
Fairness: first-come-first-served within a single cart. Without per-session tracking, both lines would see seats_remaining: 1 and both would be marked available.
19
3 children, 3 seats → all available
Boundary: exact match should work. Off-by-one errors here would block legitimate bookings.
20
2 children, exactly 2 seats → both available
Boundary test at the exact limit.
21
Mixed statuses across different sessions
Integration test: one session available, one deleted, one full, one not open, one price changed. Validates all 5 status paths simultaneously.
22
All lines unavailable → canCheckout: false
Edge: if nothing can be booked, the checkout button must be disabled.
If session doesn't exist, we should never check its fields (would crash). Confirms the continue short-circuit works.
25
not_open > session_not_available
A session that is both draft AND has a future registration date: we want the user to see "not open yet", not "unavailable" — it implies the session will become available.
The transfer_group links the PI to later Transfers. Format must match activityfox-checkout-{checkoutId} exactly — a mismatch means transfers don't reconcile in Stripe Dashboard.
10
PI metadata includes all required fields
Stripe metadata is how we reconcile disputes, refunds, and customer support lookups. Missing metadata = manual investigation.
The most critical endpoint: verifies payment, distributes money to vendors,
and creates bookings. Partial failures here mean money moved but bookings
didn't happen — every guard matters.
Double-charge prevention. If frontend retries after a network timeout, we must not create duplicate transfers. This is the most important guard in the entire checkout system.
5
409 if status is not awaiting_payment
A checkout stuck in pending means the PI was never created — confirming it would crash on null stripe_payment_intent_id.
Creates transfers + Sharetribe transactions for each line
Full integration: verifies transfer amounts (85% to vendor), transfer_group linkage, Sharetribe transactions.initiate() with correct process alias, listing ID, booking dates, and protectedData.
Line 1: Transfer ✓ Sharetribe TX ✗ (listing not found) → failed[] Line 2: Transfer ✓ Sharetribe TX ✓ → transactions[] │ checkout_groups.status = "completed" ◀──────────────────────┘ (even with partial failure — money has moved)
#
Test
Why it exists
12
Continues processing after one line fails
Non-atomic by design. If line 1 fails (listing deleted between validate and confirm), line 2 must still process. Failed lines are returned in the failed[] array for the frontend to display. The alternative — rolling back all transfers — is worse because it introduces refund complexity.
The money invariant: no money created or destroyed.
10
0 amount → 0 payout
Symmetry with fee.
11
fee + payout = amount across 12 values
The critical money-leak test. Tests amounts from 1 cent to $99,999.99. If Math.round in fee and amount - fee in payout ever disagree, money vanishes. This would compound across thousands of transactions.
12
1 sub-unit: payout=1, fee=0
Minimum non-zero: vendor gets the penny, platform gets nothing.
Sharetribe handles this. transactions.initiate() is the atomic gate — if the seat is taken, Sharetribe rejects the transaction. Our validate-cart is advisory only.
Stripe webhook for async PI confirmation
Not implemented yet. Current flow is on-session only — 3DS completes inline. Webhooks will be added for the drop-time deferred checkout feature.
Frontend Stripe Elements integration
Tested via browser integration tests, not unit tests.
Supabase RLS policies
Tested via migration integration tests against a real Supabase instance, not mocked unit tests.
Transaction process state machine
Defined in EDN, tested by Sharetribe's flex-cli tooling.