casset/docs
FeaturesOpen app
docs indexreference
00Overview01Thesis02Architecture03System reality04Roadmap05Investor brief06Technical brief07Full tech HTML08API reference09Playback10Audio pipeline11Commerce12Base anchoring13Hook system14Music video15Theming16Creator guide17Glossary
how money moves

Commerce.

Support and unlocks should strengthen the Profile World, not turn Casset into a checkout page.

Commerce is a support layer around audiovisual identity. A fan collects because a Hook Object, Listening Room, or Release Ritual made the moment feel worth keeping. Under the hood Casset uses Stripe Connect Destination charges: the platform is the merchant of record, the artist is the connected account, and the webhook finishes entitlement, activity, referral, and payout state.

The happy path

  1. Fan lands in a Profile World, plays the Hook Object, and taps Collect when they want to support/unlock more. Client calls POST /api/checkout/payment-intent with the artist slug + referral code (if any).
  2. Server creates a Stripe PaymentIntent with the artist's connected account as transfer_data.destination and an application_fee_amount for the platform cut. Client secret is returned.
  3. Client mounts Stripe's Payment Request (Apple Pay / Google Pay). If the device can pay, the native sheet appears; otherwise we fall back to a standard card element.
  4. On confirm, Stripe captures the payment and fires payment_intent.succeeded at /api/webhooks/stripe. The webhook is the source of truth — the client never grants entitlement on its own.
  5. Webhook handler:
    • Inserts a Purchase row (idempotent on the PI id).
    • Emits an ActivityEvent so followers and the artist see "@fan collected" in realtime.
    • If a ReferralAttribution cookie is present, writes the referral so the referrer accrues credit.
    • Nudges any Notification rows so the artist sees the collect in their bell.
  6. Stripe disburses the destination transfer on the artist's configured payout schedule (default: daily rolling).

Why Apple Pay first

The single most predictive signal for collect conversion on mobile is whether the purchase is one tap. Casset leans on Apple Pay / Google Pay because they're:

  • Already authenticated (Face ID), so there's no card form, no email step, no ZIP lookup.
  • Trustworthy at a glance — the sheet is OS-chrome, not our chrome.
  • Supported inside TikTok / Instagram in-app browsers, which is where half of inbound traffic lands.

Card fallback exists, but the default success path never requires typing.

Entitlement, not tokens

Casset doesn't sell tokens or subscriptions. A Purchase row is a permanent entitlement record. Anywhere the product asks "can this user hear the full track?", the answer comes from:

SELECT 1
  FROM Purchase
 WHERE userId   = <session.userId>
   AND artistId = <track.artistId>
   AND state    = 'granted'
 LIMIT 1

The collect is one-time, the unlock is forever. That's also why every audio byte goes through lib/audio-access.ts — the entitlement check happens on every stream request, not once at login.

Referrals

Every share produces a unique URL via POST /api/casset/[slug]/referral-link. Clicking it sets an attribution cookie (ref=fromUserId) that persists for 30 days. If the visitor collects within the window, we write a ReferralAttribution row tying that Purchase to the referrer.

Referral rewards are currently credit-based, tracked in CreditLedger (append-only). In the studio, referrers see a "you're credited" counter; when rewards convert to cash, payout is merged into the next scheduled transfer.

Per-track rewards

Rewards are the lightweight, per-hook bounty mechanic. On any track, owners can set:

  • rewardAmount — integer cents.
  • rewardCurrency — ISO 4217, default USD.
  • rewardDescription — the offer ("10 fans who share get the demo").
  • rewardActive — bool; lets you draft + flip on later.

When a fan posts a qualifying hook video (attached via POST /api/hooks/submissions), the artist reviews the submissions in /clips. Approved submissions enter the payout pipeline.

Release quests / campaigns

Campaigns are useful only when they make a Release Ritual more participatory. The implemented system supports structured prize pools: creators fund a campaign, fans submit hook videos, and winners split the pool by a scoring algorithm (views × engagement × integrity). Product language should frame this as release energy, not promoter software.

  • POST /api/campaign/[id]/fund — creator funds the pool; Stripe holds the money until the campaign ends.
  • POST /api/campaign/[id]/launch — opens the campaign for submissions. lib/scoring/ runs periodic snapshots.
  • POST /api/campaign/[id]/select-winner — closes the drop, snapshots final scores, and queues payouts.
  • POST /api/campaign/[id]/payout — releases the prize pool via Stripe Connect transfers.

Fees + economics

Headline split (today):

  • Stripe: ~2.9% + $0.30 processing
  • Casset platform fee: 10%
  • Artist: everything else

Platform fee may change; this page is informational, not a commercial commitment. Check your Studio dashboard for your effective rate.

Refunds + disputes

Refunds come through the same webhook as charges but on charge.refunded. The handler:

  1. Transitions the Purchase.state from granted to refunded so entitlement queries stop returning it.
  2. Writes a reversing entry in CreditLedger so the artist's earned-balance updates without mutating the original record.
  3. Cancels any pending referral rewards tied to the purchase.

Chargebacks follow the same flow but originate from charge.dispute.created. The artist is notified and can submit evidence through Stripe; we don't auto-accept.

Onboarding the artist

Before a casset can accept payments, the owning user needs a connected Stripe account. We use POST /api/studio/stripe/connect → embedded Stripe onboarding flow → POST /api/studio/stripe/webhook handles the account-updated events. The artist record tracks two flags:

  • stripeAccountId — the acct_ id on Stripe.
  • stripeOnboardingComplete — set when Stripe reportscharges_enabled && payouts_enabled.

The UI gates the Collect button on stripeOnboardingComplete — no button, no money in flight, no failed charges.

Stripe webhooks, summarized

  • payment_intent.succeeded — grant Purchase, emit activity, resolve referrals.
  • charge.refunded — revoke entitlement, reverse credits.
  • charge.dispute.created — notify artist, freeze related payouts.
  • payout.paid — mark CampaignPayout / PerformancePayout as settled.
  • account.updated — sync stripeOnboardingComplete flag.

Full routing table in docs/stripe-webhooks.md.

← Docs home→ Architecture→ API reference
© Casset 2026
PrivacyTrustTerms