Single-product ebook storefront for Indonesian communities: QRIS payment via Midtrans, per-buyer PDF watermarking (social DRM), and expiring download links. One master PDF in, personalized stamped copies out.
Nuskha — Arabic for "a copy / manuscript". Every buyer gets their own. Rename freely; the code doesn't care.
Marketplace platforms (KDP, Google Play Books) can't do members-only sales. International direct-sales platforms (Payhip, Gumroad) do automatic PDF stamping but only take card/PayPal. Indonesian platforms (Lynk.id, Mayar) take QRIS but don't stamp. This app does both, self-hosted.
- Buyer opens the buy page, enters name + email, clicks pay.
- App creates a Midtrans QRIS transaction (Snap), buyer scans and pays.
- Midtrans fires a payment notification webhook. App verifies the SHA-512
signature, checks
settlementstatus, records the order (idempotent — Midtrans retries notifications). - App emails the buyer a signed download link (expires in 72h, limited download count).
- On download, the master PDF is stamped on the fly with the buyer's email and order ID (small gray footer text, every page) plus restricted PDF permissions, then streamed. No per-buyer copies stored.
- Buyers can re-request their link via a resend page (enter order email).
- Java 25, Spring Boot 4, Maven
- Thymeleaf + Alpine-CSP (fetch() for partial updates) — no SPA
- PostgreSQL + Flyway
- PDFBox 3.x for stamping and permissions
- Midtrans Snap API (QRIS) — sandbox for dev
- SMTP for transactional mail (authenticated domain: SPF/DKIM/DMARC required)
src/main/java/.../nuskha/
order/ # Order entity, repository, service, buy page controller
payment/ # Midtrans client, webhook controller, signature verification
delivery/ # Download link signing, stamping service, download controller
mail/ # Transactional email
config/ # Product config (title, price, master PDF path), app config
src/main/resources/
templates/ # buy.html, thanks.html, resend.html
db/migration/ # Flyway
All product-specific values are configuration, not code — the app is a reusable single-product storefront:
| Property | Description |
|---|---|
nuskha.product.title |
Book title shown on buy page |
nuskha.product.price |
Price in IDR |
nuskha.product.master |
Path to master PDF |
nuskha.download.ttl |
Link validity (default 72h) |
nuskha.download.max |
Max downloads per order (default 5) |
midtrans.server-key |
Midtrans server key |
midtrans.client-key |
Midtrans client key |
midtrans.production |
false = sandbox |
spring.mail.* |
SMTP settings |
Secrets via environment variables. No secrets in the repo, ever.
# Postgres via Docker/OrbStack, or rely on Testcontainers for tests
docker run -d --name nuskha-pg -e POSTGRES_PASSWORD=nuskha -p 5432:5432 postgres:17
# Midtrans sandbox keys from https://dashboard.sandbox.midtrans.com
export MIDTRANS_SERVER_KEY=SB-Mid-server-...
export MIDTRANS_CLIENT_KEY=SB-Mid-client-...
./mvnw spring-boot:runWebhook testing locally: use the Midtrans dashboard notification tester, or
tunnel (ssh -R / cloudflared) to expose the webhook endpoint.
@SpringBootTest with Testcontainers (real PostgreSQL). No mocks. Tests are
functional-workflow-oriented: create order → simulate signed Midtrans
notification → assert mail queued and link works → download → assert stamp
present in PDF (extract text with PDFBox, check footer content) → assert link
expiry and max-download enforcement.
Single JAR on any small VPS behind a reverse proxy (Caddy/nginx, TLS required — Midtrans webhooks are HTTPS-only). Systemd unit or container, your choice.
Nuskha deliberately uses social DRM (per-buyer watermarking) instead of encryption-based DRM. This is a considered position, not a shortcut.
Hard DRM (Kindle KFX, Apple FairPlay, Adobe ACS) requires the reader's device to decrypt the book in order to display it — the keys are always on hostile territory, which is why every major scheme is routinely stripped by off-the-shelf tools. The platforms keep DRM anyway because its real function is library lock-in: a customer with hundreds of DRM'd purchases can't switch ecosystems, so every next purchase defaults to the same store. The anti-piracy effect is a leaky side effect. Meanwhile DRM punishes exactly the people who paid: proprietary readers, no lending, books that die with the account.
Publishers who control their own distribution keep converging on watermarked, DRM-free files:
- Pottermore sold the Harry Potter EPUBs DRM-free with Booxtream watermarking — multiple redundant marks per copy (visible obfuscated transaction ID on the copyright page, hidden serials in image attributes, CSS markers, randomized internal filenames) — on the most piracy-attractive catalog imaginable.
- Tor Books dropped DRM entirely in 2012 and reported no measurable increase in piracy. Baen has been DRM-free since the early 2000s.
- O'Reilly, Springer, Pragmatic Bookshelf, No Starch ship PDF/EPUB stamped with the buyer's email. In several European markets (Netherlands, Germany, Italy, Sweden), watermarking has displaced DRM as the norm.
A watermark is a sensor plus a credible threat, not a lock:
- The visible footer stops the casual leak. Forwarding a file with your own email and order ID on every page feels like leaving your name at the scene. That covers the realistic threat: a member sharing the PDF into a chat group.
- The hidden mark (order ID in XMP metadata) punishes half-measures. Stripping the visible footer isn't enough, and the leaker can't easily be sure they found everything. The deterrent is the doubt, not the robustness.
- Enforcement is an option you buy, not a cost you pay. Big publishers pair watermarks with crawling services (Digimarc Guardian, Link-Busters) and takedowns; at community scale, "monitoring" is noticing a file circulating, and "enforcement" is a private message to the person whose email is on page one. The watermark works even if that message is never sent, because the buyer can't know whether anyone is watching.
- A technical attacker can strip both marks: parse the content streams, clean the metadata, or simply buy two copies and diff them (every byte that differs is the watermark). No watermarking scheme survives collusion; the academic fixes (Tardos codes) aren't worth deploying here.
- The PDF permission flags (
StandardProtectionPolicy) are a speed bump, trivially removed. UI copy must never call any of this "copy protection". - The defense is proportionate to the threat: this is a members-only community product. Anyone diffing two purchases to launder a clean copy has spent more effort than the book costs — and in a community of known members, taken a social risk far larger than the technical one. The threat model is gossip-scale; so is the defense.
Design consequence: disclose the stamping on the buy page. The deterrent works best when the buyer reads the warning before purchase, and buyers who know upfront don't feel ambushed.
- EPUB watermarking (later: EPUB is a ZIP of XHTML, inject colophon + footer)
- Multi-product catalog
- User accounts / login
- "Real" DRM — stamping is social DRM; a screenshot defeats anything stronger anyway
Apache-2.0