Skip to content

feat(billing): sell the signed macOS DMG, drop license keys - #31

Open
0xNyk wants to merge 1 commit into
mainfrom
feat/paid-dmg-download
Open

0xNyk wants to merge 1 commit into
mainfrom
feat/paid-dmg-download

Conversation

@0xNyk

@0xNyk 0xNyk commented Sep 17, 2026

Copy link
Copy Markdown
Member

Summary

Free is the GPL source; the paid product is the signed, notarized macOS app. License keys and the in-app Pro entitlement are removed.

  • Download gate: POST /api/download/macos verifies the Stripe Checkout Session (paid, correct price, live mode, not refunded, disputed or partially refunded) and returns a 300-second presigned URL to a private R2 bucket. The SigV4 presigner uses node:crypto only and matches the AWS documented test vector.
  • Re-download: /buy/success is the bookmarkable download page; /download accepts the pasted link. /buy/success loads no analytics, because its URL carries the session id.
  • App: Pro store, commands, activation UI and Pro-gated update checks are removed. Updates are on for everyone from https://updates.dictx.splitlabs.io/latest.json. bundle.license corrected to GPL-3.0-or-later.
  • Release CI: macOS only. DMGs go to the private R2 bucket, updater bundles and latest.json to the public bucket. PR and manual test builds are unsigned and read-only, so signed DMGs never land in public run artifacts and PR code never sees signing secrets.
  • Docs: README, CLAUDE.md, HANDOFF.md, docs/commercial/checkout-migration.md and landing/README.md describe the flow, secrets and one-time setup.

Verification

  • node --test landing/tests/*.test.js: 12/12
  • bun run lint, bun run build, Prettier on changed files: pass
  • cargo fmt -- --check, cargo clippy -- -D warnings: pass
  • Independent review: pass, including a separate Python SigV4 implementation matching the JS output byte for byte, and a command-by-command check of bindings.ts against collect_commands!
  • Security audit: no download without a valid purchase; its findings are fixed in this branch
  • check:translations fails only on the settings.general.transcribeHover.* keys already missing on main

Not yet exercised: a real release run against R2, and a live Stripe test-mode purchase.

Before merging or releasing

  1. Create a private and a public R2 bucket; attach updates.dictx.splitlabs.io to the public one only.
  2. CI token (read and write, both buckets) as GitHub secrets: R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_PRIVATE_BUCKET, R2_PUBLIC_BUCKET.
  3. Read-only token (private bucket) as Vercel Production variables: R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_PRIVATE_BUCKET. Remove DICTX_LICENSE_SECRET. Redeploy.
  4. Keep promotion codes off on the Payment Link; add a Vercel WAF rate limit on /api/download/*.
  5. Do a test-mode purchase. Paid sessions without an expandable payment_intent.latest_charge are refused, so confirm Managed Payments returns it.

Accepted tradeoffs

  • DataFast no longer attributes revenue on /buy/success.
  • The update bundle is public, and source builds use the same feed.
  • latest.json goes live before the draft GitHub release is published.

Free is the GPL source; the paid product is the signed, notarized macOS
app. No license keys and no in-app Pro entitlement remain.

- /api/download/macos (POST) verifies the Stripe Checkout Session and
  returns a 300s SigV4-presigned URL to the private R2 bucket. The
  presigner uses node:crypto only and matches the AWS documented vector.
- /buy/success downloads directly and is the bookmarkable re-download
  link; /download accepts the pasted link. No analytics on /buy/success
  because its URL carries the session id.
- Refund checks fail closed when a paid session has no charge, and
  partial refunds end the download.
- Removed /api/pro/*, the license HMAC, the app's Pro store, commands,
  activation UI and Pro-gated update checks. Updates are on for everyone
  from https://updates.dictx.splitlabs.io/latest.json.
- Release CI builds macOS only, uploads DMGs to private R2 and updater
  bundles plus latest.json to the public bucket. PR and manual test
  builds are unsigned and read-only.
- Docs describe the flow, secrets and one-time R2 setup.
@vercel

vercel Bot commented Sep 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
dictx Ready Ready Preview Sep 17, 2026 10:20am UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview — 9a8df5eb Deployed Sep 17, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant