Initial commit

This commit is contained in:
Michilis
2025-12-14 23:08:45 -03:00
commit 1e1753dff3
58 changed files with 18294 additions and 0 deletions

837
specs.md Normal file
View File

@@ -0,0 +1,837 @@
# Paid Link Paywall Platform
Goal
Build a product that lets creators turn any link into paid access in under 60 seconds, and embed the paywall on any website. Users do not need to know Nostr exists. No custody of user funds.
Core one-liner
Paste a link, set a price, share or embed, get paid.
Non-goals for v1
* No community features (posts, comments, member feeds)
* No file hosting (unless optional later)
* No subscription memberships (one-time purchase only in v1)
* No complex analytics or funnels
* No marketplace or discovery directory
Key principles
* Fastest possible creator onboarding: 1 minute to first paywall
* Buyer friction is minimal: open link, pay, unlock
* Works with existing content locations (Notion, Google Docs, PDFs, Loom, YouTube unlisted, private web pages, GitHub, etc.)
* Embed-first product: creators can drop into Webflow, WordPress, Framer, custom sites
* Security is token-based and server-verified
* Payment is non-custodial: platform only checks payment status and grants access
---
## Product surfaces
1. Creator web app
* Landing page
* Auth
* Creator dashboard
* Create paywall flow
* Paywall detail page
* Sales list
* Payouts and settings
* Embed docs page
2. Buyer paywall pages
* Hosted paywall page (shareable)
* Embedded paywall UI (iframe)
* Embedded button + checkout modal (script)
3. Developer API
* Create paywall
* Update paywall
* List paywalls
* Create checkout session
* Verify access token
* Webhooks
---
## Roles and permissions
Roles
* Creator (default)
* Admin
Creator can
* Create/update/archive paywalls
* View sales
* Configure payout destination
* Generate embed code
* Configure access rules
Admin can
* View all creators
* View paywalls
* View sales
* Manage disputes and refunds references (actual refunds depend on payment provider)
* Disable creators or paywalls
* Configure global settings
---
## Data model
Entities
Creator
* id (uuid)
* created_at
* status (active, disabled)
* display_name
* email (optional if using email login)
* auth_providers (password, nostr, google, github)
* nostr_pubkey (optional)
* avatar_url
* default_currency (sats)
* payout_config (provider-specific)
* tax_info (future)
Paywall
* id (uuid)
* creator_id
* status (active, archived, disabled)
* created_at, updated_at
* title
* description (short)
* cover_image_url (optional)
* original_url (the target link)
* original_url_type (url, youtube, notion, pdf, loom, other)
* preview_mode (none, text_preview, image_preview)
* preview_content (optional)
* price_sats (integer)
* access_expiry_seconds (optional; null means no expiry)
* max_devices (optional; default 3)
* max_sessions (optional; default 5)
* allow_embed (boolean)
* allowed_embed_origins (list of domains, optional)
* require_email_receipt (boolean)
* custom_success_message (optional)
* custom_branding (theme overrides, optional)
* slug (optional human friendly)
CheckoutSession
* id (uuid)
* paywall_id
* created_at
* status (pending, paid, expired, canceled)
* amount_sats
* payment_provider (lnbits, openNode, strike, etc)
* payment_request (invoice string or provider id)
* expires_at
* buyer_hint (ip hash, user agent hash)
AccessGrant
* id (uuid)
* paywall_id
* created_at
* expires_at (nullable)
* status (active, revoked)
* buyer_id (nullable)
* token_id (uuid)
* last_used_at
* usage_count
Buyer
* id (uuid)
* created_at
* email (optional)
* email_verified (boolean)
* nostr_pubkey (optional)
* notes (optional)
AccessToken
* token_id (uuid)
* paywall_id
* buyer_id (nullable)
* issued_at
* expires_at (nullable)
* scopes (view)
* signature (server signed)
Sale
* id (uuid)
* paywall_id
* creator_id
* created_at
* amount_sats
* platform_fee_sats
* net_sats
* payment_provider
* provider_reference
* buyer_id (nullable)
* status (confirmed, refunded, chargeback, disputed)
WebhookEvent
* id
* created_at
* provider
* event_type
* raw_payload
* processed_at
* status
AuditLog
* id
* created_at
* actor (creator/admin/system)
* action
* resource_type
* resource_id
* ip
* metadata
---
## Authentication
Creator login options
* Email + password
* Nostr login (NIP-07 or signer)
* Google OAuth
* GitHub OAuth
Buyer login
* No account required for purchase
* Optional email input for receipt and re-access
* Optional Nostr login for portable access later
Session management
* JWT access token (short)
* Refresh token (httpOnly cookie)
* Rate limiting on auth endpoints
* 2FA is future
Nostr integration
* If creator uses Nostr login: store pubkey and verify signed challenge.
* If creator uses email login: system creates and stores a backend keypair to sign certain actions internally (never shown to user). This key is per-creator and encrypted at rest.
---
## Payments
v1 payment methods
* Lightning only (invoice-based)
Implementation options
* LNbits as payment backend
* Alternative provider module interface to swap later
Important constraints
* Platform does not custody funds: invoice is generated for creator payout wallet or a routed invoice to platform then forwarded is not allowed.
Recommended model
* Platform maintains a service wallet only for collecting platform fees.
* Creator config includes their own Lightning Address or LNURLp.
* For each checkout, platform generates:
* one invoice to creator for net amount
* one invoice to platform for fee
* or a split payment mechanism if supported by provider.
If split is not supported
* v1 can temporarily charge a platform fee via subscription model instead of per-sale fee to avoid custodial flow.
Fee model
* Free tier: 10 percent per sale
* Pro tier: 15 per month + 3 percent per sale
---
## Core flows
### Flow A: Creator creates a paywall
Entry points
* Dashboard primary CTA: Create Paywall
* Top nav button
Steps
1. Paste URL
2. System fetches metadata:
* page title
* description
* favicon
* open graph image
3. Creator edits:
* title
* short description
* cover image
4. Set price
* sats integer
5. Set access rules
* expiry: none / 24h / 7d / 30d / custom
* max devices: default 3
* allow embed: on
* allowed origins: optional list
6. Generate output
* Share link
* Embed code (iframe)
* Button embed code
7. Save
Success state
* Show the paywall URL
* Copy buttons
* Preview mode
Validation
* URL must be https
* Block localhost and private IP ranges
* Block obvious malware/phishing domains (basic allow/deny list)
---
### Flow B: Buyer purchases access via hosted paywall page
Entry
* buyer opens [https://app.domain/p/{slug_or_id}](https://app.domain/p/{slug_or_id})
Paywall page sections
* cover image
* title
* short description
* price
* what you get (simple)
* pay button
* trust line (secure payment)
When pay is clicked
* create checkout session
* render QR invoice
* show invoice string copy
* show countdown timer
* poll payment status every 2 seconds, max 2 minutes
* on paid:
* issue access token
* redirect to unlocked view
Unlocked view
* show success message
* show button: Open content
* auto redirect after 3 seconds
* store access token in secure cookie and localStorage token id (for iframe context)
Re-access
* if cookie exists and token valid: show Open content
* if token expired: show repurchase prompt
---
### Flow C: Embedded iframe paywall
Creator embeds
<iframe src="https://app.domain/embed/{paywall_id}" ...></iframe>
States
* Loading state
* Locked state
* Checkout state (inside iframe)
* Unlocked state
Locked state UI
* small cover
* title
* price
* Unlock button
Unlock
* opens checkout inside iframe
* after paid, iframe switches to unlocked
Unlocked state behaviors
Option 1: reveal content within iframe by rendering a secure redirect button
Option 2: open target link in new tab with token parameter (less secure)
Option 3: show an inline preview plus open link button
Recommended v1
* Open target link in new tab after payment, and show persistent “Open again” button.
Origin restrictions
* If creator set allowed origins, embed endpoint checks the request referrer/origin.
* If mismatch: show error: embedding not allowed.
---
### Flow D: Button embed + modal checkout
Embed snippet
<script src="https://app.domain/js/paywall.js" data-paywall="{id}" data-theme="auto"></script>
Behavior
* Script finds placeholder element or injects a button.
* On click, opens modal overlay.
* Modal shows paywall details and invoice.
* On paid, modal shows success and “Open content” button.
Constraints
* Must be CSP-friendly
* Must not break SSR sites
* Provide a no-JS fallback link
---
### Flow E: Creator revenue and sales tracking
Dashboard widgets
* Total revenue (last 7d, 30d)
* Net earnings
* Top paywalls
* Sales count
Sales table
* date
* paywall
* amount
* fee
* net
* status
Filters
* date range
* paywall
Exports
* CSV export v1 optional
---
## Pages and UI spec
### Public landing page
Sections
* Hero headline: “Turn any link into paid access in 60 seconds.”
* Subheadline: “No uploads. No platform lock-in. Share or embed anywhere.”
* CTA: “Create a paywall”
* Demo embed showing locked/unlocked
* Use cases grid (Notion, Google Docs, PDFs, videos)
* Pricing
* FAQ
* Footer
Design
* Modern, premium, minimal
* Dark mode first with light mode toggle
* Rounded corners, soft shadows
* Strong typography
* No crypto jargon
### Auth pages
* Login
* Signup
* Provider buttons (Google, GitHub, Nostr)
* Forgot password
### Creator dashboard
Layout
* Left sidebar
* Overview
* Paywalls
* Sales
* Embeds
* Settings
* Help
* Top bar
* Create Paywall
* Profile menu
Overview page
* KPI cards
* Sales chart (simple line)
* Recent sales list
* Quick create CTA
Paywalls list
* Table / cards
* Status chips
* Copy link
* Copy embed
* Edit
* Archive
Create paywall page
* Stepper UI (Paste link -> Details -> Price -> Access -> Output)
Paywall detail page
* Preview hosted page
* Settings panels
* Copy buttons
* Embed origins list
* Regenerate embed codes
Sales page
* Sales table
* Export
Embeds page
* Docs and copy-paste snippets
* Testing sandbox
Settings
* Profile
* Payout destination
* Branding (logo, primary color)
* Domain/whitelabel (future)
* Security (sessions)
### Buyer hosted paywall page
* Responsive
* Minimal checkout
* QR centered
* Clear instructions
* After pay: success state
---
## Look and feel
Theme
* Primary color: choose one brand color; allow creator override later
* Background: dark #0b0f14 style
* Cards: slightly lighter panels
* Typography: modern sans
* Spacing: generous
Components
* Buttons: large, rounded, high contrast
* Inputs: simple
* Toasts for copy actions
* Skeleton loaders
Microcopy tone
* Clear, short, confident
* Avoid crypto words
* Use “sats” not BTC
Accessibility
* AA contrast
* keyboard navigable
* screen reader labels
---
## Backend architecture
Recommended stack
* FastAPI or Node/Express
* Postgres
* Redis for session and rate limit
* Worker queue for webhook processing
Services
1. API service
* REST endpoints
* auth
* paywall CRUD
* checkout sessions
* access verification
2. Webhook worker
* provider webhooks
* marks CheckoutSession paid
* issues AccessGrant
* records Sale
3. Embed service
* serves iframe content
* verifies origin
* uses access token
Security
* Strict input validation
* SSRF protection on metadata fetch
* Rate limits
* IP throttling on checkout creation
* Signed tokens (JWT with rotating keys)
* Encrypt secrets at rest
Metadata fetcher
* Fetch open graph tags
* Timeout 3 seconds
* Only allow https
* Block private ranges
---
## API endpoints (v1)
Auth
* POST /auth/signup
* POST /auth/login
* POST /auth/logout
* POST /auth/refresh
* POST /auth/oauth/{provider}/start
* POST /auth/oauth/{provider}/callback
* POST /auth/nostr/challenge
* POST /auth/nostr/verify
Paywalls
* POST /paywalls
* GET /paywalls
* GET /paywalls/{id}
* PATCH /paywalls/{id}
* POST /paywalls/{id}/archive
Checkout
* POST /paywalls/{id}/checkout
* GET /checkout/{session_id}
Access
* POST /access/verify
* POST /access/revoke
Public
* GET /p/{slug_or_id} (render)
* GET /embed/{id} (render)
* GET /js/paywall.js (button script)
Webhooks
* POST /webhooks/{provider}
Admin
* GET /admin/creators
* GET /admin/paywalls
* GET /admin/sales
* POST /admin/paywalls/{id}/disable
---
## Token and access logic
On payment confirmed
* Create AccessGrant
* Issue AccessToken JWT
Claims
* token_id
* paywall_id
* buyer_id (optional)
* iat
* exp (optional)
Storage
* httpOnly cookie: access_jwt
* localStorage: token_id (for embed communication)
Verification
* /access/verify checks signature and status
* checks revoked
* checks expiry
* checks max device fingerprint count
Device fingerprint
* hash of user agent + stable client id cookie
* do not use invasive fingerprinting
Limits
* max devices default 3
* if exceeded: prompt to “Reset devices” (creator can allow)
---
## Abuse prevention
Threats
* Link sharing
* Invoice replay
* Token theft
* Bots spamming checkout sessions
* SSRF via URL fetch
Controls
* Access tokens tied to limited devices
* Session expiry and revocation
* Rate limit checkout creation
* Webhook idempotency
* Block private network URL fetch
---
## Observability and operations
Logging
* request id
* creator id
* paywall id
* checkout session id
* webhook event id
Metrics
* checkouts created
* conversion rate
* webhook latency
* embed loads
* errors
Alerts
* webhook failures
* payment provider downtime
---
## Deployment
Environments
* dev
* staging
* production
Secrets
* provider keys
* jwt signing keys
* db password
CDN
* cache static assets
* do not cache paywall pages with personal state
---
## v1 acceptance criteria
Creator
* Can sign up and create a paywall in under 60 seconds
* Can copy share link and embed code
* Can see sales appear after payments
Buyer
* Can pay via lightning invoice and unlock within seconds
* Can revisit link and still access if not expired
Embed
* Iframe works on standard websites
* Button script opens modal checkout
Security
* Tokens are signed and validated server-side
* Metadata fetch is protected against SSRF
---
## Roadmap after v1
v1.1
* Email receipts
* Custom success message
* Better origin controls
v1.2
* Whitelabel domains
* Team accounts
v2
* Subscriptions
* Bundles
* Creator pages
* Affiliate links