max / makenotwork
13 files changed,
+337 insertions,
-40 deletions
| @@ -6,6 +6,25 @@ v0.3.2. Audit grade A. 225 tests. | |||
| 6 | 6 | ||
| 7 | 7 | --- | |
| 8 | 8 | ||
| 9 | + | ## Code Fuzz Findings (2026-04-26) | |
| 10 | + | ||
| 11 | + | ### Critical | |
| 12 | + | - [ ] Cross-community post removal via flag: `remove_flagged_post_handler` and `dismiss_flag_handler` verify mod/owner of the URL slug community but act on any `flag_id` without checking it belongs to that community. A mod of community-A can remove posts from community-B. Fix: after fetching the flag row, join through posts→threads→categories to get community_id and verify it matches. (`src/routes/flagging.rs:101,128`) | |
| 13 | + | ||
| 14 | + | ### Serious | |
| 15 | + | - [ ] Cross-community category edit: `edit_category_handler` verifies owner of slug community, then updates any category by UUID. Fix: add `AND community_id = $2` to the UPDATE in `update_category`. (`src/routes/settings.rs:231`, `crates/mt-db/src/mutations.rs:349`) | |
| 16 | + | - [ ] Cross-community tag delete: `delete_tag_handler` verifies owner of slug community, then deletes any tag by UUID. Fix: add `AND community_id = $2` to the DELETE in `delete_tag`. (`src/routes/settings.rs:363`, `crates/mt-db/src/mutations.rs:666`) | |
| 17 | + | - [ ] Search query panics on multi-byte UTF-8: `&q[..200]` panics if byte 200 is mid-character. Fix: use `is_char_boundary` loop to find a safe truncation point. (`src/routes/search.rs:32`) | |
| 18 | + | - [ ] SSRF bypass in link preview via alternative IP encodings (octal, decimal, hex, shorthand, IPv6-mapped IPv4). String-based host check doesn't catch them. Fix: parse host with `std::net::IpAddr` to normalize all IP representations before checking private ranges. (`src/link_preview.rs:13`) | |
| 19 | + | ||
| 20 | + | ### Minor | |
| 21 | + | - [ ] `parse_duration` silently defaults unknown strings to permanent ban (e.g. typo "3d" → permanent). Fix: return error on unrecognized values. (`src/routes/helpers.rs:275`) | |
| 22 | + | - [ ] `/internal/threads/{id}/stats` is unauthenticated and publicly accessible (thread UUIDs visible in URLs). Fix: add `InternalAuth` extractor or accept the info leak. (`src/routes/internal.rs:276`) | |
| 23 | + | - [ ] `auto_hide_if_threshold_met` records the flagger's user_id as `removed_by`, not a system/mod account. Misleading audit trail. (`src/routes/flagging.rs:82`) | |
| 24 | + | - [ ] `/search` endpoint has no rate limiting (GET in read_routes). Full-text + trigram similarity queries are expensive. Fix: add per-IP rate limit or move to write_routes group. (`src/routes/search.rs`) | |
| 25 | + | ||
| 26 | + | --- | |
| 27 | + | ||
| 9 | 28 | ## Code Review Remediation — Remaining | |
| 10 | 29 | - [ ] Transitive dep advisories: rand 0.8/0.9 (RUSTSEC-2026-0097), rsa (RUSTSEC-2023-0071), lru (RUSTSEC-2026-0002) — no direct fix available, monitor upstream | |
| 11 | 30 | - [ ] Add partial index on `posts.removed_at` (`WHERE removed_at IS NOT NULL`) when data volume warrants it |
| @@ -3385,7 +3385,7 @@ dependencies = [ | |||
| 3385 | 3385 | ||
| 3386 | 3386 | [[package]] | |
| 3387 | 3387 | name = "makenotwork" | |
| 3388 | - | version = "0.4.0" | |
| 3388 | + | version = "0.4.2" | |
| 3389 | 3389 | dependencies = [ | |
| 3390 | 3390 | "anyhow", | |
| 3391 | 3391 | "argon2", |
| @@ -120,7 +120,7 @@ Files > 100 MB are now held for review instead of downloaded into RAM. Next step | |||
| 120 | 120 | ||
| 121 | 121 | ## Code Fuzz Findings (2026-04-25) | |
| 122 | 122 | ||
| 123 | - | Two rounds of adversarial code review. 31 findings total: 30 fixed, 1 accepted risk, 1 deferred. | |
| 123 | + | Three rounds of adversarial code review. 51 findings total: 50 fixed, 1 accepted risk, 1 deferred. | |
| 124 | 124 | ||
| 125 | 125 | ### Accepted Risk | |
| 126 | 126 | - Idempotency check not atomic with operation — concurrent requests both execute (`db/idempotency.rs`). Safe because underlying ops are themselves idempotent. | |
| @@ -131,6 +131,46 @@ Two rounds of adversarial code review. 31 findings total: 30 fixed, 1 accepted r | |||
| 131 | 131 | ### Resolved (28 findings) | |
| 132 | 132 | All critical, serious, and minor findings from rounds 1 and 2 are fixed. See git history for details. | |
| 133 | 133 | ||
| 134 | + | ## Code Fuzz Findings -- Round 3 (2026-04-25) | |
| 135 | + | ||
| 136 | + | ### Fixed | |
| 137 | + | - [x] SubscriptionStatus missing Trialing/Incomplete/IncompleteExpired variants (db/enums.rs) | |
| 138 | + | - [x] SyncKit auth does not block deactivated users (synckit_auth.rs) | |
| 139 | + | - [x] Build token comparison not constant-time (routes/builds.rs) | |
| 140 | + | - [x] Subscription access checks ignore paused_at (db/subscriptions.rs) | |
| 141 | + | - [x] Cover/MediaImage uploads bypass storage cap with i64::MAX (db/creator_tiers.rs) | |
| 142 | + | - [x] Promo code use_count consumed even when user already owns item (db/transactions.rs) | |
| 143 | + | - [x] Idempotency middleware caches empty string instead of real body (metrics.rs) | |
| 144 | + | - [x] OAuth: no dummy hash on user-not-found path (timing leak, routes/oauth.rs) | |
| 145 | + | - [x] OAuth: no password length cap (Argon2 DoS, routes/oauth.rs) | |
| 146 | + | - [x] OAuth: suspended/deactivated users can still authorize (routes/oauth.rs) | |
| 147 | + | - [x] OAuth session validation: result.valid not checked before suspension (routes/oauth.rs) | |
| 148 | + | - [x] OAuth legacy session path ignores deactivated flag (routes/oauth.rs) | |
| 149 | + | - [x] Subscription test fixture missing paused_at field (db/models/subscription.rs) | |
| 150 | + | - [x] Git routes have no rate limiting (routes/git/mod.rs) | |
| 151 | + | - [x] No Content-Security-Policy header (lib.rs) | |
| 152 | + | - [x] /metrics endpoint unprotected (lib.rs) | |
| 153 | + | - [x] Upload confirm: no idempotency, no S3 cleanup on storage error (routes/storage/uploads.rs) | |
| 154 | + | - [x] Version confirm: no idempotency, no S3 cleanup on storage error (routes/storage/versions.rs) | |
| 155 | + | - [x] Image confirm: storage increment after DB write, no idempotency (routes/storage/images.rs) | |
| 156 | + | - [x] Media confirm: no content_type/extension re-validation, no S3 cleanup (routes/storage/media.rs) | |
| 157 | + | ||
| 158 | + | - [x] CSRF token not rotated after login -- now regenerated in login_user (auth.rs, csrf.rs) | |
| 159 | + | - [x] Read-modify-write race on sort_order/position -- atomic INSERT...SELECT (db/custom_links.rs, db/collections.rs) | |
| 160 | + | - [x] N+1 reorder loops without transactions -- wrapped in transactions (db/custom_links.rs, db/collections.rs) | |
| 161 | + | - [x] Race condition in custom domain creation -- SELECT FOR UPDATE in transaction (db/custom_domains.rs) | |
| 162 | + | ||
| 163 | + | - [x] Double-purchase TOCTOU -- partial unique index on (buyer_id, item_id/project_id) WHERE status='pending' prevents concurrent checkouts (migration 073, checkout handlers) | |
| 164 | + | ||
| 165 | + | - [x] Promo code max_uses bypassable on paid path -- use_count now reserved at checkout time, released by scheduler on stale cleanup (migration 074, checkout handlers, scheduler) | |
| 166 | + | ||
| 167 | + | ### Deferred | |
| 168 | + | - Rate limit IP extraction trusts X-Forwarded-For when traffic bypasses Cloudflare (helpers.rs). Fix requires splitting rate limit extraction by path: CF-Connecting-IP for public web routes, peer socket for internal/CLI/git. Needs careful routing since CLI, git smart HTTP, and SyncKit all hit the same server but some bypass Cloudflare. | |
| 169 | + | - S3 key/file size UPDATE queries lack ownership in SQL -- defense-in-depth; callers verify ownership (db/items.rs) | |
| 170 | + | ||
| 171 | + | ### Accepted | |
| 172 | + | - Revoked session usable for up to 30s -- session cache IS cleared by revoke_session and revoke_other_sessions handlers; window only applies to direct DB manipulation (admin) | |
| 173 | + | ||
| 134 | 174 | --- | |
| 135 | 175 | ||
| 136 | 176 | ## Creator Trust Audit (2026-04-25) | |
| @@ -140,8 +180,24 @@ Systematic creator-perspective audit of docs, legal, code, and competitive posit | |||
| 140 | 180 | ### Resolved (20+ findings) | |
| 141 | 181 | All doc/code fixes, trust gaps, security issues, and doc clarity items are complete. Key changes: subscription export endpoint, offsite backups with WAM alerting, API key hashing, security headers, fan subscription pause on suspension, account limbo state, support ticket portal, expanded tax/payout/discovery/storage docs, privacy policy updates. See git history. | |
| 142 | 182 | ||
| 143 | - | ### Remaining | |
| 144 | - | - [ ] No incident post-mortems or public historical incident log (process, not code) | |
| 183 | + | ### Doc/Code Contradictions (all resolved) | |
| 184 | + | - [x] Payout minimum: aligned payouts.md with Stripe reality ($1 minimum), named Stripe explicitly | |
| 185 | + | - [x] Content protection watermarking: scoped claim to "audio and image files delivered without watermarks", disclosed text fingerprinting | |
| 186 | + | - [x] Mailing list import: reworded as roadmap item, linked to migration guide | |
| 187 | + | - [x] IP retention: added daily scheduler job scrubbing IPs from user_sessions, download_fingerprints, streaming_sessions at 30 days | |
| 188 | + | ||
| 189 | + | ### Missing Creator-Facing Documentation | |
| 190 | + | - [x] Named Stripe explicitly in payouts.md and payments.md (replaced all "payment processor" references) | |
| 191 | + | - [x] Creator application criteria added to getting-started.md (what we look for, what gets rejected) | |
| 192 | + | - [x] Tips documented (new guide/tips.md) | |
| 193 | + | - [x] Bundles documented (new guide/bundles.md) | |
| 194 | + | - [x] Revenue splits documented (new guide/splits.md) | |
| 195 | + | - [x] Currencies/countries: linked to Stripe's own docs (46+ countries, 135+ currencies) rather than maintaining stale tables; fixed grammar from bulk find-replace | |
| 196 | + | ||
| 197 | + | ### Trust Gaps | |
| 198 | + | - [x] 30-day post-termination export window -- migration 076 adds terminated_at; admin terminate route (requires prior suspension); scheduler deletes after 30 days; Stripe subscriptions canceled; termination email sent with export instructions | |
| 199 | + | - [x] Per-item content removal admin action -- migration 075, admin routes (remove/restore), email notifications, publish guards prevent re-publishing removed items | |
| 200 | + | - ~~No incident post-mortems or public historical incident log~~ — will publish as posts in the MNW Changelog blog project | |
| 145 | 201 | ||
| 146 | 202 | ### Competitive Positioning (acknowledged, not bugs) | |
| 147 | 203 | - No free tier — deliberate tradeoff. Earn-back credit program planned. |
| @@ -2,6 +2,8 @@ | |||
| 2 | 2 | ||
| 3 | 3 | Your first 15 minutes on Makenot.work — from sign-up to your first published item. | |
| 4 | 4 | ||
| 5 | + | **Not ready to commit?** [Try sandbox mode](./sandbox.md) to explore the full creator dashboard without signing up. It lasts 1 hour and requires no account. | |
| 6 | + | ||
| 5 | 7 | ## Create Your Account | |
| 6 | 8 | ||
| 7 | 9 | 1. Visit the homepage and click **Join** | |
| @@ -26,16 +28,18 @@ To sell your work, apply for creator access: | |||
| 26 | 28 | ||
| 27 | 29 | Most applications are approved within a few days. You'll get an email when you're in. | |
| 28 | 30 | ||
| 31 | + | **What we look for:** We approve anyone who makes original creative work -- music, writing, software, art, video, courses. We don't require a minimum audience or existing sales history. Applications are rejected only for content that violates our [acceptable use policy](../legal/acceptable-use.md) (e.g., reselling others' work, prohibited content). | |
| 32 | + | ||
| 29 | 33 | ## Connect Payments | |
| 30 | 34 | ||
| 31 | - | Once approved as a creator, connect your payment account to receive fan payments: | |
| 35 | + | Once approved as a creator, connect Stripe to receive fan payments: | |
| 32 | 36 | ||
| 33 | 37 | 1. Go to your **Dashboard** | |
| 34 | 38 | 2. Click **Connect Payments** | |
| 35 | - | 3. Follow the payment onboarding flow | |
| 36 | - | 4. Complete identity verification (payment processor requirement) | |
| 39 | + | 3. Follow the Stripe onboarding flow | |
| 40 | + | 4. Complete identity verification (Stripe requirement) | |
| 37 | 41 | ||
| 38 | - | Payments go directly to your payment account. We never hold or touch your revenue. | |
| 42 | + | Payments go directly to your Stripe account. We never hold or touch your revenue. | |
| 39 | 43 | ||
| 40 | 44 | ## Create Your First Project | |
| 41 | 45 |
| @@ -0,0 +1,47 @@ | |||
| 1 | + | # Bundles | |
| 2 | + | ||
| 3 | + | Bundles let you sell multiple items together as a single purchase. A "Complete Discography" pack, a "Starter Kit" with templates and presets, or a "Full Course" combining individual modules. | |
| 4 | + | ||
| 5 | + | ## Creating a Bundle | |
| 6 | + | ||
| 7 | + | 1. Navigate to your project | |
| 8 | + | 2. Click **New Item** | |
| 9 | + | 3. Choose **Bundle** as the item type | |
| 10 | + | 4. Set a title, description, and price | |
| 11 | + | ||
| 12 | + | Bundles are items themselves -- they appear in your project listing alongside regular items. | |
| 13 | + | ||
| 14 | + | ## Adding Items to a Bundle | |
| 15 | + | ||
| 16 | + | After creating the bundle: | |
| 17 | + | ||
| 18 | + | 1. Open the bundle from your dashboard | |
| 19 | + | 2. Add items from the same project | |
| 20 | + | ||
| 21 | + | **Rules:** | |
| 22 | + | - Items must be in the same project as the bundle | |
| 23 | + | - Bundles cannot contain other bundles | |
| 24 | + | - An item can belong to multiple bundles | |
| 25 | + | - Items in a bundle can also be sold individually | |
| 26 | + | ||
| 27 | + | ## What Fans Get | |
| 28 | + | ||
| 29 | + | When a fan buys a bundle, they get immediate access to every item in it. Each child item appears in their library as if they had purchased it individually. | |
| 30 | + | ||
| 31 | + | If you add new items to a bundle later, existing buyers get access to them automatically. | |
| 32 | + | ||
| 33 | + | ## Pricing | |
| 34 | + | ||
| 35 | + | Set the bundle price independently of its contents. A bundle containing three $10 items could be priced at $25 (a discount) or $35 (a premium for convenience) -- your choice. | |
| 36 | + | ||
| 37 | + | Fans can also buy individual items without buying the bundle. Both paths are valid. | |
| 38 | + | ||
| 39 | + | ## Unlisted Items in Bundles | |
| 40 | + | ||
| 41 | + | Items marked as **unlisted** are hidden from your project's main listing but still appear as bundle contents. This is useful for items you only want available as part of a package. | |
| 42 | + | ||
| 43 | + | ## See Also | |
| 44 | + | ||
| 45 | + | - [Items](./items.md) -- Creating and managing individual items | |
| 46 | + | - [Collections](./collections.md) -- Curated lists (display-only, not purchasable as a unit) | |
| 47 | + | - [Promo Codes](./promo-codes.md) -- Discounts on bundles and items |
| @@ -53,7 +53,7 @@ Fans can unsubscribe from individual lists via the link in any email, or by unfo | |||
| 53 | 53 | ||
| 54 | 54 | ## Importing Subscribers | |
| 55 | 55 | ||
| 56 | - | You can import external email addresses (people without Makenot.work accounts) to your mailing lists. This is useful when migrating from another platform. | |
| 56 | + | Subscriber import from other platforms (CSV upload with consent emails) is on the roadmap but not yet available. When migrating, use the [data import tools](./migration.md) to bring over your content; your audience can follow your new project to subscribe. | |
| 57 | 57 | ||
| 58 | 58 | ## See Also | |
| 59 | 59 |
| @@ -4,14 +4,14 @@ | |||
| 4 | 4 | ||
| 5 | 5 | We use a direct-deposit payment model. When you sign up: | |
| 6 | 6 | ||
| 7 | - | 1. Connect or create a payment account | |
| 8 | - | 2. Verify your identity (payment processor requirement) | |
| 7 | + | 1. Connect or create a Stripe account | |
| 8 | + | 2. Verify your identity (Stripe requirement) | |
| 9 | 9 | 3. Link your bank account | |
| 10 | 10 | 4. Payments flow directly to you | |
| 11 | 11 | ||
| 12 | 12 | ## Payout Schedule | |
| 13 | 13 | ||
| 14 | - | You control this in your payment processor's dashboard: | |
| 14 | + | You control this in your [Stripe dashboard](https://dashboard.stripe.com): | |
| 15 | 15 | ||
| 16 | 16 | - **Instant**: Available immediately (small fee) | |
| 17 | 17 | - **Daily**: Next business day | |
| @@ -22,11 +22,11 @@ Default is standard payouts (2-3 business days). | |||
| 22 | 22 | ||
| 23 | 23 | ## Minimum Payout | |
| 24 | 24 | ||
| 25 | - | No minimum. Even $0.01 will transfer (though processing fees make tiny amounts impractical). | |
| 25 | + | Stripe requires a minimum balance before payout (typically $1). You can set a higher threshold if you prefer larger, less frequent payouts. Processing fees make very small amounts impractical regardless of the minimum. | |
| 26 | 26 | ||
| 27 | 27 | ## International Creators | |
| 28 | 28 | ||
| 29 | - | Our payment processor supports 40+ countries. Payouts are converted to your local currency by the payment processor when your country's currency differs from the purchase currency (USD). Conversion rates and any currency conversion fees are set by the payment processor, not by us. | |
| 29 | + | Stripe supports creators in [46+ countries](https://stripe.com/global). Payouts are converted to your local currency by Stripe when your country's currency differs from the purchase currency (USD). Conversion rates and any currency conversion fees are set by Stripe, not by us. | |
| 30 | 30 | ||
| 31 | 31 | If your country isn't supported, contact us — we're working on alternatives. | |
| 32 | 32 | ||
| @@ -36,19 +36,19 @@ You are responsible for your own tax obligations. We do not withhold taxes, calc | |||
| 36 | 36 | ||
| 37 | 37 | ### US Creators | |
| 38 | 38 | ||
| 39 | - | - The payment processor issues a **1099-K** if your gross payments exceed IRS reporting thresholds ($600/year as of 2024) | |
| 39 | + | - Stripe issues a **1099-K** if your gross payments exceed IRS [reporting thresholds](https://support.stripe.com/topics/1099-tax-forms) ($600/year as of 2024) | |
| 40 | 40 | - You are responsible for reporting all income, including amounts below the 1099-K threshold | |
| 41 | - | - We do not issue 1099-MISC or W-2 forms — your relationship is with the payment processor, not with us as an employer | |
| 41 | + | - We do not issue 1099-MISC or W-2 forms — your relationship is with Stripe, not with us as an employer | |
| 42 | 42 | - Keep your own records: your dashboard shows revenue charts and full transaction history, and you can export sales as CSV at any time | |
| 43 | 43 | - Consult a tax professional if you are unsure about your obligations — this is not tax advice | |
| 44 | 44 | ||
| 45 | 45 | ### Non-US Creators | |
| 46 | 46 | ||
| 47 | 47 | - Follow your local tax laws for self-employment or platform income | |
| 48 | - | - The payment processor provides transaction records and may issue tax forms required by your jurisdiction | |
| 48 | + | - Stripe provides transaction records and may issue tax forms required by your jurisdiction | |
| 49 | 49 | - VAT/GST collection is your responsibility — we do not currently collect or remit taxes on your behalf | |
| 50 | 50 | - We provide annual revenue summaries on request (email billing@makenot.work) | |
| 51 | - | - If your country requires tax withholding on cross-border payments, the payment processor handles this based on your W-8BEN or equivalent tax form | |
| 51 | + | - If your country requires tax withholding on cross-border payments, Stripe handles this based on your W-8BEN or equivalent tax form | |
| 52 | 52 | ||
| 53 | 53 | <!-- PENDING: This section should be reviewed by a tax professional. --> | |
| 54 | 54 | <!-- Specific areas: 1099-K threshold accuracy, international withholding guidance, VAT/GST obligations. --> | |
| @@ -62,15 +62,15 @@ Your dashboard shows: | |||
| 62 | 62 | - Per-project and per-item breakdowns | |
| 63 | 63 | - Full transaction history | |
| 64 | 64 | ||
| 65 | - | Your payment processor's dashboard provides additional payout detail (pending transfers, completed payouts, payout schedule). | |
| 65 | + | Your [Stripe dashboard](https://dashboard.stripe.com) provides additional payout detail (pending transfers, completed payouts, payout schedule). | |
| 66 | 66 | ||
| 67 | 67 | ## Refunds | |
| 68 | 68 | ||
| 69 | 69 | If a fan requests a refund and you approve: | |
| 70 | 70 | ||
| 71 | 71 | - Funds deducted from your next payout | |
| 72 | - | - Or from your payment account balance | |
| 73 | - | - Refund fees may apply (payment processor policy) | |
| 72 | + | - Or from your Stripe balance | |
| 73 | + | - Refund fees may apply ([Stripe refund policy](https://docs.stripe.com/refunds)) | |
| 74 | 74 | ||
| 75 | 75 | You have full control over refund decisions. | |
| 76 | 76 | ||
| @@ -78,3 +78,5 @@ You have full control over refund decisions. | |||
| 78 | 78 | ||
| 79 | 79 | - [Analytics & Dashboard](./analytics.md) — Revenue charts and transaction history | |
| 80 | 80 | - [Pricing & Monetization](./03-selling.md) — Setting prices and payment flow | |
| 81 | + | - [Stripe Global](https://stripe.com/global) — Supported countries | |
| 82 | + | - [Stripe Payouts](https://docs.stripe.com/payouts) — Payout schedules and timing |
| @@ -0,0 +1,65 @@ | |||
| 1 | + | # Sandbox Mode | |
| 2 | + | ||
| 3 | + | Try the full creator dashboard without signing up. Sandbox mode gives you an ephemeral account that works exactly like a real one — create projects, manage items, set pricing, write blog posts, generate license keys — with a few guardrails. | |
| 4 | + | ||
| 5 | + | ## Starting a Sandbox | |
| 6 | + | ||
| 7 | + | 1. Visit [/sandbox](/sandbox) (or click **Try the Dashboard** on the homepage) | |
| 8 | + | 2. Click **Start sandbox** | |
| 9 | + | 3. You're dropped into a pre-seeded dashboard with a demo project and two sample items | |
| 10 | + | ||
| 11 | + | No email, no password, no commitment. The session starts immediately. | |
| 12 | + | ||
| 13 | + | ## What Works | |
| 14 | + | ||
| 15 | + | Everything a creator can do in the dashboard works in sandbox mode: | |
| 16 | + | ||
| 17 | + | - **Projects** — Create, edit, delete, change visibility and settings | |
| 18 | + | - **Items** — Create any item type, set pricing (fixed, free, pay-what-you-want), add descriptions, manage chapters and sections | |
| 19 | + | - **Blog** — Write and publish blog posts with the full markdown editor | |
| 20 | + | - **Tags & metadata** — Organize content with tags, categories, and labels | |
| 21 | + | - **Subscription tiers** — Create tiers with simulated payment details | |
| 22 | + | - **License keys** — Configure license key settings and generate keys | |
| 23 | + | - **Promo codes** — Create discount and free-access codes | |
| 24 | + | - **Collections** — Group items into curated collections | |
| 25 | + | - **Custom links** — Add links to your profile | |
| 26 | + | - **File uploads** — Upload real files up to 5 MB each (50 MB total) | |
| 27 | + | - **Analytics** — View the revenue dashboard and sales charts (with your sandbox data) | |
| 28 | + | - **Export** — Export your sandbox data as CSV or JSON | |
| 29 | + | ||
| 30 | + | ## What's Different | |
| 31 | + | ||
| 32 | + | A few things are restricted to keep sandbox mode safe and ephemeral: | |
| 33 | + | ||
| 34 | + | | Feature | Sandbox behavior | | |
| 35 | + | |---------|-----------------| | |
| 36 | + | | **Stripe payments** | Simulated. Subscription tiers get placeholder IDs. No real money moves. | | |
| 37 | + | | **Broadcast email** | Blocked. No emails are sent from sandbox accounts. | | |
| 38 | + | | **Follows** | Blocked. Sandbox accounts can't follow real users. | | |
| 39 | + | | **Public visibility** | Sandbox content never appears on the discover page, search, or public profiles. | | |
| 40 | + | | **2FA / passkeys** | Disabled. Security setup on a 1-hour account isn't useful. | | |
| 41 | + | | **File size** | 5 MB per file, 50 MB total (real accounts get up to 20 GB per file depending on tier). | | |
| 42 | + | | **Duration** | 1 hour. After that, the account and all its data are permanently deleted. | | |
| 43 | + | ||
| 44 | + | ## Limits | |
| 45 | + | ||
| 46 | + | - **1 hour** — your sandbox expires automatically. There is no way to extend it. | |
| 47 | + | - **5 MB per file** — enough to upload sample audio, cover images, or small downloads. | |
| 48 | + | - **50 MB total storage** — keeps sandbox resource usage bounded. | |
| 49 | + | - **Rate limited** — a maximum of 2 sandboxes can be created per 30 seconds from the same IP, with 3 concurrent sandboxes active at once. | |
| 50 | + | ||
| 51 | + | ## Converting to a Real Account | |
| 52 | + | ||
| 53 | + | Sandbox data cannot be transferred to a real account. The sandbox is for exploration, not production use. When you're ready: | |
| 54 | + | ||
| 55 | + | 1. [Create a real account](/join) | |
| 56 | + | 2. [Apply for creator access](/creators) | |
| 57 | + | 3. Recreate the projects and items you liked from your sandbox session | |
| 58 | + | ||
| 59 | + | The sandbox is designed to answer "what would this feel like?" — not to be a staging environment. | |
| 60 | + | ||
| 61 | + | ## See Also | |
| 62 | + | ||
| 63 | + | - [Getting Started](./01-getting-started.md) — Full account setup walkthrough | |
| 64 | + | - [Content Types](./02-content.md) — Items, uploads, and organization | |
| 65 | + | - [Pricing Tiers](./tiers.md) — File limits and features per tier |
| @@ -0,0 +1,58 @@ | |||
| 1 | + | # Revenue Splits | |
| 2 | + | ||
| 3 | + | Revenue splits let you share earnings with collaborators on a project. Add a co-author, producer, or contributor and specify what percentage of each sale they receive. | |
| 4 | + | ||
| 5 | + | ## Adding a Collaborator | |
| 6 | + | ||
| 7 | + | 1. Go to your project's **Members** tab in the dashboard | |
| 8 | + | 2. Enter the collaborator's Makenot.work username | |
| 9 | + | 3. Set their split percentage (1-99%) | |
| 10 | + | 4. Click **Add Member** | |
| 11 | + | ||
| 12 | + | The collaborator must have a Makenot.work account. They do not need to be a creator or have Stripe connected (though they will need Stripe to receive payouts). | |
| 13 | + | ||
| 14 | + | ## How Splits Work | |
| 15 | + | ||
| 16 | + | When a fan buys any item in the project or sends a tip: | |
| 17 | + | ||
| 18 | + | - The split is calculated automatically based on the configured percentages | |
| 19 | + | - You (the project owner) receive the remainder after all member splits | |
| 20 | + | - Split records are created immediately when payment completes | |
| 21 | + | ||
| 22 | + | **Example:** You set a collaborator at 30%. A fan buys a $10 item. After Stripe's ~$0.59 processing fee, the collaborator's share is $2.82 (30% of $9.41) and yours is $6.59. | |
| 23 | + | ||
| 24 | + | ## Payouts | |
| 25 | + | ||
| 26 | + | Split obligations are **recorded, not transferred automatically**. The full payment goes to your Stripe account (you are the merchant of record), and you settle with collaborators directly. Your dashboard tracks what you owe each collaborator so nothing falls through the cracks. | |
| 27 | + | ||
| 28 | + | Automated split payouts (direct Stripe transfers to collaborators) are on the roadmap. | |
| 29 | + | ||
| 30 | + | ## What Collaborators See | |
| 31 | + | ||
| 32 | + | Collaborators can view their split records in their own dashboard: | |
| 33 | + | ||
| 34 | + | - Amount owed per transaction | |
| 35 | + | - Their percentage | |
| 36 | + | - Source (item sale or tip) | |
| 37 | + | - Running total of unsettled splits | |
| 38 | + | ||
| 39 | + | They can also export split records as CSV. | |
| 40 | + | ||
| 41 | + | ## Roles | |
| 42 | + | ||
| 43 | + | | Role | Can edit project | Can add members | Receives splits | | |
| 44 | + | |------|-----------------|-----------------|-----------------| | |
| 45 | + | | **Owner** | Yes | Yes | Remainder after splits | | |
| 46 | + | | **Member** | No | No | Configured percentage | | |
| 47 | + | ||
| 48 | + | Only the project owner can add, remove, or change member splits. | |
| 49 | + | ||
| 50 | + | ## Removing a Collaborator | |
| 51 | + | ||
| 52 | + | Removing a member sets their split to 0% for future sales. Existing split records (for past sales) are preserved -- you still owe what was already earned. | |
| 53 | + | ||
| 54 | + | ## See Also | |
| 55 | + | ||
| 56 | + | - [Projects](./projects.md) -- Project setup and management | |
| 57 | + | - [Payouts](./payouts.md) -- Receiving your earnings | |
| 58 | + | - [Tips](./tips.md) -- Tips are also split among collaborators |
| @@ -0,0 +1,42 @@ | |||
| 1 | + | # Tips | |
| 2 | + | ||
| 3 | + | Tips let fans send one-time payments to creators without buying a specific item. Think of it as a "thank you" or "keep going" button. | |
| 4 | + | ||
| 5 | + | ## Enabling Tips | |
| 6 | + | ||
| 7 | + | Tips are off by default. To turn them on: | |
| 8 | + | ||
| 9 | + | 1. Go to **Dashboard > Settings** | |
| 10 | + | 2. Enable **Accept Tips** | |
| 11 | + | 3. Stripe must be connected with charges enabled | |
| 12 | + | ||
| 13 | + | Once enabled, a tip button appears on your public profile page. | |
| 14 | + | ||
| 15 | + | ## How Fans Tip | |
| 16 | + | ||
| 17 | + | 1. Fan visits your profile (`/u/yourusername`) | |
| 18 | + | 2. Clicks the tip button | |
| 19 | + | 3. Enters an amount ($1 - $10,000) and an optional message (up to 280 characters) | |
| 20 | + | 4. Completes payment through Stripe Checkout | |
| 21 | + | ||
| 22 | + | The full amount minus Stripe's processing fee (~2.9% + $0.30) goes directly to your Stripe account. We take no cut. | |
| 23 | + | ||
| 24 | + | ## Viewing Tips | |
| 25 | + | ||
| 26 | + | Your dashboard shows all tips received: who tipped, how much, any message they included, and when. You can also view total tip revenue in your analytics. | |
| 27 | + | ||
| 28 | + | Fans can see their tip history in their own account. | |
| 29 | + | ||
| 30 | + | ## Tips and Revenue Splits | |
| 31 | + | ||
| 32 | + | If your project has collaborators with revenue splits configured, tips are split according to the same percentages as item sales. | |
| 33 | + | ||
| 34 | + | ## Notifications | |
| 35 | + | ||
| 36 | + | When you receive a tip, you get an email notification (if enabled in your notification settings). | |
| 37 | + | ||
| 38 | + | ## See Also | |
| 39 | + | ||
| 40 | + | - [Payments & Refunds](../legal/payments.md) -- How payments work | |
| 41 | + | - [Payouts](./payouts.md) -- Receiving your earnings | |
| 42 | + | - [Revenue Splits](./splits.md) -- Sharing revenue with collaborators |
| @@ -6,7 +6,7 @@ How payments work on Makenot.work. | |||
| 6 | 6 | ||
| 7 | 7 | ## The Short Version | |
| 8 | 8 | ||
| 9 | - | Our payment processor handles all payments. You're the merchant of record. We don't take a cut of your revenue. Fans handle refunds with you or dispute via their bank. | |
| 9 | + | Stripe handles all payments. You're the merchant of record. We don't take a cut of your revenue. Fans handle refunds with you or dispute via their bank. | |
| 10 | 10 | ||
| 11 | 11 | --- | |
| 12 | 12 | ||
| @@ -16,8 +16,8 @@ Our payment processor handles all payments. You're the merchant of record. We do | |||
| 16 | 16 | ||
| 17 | 17 | When fans buy your content or subscribe: | |
| 18 | 18 | ||
| 19 | - | 1. Fan pays via payment processor (~2.9% + $0.30 per transaction) | |
| 20 | - | 2. Funds go directly to your connected payment account | |
| 19 | + | 1. Fan pays via Stripe (~2.9% + $0.30 per transaction) | |
| 20 | + | 2. Funds go directly to your connected Stripe account | |
| 21 | 21 | 3. Deposits to your bank on your chosen schedule | |
| 22 | 22 | 4. We never touch or hold your money | |
| 23 | 23 | ||
| @@ -33,12 +33,12 @@ Your monthly Makenot.work subscription ($10-40) is separate: | |||
| 33 | 33 | ||
| 34 | 34 | ## You're the Merchant of Record | |
| 35 | 35 | ||
| 36 | - | When you connect payments, the processor creates an account for you. This means: | |
| 36 | + | When you connect Stripe, it creates an account for you. This means: | |
| 37 | 37 | ||
| 38 | 38 | - **You are the merchant** - Fans are paying you, not us | |
| 39 | 39 | - **You set prices** - Including pay-what-you-want options | |
| 40 | 40 | - **You handle disputes** - Chargebacks and refunds are your responsibility | |
| 41 | - | - **Your payment account is yours** - If you leave, it stays with you | |
| 41 | + | - **Your Stripe account is yours** - If you leave, it stays with you | |
| 42 | 42 | ||
| 43 | 43 | This keeps your revenue in your hands. The tradeoff: you absorb the (typically rare) chargeback risk. | |
| 44 | 44 | ||
| @@ -46,7 +46,7 @@ This keeps your revenue in your hands. The tradeoff: you absorb the (typically r | |||
| 46 | 46 | ||
| 47 | 47 | ## Payouts | |
| 48 | 48 | ||
| 49 | - | You control when funds move from your payment account balance to your bank: | |
| 49 | + | You control when funds move from your Stripe balance to your bank: | |
| 50 | 50 | ||
| 51 | 51 | | Schedule | Timing | Notes | | |
| 52 | 52 | |----------|--------|-------| | |
| @@ -75,7 +75,7 @@ We don't process refunds on your behalf. You're the merchant—refund decisions | |||
| 75 | 75 | ||
| 76 | 76 | Fans should contact you directly. You can: | |
| 77 | 77 | ||
| 78 | - | - Issue a full or partial refund via your payment processor's dashboard | |
| 78 | + | - Issue a full or partial refund via your [Stripe dashboard](https://dashboard.stripe.com) | |
| 79 | 79 | - Offer alternative resolution (access fix, different content) | |
| 80 | 80 | - Decline if the purchase was delivered as promised | |
| 81 | 81 | ||
| @@ -96,7 +96,7 @@ A $5 refund is cheaper than a $15 chargeback fee. | |||
| 96 | 96 | If a fan disputes a charge with their bank: | |
| 97 | 97 | ||
| 98 | 98 | 1. **Your balance is debited** - Disputed amount plus fee (~$15) | |
| 99 | - | 2. **You respond through the payment processor** - Provide evidence the charge was legitimate | |
| 99 | + | 2. **You respond through Stripe** - Provide evidence the charge was legitimate | |
| 100 | 100 | 3. **Bank decides** - You either get the money back or lose it | |
| 101 | 101 | ||
| 102 | 102 | ### Why This Model | |
| @@ -134,7 +134,7 @@ Consult a tax professional for your specific situation. | |||
| 134 | 134 | ### What We Provide | |
| 135 | 135 | ||
| 136 | 136 | - **Transaction records** - Exportable history of all payments | |
| 137 | - | - **1099-K** (US creators) - Issued by the payment processor if you meet thresholds | |
| 137 | + | - **1099-K** (US creators) - Issued by Stripe if you meet [reporting thresholds](https://support.stripe.com/topics/1099-tax-forms) | |
| 138 | 138 | - **Invoices** - For your platform subscription | |
| 139 | 139 | ||
| 140 | 140 | ### Your Responsibilities | |
| @@ -153,22 +153,22 @@ Your Makenot.work subscription may be deductible as a business expense. Keep you | |||
| 153 | 153 | ||
| 154 | 154 | ## International Payments | |
| 155 | 155 | ||
| 156 | - | Our payment processor supports 40+ countries. Creators outside the US: | |
| 156 | + | Stripe supports creators in [46+ countries](https://stripe.com/global). Creators outside the US: | |
| 157 | 157 | ||
| 158 | - | - Set up a payment account in your country | |
| 159 | - | - Receive payments in multiple currencies | |
| 160 | - | - The payment processor handles currency conversion | |
| 158 | + | - Set up a Stripe account in your country | |
| 159 | + | - Receive payments in [135+ currencies](https://docs.stripe.com/currencies) | |
| 160 | + | - Stripe handles currency conversion automatically | |
| 161 | 161 | ||
| 162 | - | Conversion rates and timing vary by country. Check your payment processor's documentation for specifics. | |
| 162 | + | Conversion rates and timing vary by country. See [Stripe's payout documentation](https://docs.stripe.com/payouts) for specifics. | |
| 163 | 163 | ||
| 164 | 164 | --- | |
| 165 | 165 | ||
| 166 | 166 | ## What We Don't Do | |
| 167 | 167 | ||
| 168 | - | - **Hold creator funds** - Money goes directly to your payment account | |
| 168 | + | - **Hold creator funds** - Money goes directly to your Stripe account | |
| 169 | 169 | - **Take a percentage** - Our fee is flat monthly subscription only | |
| 170 | 170 | - **Control your pricing** - Set whatever prices you want | |
| 171 | - | - **Process payments ourselves** - Our payment processor handles everything | |
| 171 | + | - **Process payments ourselves** - Stripe handles everything | |
| 172 | 172 | - **Provide tax advice** - Consult a professional | |
| 173 | 173 | ||
| 174 | 174 | --- | |
| @@ -177,4 +177,6 @@ Conversion rates and timing vary by country. Check your payment processor's docu | |||
| 177 | 177 | ||
| 178 | 178 | - [Creator Guarantees](../about/guarantees.md) - our commitments on revenue | |
| 179 | 179 | - [Pricing Tiers](../guide/tiers.md) - tier features and pricing | |
| 180 | - | - [Infrastructure & Vendors](../tech/infrastructure.md) - current payment processor details | |
| 180 | + | - [Stripe Global](https://stripe.com/global) - supported countries | |
| 181 | + | - [Stripe Currencies](https://docs.stripe.com/currencies) - supported currencies | |
| 182 | + | - [Stripe Connect](https://stripe.com/connect) - how the payment model works |
| @@ -12,7 +12,9 @@ We won't be part of that. | |||
| 12 | 12 | ||
| 13 | 13 | ## What We Do Instead | |
| 14 | 14 | ||
| 15 | - | We give fans clean, unencumbered files. Presigned download URLs are time-limited and tied to authenticated accounts, so casual link-sharing doesn't work. But we don't embed watermarks, restrict playback, or limit how fans use files they've paid for. | |
| 15 | + | We give fans clean, unencumbered files. Presigned download URLs are time-limited and tied to authenticated accounts, so casual link-sharing doesn't work. We don't restrict playback or limit how fans use files they've paid for. | |
| 16 | + | ||
| 17 | + | For text files (README, LICENSE, liner notes), we may embed an invisible fingerprint tied to the buyer's purchase. This lets creators trace leaked files back to a specific download without affecting the content's usability. Audio and image files are delivered without watermarks. | |
| 16 | 18 | ||
| 17 | 19 | If you need stronger protection than this, we may not be the right platform for your content. We'd rather be honest about that upfront. | |
| 18 | 20 |
| @@ -53,7 +53,7 @@ pub async fn docs_index( | |||
| 53 | 53 | // Post-process the Guide section: bucket entries into subcategories. | |
| 54 | 54 | if let Some(guide) = sections.iter_mut().find(|s| s.name == "Guide") { | |
| 55 | 55 | const SUBCATEGORIES: &[(&str, &[&str])] = &[ | |
| 56 | - | ("Getting Started", &["01-getting-started", "profile", "security", "best-practices"]), | |
| 56 | + | ("Getting Started", &["01-getting-started", "sandbox", "profile", "security", "best-practices"]), | |
| 57 | 57 | ("Content & Organization", &["02-content", "items", "projects", "files", "tags", "metadata", "collections", "blog"]), | |
| 58 | 58 | ("Selling & Revenue", &["03-selling", "pricing", "payouts", "analytics", "promo-codes", "contact-sharing", "fan-plus"]), | |
| 59 | 59 | ("Fans & Distribution", &["fan-guide", "rss", "mailing-lists", "export"]), |