Payments & Monetization β
TuneCamp supports a hybrid payment system combining traditional Fiat (via Stripe) and Web3 (Base Network) to provide a seamless monetization experience for artists.
1. Hybrid Payment Flows β
Stripe Checkout (Fiat) β
- Purpose: Allows users to buy tracks, albums, or store assets using credit/debit cards.
- Route:
POST /api/payments/stripe/create-session - Mechanism:
- Frontend requests a session for an
itemIdandtype(track/album/asset). - Backend calculates the price (converting ETH to USD if necessary via
price.ts). - Backend resolves the item's artist. For assets this is the asset's
artist_idif it was self-published by an artist; admin-created assets (noartist_id) have no resolvable artist. If the artist has a connected Stripe account (artists.stripe_account_id), the session is created as a Stripe Connect direct charge on that account, with the instance fee taken as anapplication_fee_amount. Otherwise the session is created on the instance's own Stripe account (single-artist / self-host fallback, or admin-managed assets β the operator already keeps 100%). - A Stripe Checkout session is created and the URL is returned to the client.
- Upon successful payment, Stripe sends a webhook to
/api/payments/stripe/webhook. For direct charges this arrives as a connected-account event (enable "Listen to events on connected accounts" on the endpoint). - Backend generates an Unlock Code and stores it in the database.
- Frontend requests a session for an
Crypto Onramp β not implemented β
- Route:
GET /api/payments/onramp-configalways returnsconfigured: false. There is noonramp-sessionendpoint and no onramp provider (Stripe Onramp, MoonPay) is wired up. - Buyers without crypto cannot convert fiat β USDC in-app today. The only crypto path is Web3 On-chain Verification below (send ETH/USDC you already hold).
Web3 On-chain Verification β
- Purpose: Unlocks content based on direct blockchain transactions.
- Route:
POST /api/payments/verify - Supported Methods:
- Direct ETH: Sending ETH directly to the artist's wallet.
- Direct USDC: Sending USDC (ERC-20) to the artist's wallet.
- Checkout Contract: Calling the
purchaseWithETHorpurchaseWithUSDCmethods on theTuneCampCheckoutsmart contract.
- Mechanism: Backend fetches the transaction and receipt from the Base RPC, parses the transaction data (using
ethers.js), and verifies the recipient and amount. - Note: Store assets have no on-chain verify endpoint yet β the crypto tab is hidden for asset checkouts, which are Stripe-only.
1.5 Release distribution modes ("Download Experience") β
Every album/release picks one distribution mode in the editor (Studio β Release β Download Experience). It is stored on albums.download and decides which (if any) purchase/download flow the release page exposes.
| Mode (UI) | download value | Behavior |
|---|---|---|
| Streaming Only | none (or null) | In-app streaming only. No download button, no purchase flow. |
| Free Download | free | Public ZIP download via GET /api/releases/:slug/download?format=mp3|wav β no payment, no code. |
| Unlock Codes | codes | Download is gated behind a unique unlock code (see Β§2). Codes are issued on purchase (Stripe/crypto) or handed out manually from the editor. |
| External Showcase | external | All on-platform purchase/download flows are disabled. The release page replaces the buy button with a single "Buy on Bandcamp" link pointing at the configured External Buy URL. |
These modes are enforced by the server, not just by the release page. Every download route β GET /api/tracks/:id/download, GET /api/albums/:id/download and GET /api/releases/:id/download β runs the shared policy in src/server/common/download-access.ts on top of the visibility check:
freeis served to anyone who can see the release, guests included.codes(and any release or track carrying a price) needs an unlock code matching the track or its release, a purchase recorded against the account, or an active subscription β the same entitlementsGET /api/payments/download/:trackIdaccepts. Without one the answer is402 PAYMENT_REQUIRED.none/nullandexternalanswer403β there is no downloadable payload to hand out on this instance.- Curators, Managers and Root Admins, the owning account and the linked artist bypass the gate entirely, so staff and the seller always reach their own masters.
External Showcase details:
- The buy URL is stored as the first entry of
albums.external_links([{ label, url }]). The button label isBuy on {label}, defaulting to "Buy on Bandcamp" when no label is set. - Selecting this mode forces
use_nft = falseand clears the price β TuneCamp never takes a cut of an off-platform sale. - Streaming on TuneCamp still works for any audio that was uploaded to the release; only the sale happens off-platform. Uploading audio is optional and only needed if you also want in-app playback (publishing/streaming model is unchanged β TuneCamp does not stream from Bandcamp).
Track-Slot Topup (Stripe) β
- Purpose: Lets a listener buy extra track-upload slots when they hit their
listenerTrackCap(or per-usertrack_quotaoverride). - Route:
POST /api/payments/stripe/create-trackcap-session - Mechanism:
- Requires an authenticated user; the return URL must point back at this instance.
- Price and slot count come from the admin settings
trackcapTopupPriceUsdandtrackcapTopupTracksGranted. - Always charged to the instance's own Stripe account (never Connect-routed β this is a platform quota, not an artist sale).
- On
checkout.session.completed, the webhook callsAuthService.addPurchasedTracks, which raises bothtrack_quotaand its floortrack_quota_floorso a later admin override can never undercut a paid purchase.
2. Unlock Codes β
When a payment is verified (either via Stripe Webhook or On-chain Verify), the system generates a unique 10-character alphanumeric code.
- Storage:
database.createUnlockCode(code, releaseId, trackId) - Download: Users can download the track via
GET /api/payments/download/:trackId?code=XXXXX.
3. Revenue Splits & Fees β
TuneCamp implements a universal fee split mechanism that applies to all payment methods, ensuring the platform remains sustainable regardless of how the user pays.
- Platform Policy: By default, the platform takes a percentage of every sale (e.g., 15%).
- Web3 Payments (On-chain): The split is enforced directly by the
TuneCampCheckoutsmart contract. Funds are distributed instantly: the artist's share goes to their wallet, and the platform's share goes to theadminTreasuryAddress. - Stripe Payments (Fiat): The split is enforced by Stripe Connect, mirroring the on-chain contract:
- Multi-artist instances (artist has a connected account): the charge is a direct charge on the artist's Stripe account β funds land in the artist's balance directly, never in the instance's account. The instance's cut is collected automatically as
application_fee_amount, using the sameadminFeePercentagesetting as the on-chain split so fiat and crypto take an identical percentage. No manual payout or custody. - Single-artist / self-hosted instances (no connected account): the charge stays on the instance's own Stripe account. The operator is the artist, so they already keep 100% (minus Stripe's processing fee) β Connect would only add onboarding friction.
- Cross-border guard: Stripe rejects application fees for direct charges in a few countries/currencies (e.g.
MX,BR/mxn,brl). For connected accounts based there, the fee is skipped so checkout never hard-fails β the artist simply receives the full amount.
- Multi-artist instances (artist has a connected account): the charge is a direct charge on the artist's Stripe account β funds land in the artist's balance directly, never in the instance's account. The instance's cut is collected automatically as
- Direct Verification: Even for direct txHash verification, the backend checks if the appropriate "Label Fee" has been sent to the treasury before generating an unlock code.
3.1 What an artist actually keeps (honest cost breakdown) β
TuneCamp's pitch is "no platform middleman fees", not "100% of revenue". Here is where the money actually goes on a β¬10 sale:
| Cost | Who charges it | Typical amount | Notes |
|---|---|---|---|
| Instance fee | The TuneCamp instance you publish on | 0β15% | Default split is 85/15. If you self-host your own instance, this is 0% β you are the platform. Pro artists on third-party instances keep 100% of the split. |
| Card processing | Stripe | ~2.9% + β¬0.30 | Unavoidable for fiat payments anywhere. TuneCamp adds nothing on top. |
| Network gas | Base (Ethereum L2) | a few cents | Only for on-chain (ETH/USDC/NFT) purchases. Paid by the buyer. |
| Hosting | Your VPS provider | ~β¬5β15/month | Fixed cost, independent of sales volume. |
Example β β¬10 album sold via Stripe on your own self-hosted instance: β¬10 β β¬0.59 Stripe β β¬9.41 to you (~94%). The same sale on Bandcamp: 10% revenue share + ~5% payment processing β β¬8.50. The difference compounds with volume, but be honest with yourself about the fixed hosting cost: below roughly β¬10β20/month in sales, a hosted platform may net you more.
On-chain payments: near-zero fees, with caveats. A buyer who already holds USDC on Base pays only gas (cents), and you receive ~100% β the best case of any payment path, beating Stripe's ~94%. Two caveats keep it from being the default recommendation: (1) buyers without crypto have no in-app onramp today (see Β§1, "Crypto Onramp β not implemented") β they'd have to buy USDC/ETH elsewhere first, worse UX than a plain card checkout; (2) you receive USDC/ETH, so cashing out to EUR has its own exchange/withdrawal costs, and holding ETH carries price risk until you convert (USDC largely avoids this). The instance fee split (85/15 default) applies on-chain too β it is enforced by the TuneCampCheckout contract itself.
3.2 Stripe Connect onboarding (artist accounts) β
For multi-artist instances, each artist connects their own Stripe (Express) account so card payments can be routed to them directly. This is admin-managed (the same gate as artist publishing):
POST /api/admin/artists/:id/stripe-connect/onboardβ creates (or reuses) the artist's Express account, stores its id inartists.stripe_account_id, and returns a hosted Stripe onboarding link. The artist completes KYC on Stripe's side.GET /api/admin/artists/:id/stripe-connect/statusβ reportsconnected,chargesEnabled,payoutsEnabled,detailsSubmitted, andcountry.DELETE /api/admin/artists/:id/stripe-connectβ unlinks the account from the artist (does not delete it on Stripe); their checkouts revert to the instance's own account.
No new environment variables are required β onboarding reuses the instance's stripe_secret_key. Until an artist completes onboarding (chargesEnabled = false), their fiat checkouts use the single-artist fallback (instance account).
4. Configuration β
Required Environment Variables:
STRIPE_SECRET_KEY: Stripe API secret.STRIPE_WEBHOOK_SECRET: Secret for verifying webhook signatures.TUNECAMP_RPC_URL: RPC endpoint for Base Network (e.g., Alchemy or Base public RPC).TUNECAMP_OWNER_ADDRESS: Default address for platform fees.
Stripe webhook β "Listen to events on connected accounts" β
When at least one artist has a connected Stripe account, checkout sessions are created as direct charges on that account. Stripe dispatches the resulting checkout.session.completed event as a connected-account event (event.account is set). Your webhook endpoint must have "Listen to events on connected accounts" enabled in the Stripe Dashboard (Developers β Webhooks β your endpoint β Edit); otherwise these events are silently dropped and no unlock codes are generated for payments made on connected accounts.
The same STRIPE_WEBHOOK_SECRET covers both platform and connected-account events β no second secret is needed.