9:30●●●● 🔋
Earnings
Available
€340.50
Pending
€75.00
Instant Payout · Premium (post-MVP)
Not in v1. Weekly batch is the only payout path for MVP. This state previews the future paid add-on — keep it in spec but don't ship the "Withdraw now" pill yet.
Payout methods Manage
Stripe
Connected · Default
Action required · Tap to resolve
Recent activity View all
Marco Rossi
Tennis training · Tue, Apr 21 · 18:00
+€25.00
Group training · Padel
4 athletes · Mon, Apr 20 · 19:00
+€60.00
Payout to Stripe
Weekly batch · Mon, Apr 20
−€275.00
Sarah Chen
Padel training · Sun, Apr 19 · 10:00
+€30.00
You've earned €2,840 with 321Fit
Available
€0.00
Pending
€0.00
No earnings yet
Completed sessions will appear here. Funds release 24 hours after each training.
Available
€0.00
Pending
€75.00
Connect payouts to start earning
Add a payout method to receive money from completed sessions. Funds stay on hold until you connect.
Recent activity
Marco Rossi
Pending · releases Tue, Apr 22 · 18:00
€25.00
Group · Padel
Pending · releases Mon, Apr 21 · 19:00
€50.00
Withdraw now
Instant transfer to your default Stripe account. Arrives within 30 minutes.
Available €340.50
Instant payout fee (1%) −€3.41
You receive €337.09
Prefer free payouts? Your next weekly payout of €340.50 arrives Mon, Apr 27 with no fee.
Why is this pending?
Funds from completed sessions wait 24 hours before becoming available. This window covers cancellations and no-shows — so what moves into your balance is final.
Session marked complete Now
Moves to Available +24h
Paid out Next Monday
Instant payout on the way
Payout couldn't go through Retry
9:30●●●● 🔋
Transactions
All
Earnings
Payouts
Refunds
April 2026
Marco Rossi
Tennis training · Tue, Apr 21 · 18:00
+€25.00
Group · Padel
4 athletes · Mon, Apr 20 · 19:00
+€60.00
Payout to Stripe
Weekly batch · Mon, Apr 20
−€275.00
Sarah Chen
Padel training · Sun, Apr 19 · 10:00
+€30.00
Refund · Luis Garcia
Cancelled < 24h · Fri, Apr 17
−€12.50
March 2026
Payout to Stripe
Weekly batch · Mon, Mar 30
−€405.00
No transactions yet
Earnings and payouts will appear here as sessions complete.
9:30●●●● 🔋
Earning
+€25.00
Tue, Apr 21 · 18:00
Available
Athlete Marco Rossi
Training Tennis 1:1
Duration 60 min
Location TNT Studio
Session price €25.00
Platform fee €0.00
You earned €25.00
Released Wed, Apr 22 · 18:00
9:30●●●● 🔋
Payout
€275.00
Weekly batch · Mon, Apr 20
Completed
Destination Stripe · •••• 4821
Initiated Mon, Apr 20 · 09:00
Arrived Tue, Apr 21 · 11:24
Fee €0.00
Luis Garcia
Tennis · Sun, Apr 19
+€25.00
Group · Padel
4 athletes · Sat, Apr 18
+€60.00
Sarah Chen
Padel · Fri, Apr 17
+€30.00
+5 more
9:30●●●● 🔋
Payout methods
Choose how you'll receive payouts. You can add more methods later.
Stripe
Fast setup · 2–5 minutes
Revolut Business
Coming soon
Soon
We never hold your banking details — Stripe handles verification and transfers securely.
Your payouts go to the default method. Add more to keep backups.
Stripe · •••• 4821
Connected · Default
Revolut Business
Coming soon
Tap the circle to change your default payout method.
Stripe · •••• 4821
Default
Revolut · •••• 2198
Connected
More providers will be available soon. If you need a specific one, let us know.
Set as default
Disconnect
Stripe is now your default
Disconnect your only payout method?
Payouts will be paused until you add another method — your earnings will stay on hold, but completed sessions still bank correctly.
Disconnect Stripe?
Your next payout will go to Revolut. You can reconnect anytime.
9:30●●●● 🔋
Stripe
Connect Stripe to receive payouts
Stripe verifies your identity and lets us transfer your earnings securely. Takes 2–5 minutes.
Identity verification
Bank account
Tax details
Setup in progress
Stripe is reviewing your information. Payouts start once verification is complete.
StatusVerifying
SubmittedTue, Apr 23 · 09:42
Stripe connected
Active · Default
Accountacct_1Q…Zq4
Bank•••• 4821
CountrySpain · EUR
Payout scheduleWeekly · Monday
Action needed
Stripe needs additional documents to keep payouts active. Payouts are paused until this is resolved.
MissingProof of address
DeadlineFri, May 1
Coach

Earnings

Earnings — coach-side of money. Hero shows Available (withdrawable) and Pending (released 24h after each session).

Full behavior spec

Payout model (MVP)

Weekly auto-payout only (Monday sweep). Coach does nothing, money lands. No manual withdraw in v1 — removes fee complexity and keeps cashflow consolidation on our side.

Instant Payout (post-MVP, premium)

Future paid add-on: 1% fee charged to coach, money arrives within 30 min via Stripe Instant Payouts. Previewed in the + Instant Payout state so devs can see the intended UI and wire backend support, but not shipped in v1. Decision on monetization (per-transfer fee vs subscription bundle) deferred.

Hold period

Funds enter Pending when the session is marked complete. After 24h (no refund/no-show), they move to Available. This window protects us against corrections.

Minimum threshold

Auto-payout fires only when Available ≥ €20. Below threshold — rolls to next week.

States

MVP: hero with Next payout only, no Withdraw pill.
+ Instant Payout: Withdraw now pill + Premium tag + notice banner explaining this is post-MVP.
Zero: neutral hero, no Withdraw, empty-state copy.
Needs payout method: hero dimmed, lock plate with CTA. Pending earnings listed but not withdrawable.

Cross-state signals

Stripe preview card reflects Stripe account status inline — "Connected · Default" (teal dot) normally, or "Action required · Tap to resolve" (yellow dot) when requirements.currently_due is non-empty. No separate banner on Earnings — the card itself is the signal, and tapping pushes to Stripe detail where the full warn-banner lives.

Pending info sheet

Tap on the Pending hero label (or its info icon) opens a bottom sheet explaining the 24-hour hold window + visual timeline (session complete → +24h available → Monday payout). Addresses the "why is my money stuck?" support ticket pre-emptively.

Recent activity limit

Earnings screen shows 4 most recent transactions. Devs: GET /coach/transactions/?limit=4. "View all ›" pushes to full Transactions screen which has its own pagination.

API

GET /coach/balance/ — { available, pending, currency, next_payout_at }
GET /coach/transactions/?limit=4 — preview for Earnings screen
GET /coach/transactions/?cursor=... — full ledger with pagination
POST /coach/payout/instant/ — triggers Stripe Instant Payout (post-MVP)

Backend gap

Currently coach balance is computed on-the-fly from completed events. Needs real coach_balance table + append-only coach_transactions ledger. See project_coach_balance_decisions.md.

Coach

Transactions

Full chronological ledger: earnings, payouts, refunds. Grouped by month. Filter chips at top.

Filter + pagination

Filters

Chips: All / Earnings / Payouts / Refunds. Single-select. Default = All.

Pagination

Infinite scroll by month. First fetch = current + previous month. Scrolling to bottom loads older months.

Empty

Shown when /coach/transactions/ returns 0 items AND filter = All. With filter active, empty copy should adapt: "No payouts yet", etc.

Loading

Skeleton rows during first fetch. For infinite scroll — inline spinner at the bottom of the list (not skeleton).

Coach

Earning detail

Drill-down on a single earning. Amount up top with status pill (Pending / Available / Paid out).


Status pill: Pending (gray) → Available (teal) → Paid out (blue). When included in a payout batch, shows link to that payout.

Coach

Payout detail

Batched payout — shows all included earnings. Amount neutral (not minus red; payout isn't "bad").


For Instant payouts: fee row shows 1% deduction. For weekly batch — fee = €0.

Coach

Payout methods

Managing connected payout providers. Default receives weekly batch (and future Instant) payouts. Others are backup.

Interaction details

Empty state

Coach has no payout accounts connected yet — reached from Earnings Needs payout method CTA. Each provider shows inline "Connect" action. Tapping Connect on Stripe launches the StripeConnect embedded component (native iOS SDK, not WebView) — form lives in-app, validation + doc upload + selfie verification all native.

After Stripe webhook fires account.updated with charges_enabled & payouts_enabled → backend creates PayoutAccount with is_default=true → user returns to Single (MVP) state. If webhook reports requirements.currently_due non-empty → Stripe screen shows Action required.

Single state (MVP)

Only Stripe connected, no radio (default is implicit), ⋯ menu has only Disconnect. Revolut is a "Coming soon" placeholder to show provider abstraction intent.

Multi state (future)

2+ methods connected. Each card has an iOS-style radio on the left. Tapping empty radio = instant switch (toast "X is now your default"). Tapping filled radio does nothing. "Set as default" in ⋯ menu is hidden when the method is already default.

Disconnect behavior

Backup exists: soft confirm sheet — "Your next payout will go to {fallback}". Destructive red CTA.

Only method: warning sheet — yellow triangle + "Payouts will be paused until you add another method — your earnings will stay on hold". Explicit tradeoff. User can still proceed (not blocked). On confirm → falls to Needs-payout-method state on Earnings screen.

API

GET /coach/payout-methods/ — list (returns {} for empty state)
POST /coach/payout-methods/stripe/session/ — returns {client_secret} for embedded onboarding component
PATCH /coach/payout-methods/{id}/ {is_default: true} — switch default
DELETE /coach/payout-methods/{id}/ — disconnect (backend must re-route default to next available; pause payouts if none left)

Coach

Stripe Connect

Per-provider detail screen. Four states reflect the Stripe Connect account lifecycle.


iOS native SDK: uses StripeConnect module — not WebView. Nativecontroller rendered in-app. Backend needs to expose account_session client_secret.

Integration plan

iOS

StripeAPI.shared.publishableKey = ...
let mgr = EmbeddedComponentManager(fetchClientSecret: ...)
let vc = mgr.createAccountOnboarding(...)

Backend

POST /coach/stripe/account-session/ — returns { client_secret } for embedded components. Replaces current /coach/stripe-onboarding redirect URL.

State mapping

Stripe webhooks → update PayoutAccount.status: none → pending → done. Action-required triggered by requirements.currently_due becoming non-empty. Earnings screen's Stripe preview card mirrors this automatically.

Disconnect entry points

Coach can reach Disconnect either via ⋯ menu on the method card in Payout methods list, OR via the destructive button on the Connected state of this Stripe detail. Both open the same warning sheet — backup-variant if other methods exist, only-variant if Stripe is the single connected method.