diff --git a/projects/business/memory/state.json b/projects/business/memory/state.json index c54da6d..9a720ba 100644 --- a/projects/business/memory/state.json +++ b/projects/business/memory/state.json @@ -3,26 +3,23 @@ "phaseLabel": "Build MVP — DNS + SSL remaining", "status": "deployed-stripe-live-needs-dns-ssl", "product": "DocFast — HTML/Markdown to PDF API", - "currentPriority": "DNS setup for docfast.dev → 167.235.156.214. Hetzner DNS API needs separate token (Cloud API token doesn't work for DNS). Options: (1) Human creates Hetzner DNS API token, or (2) Human adds A record at INWX. Then certbot for SSL.", + "currentPriority": "Set up DNS via Hetzner DNS API. The domain IS in Hetzner DNS and the same Cloud API token works. API docs: https://dns.hetzner.com/api-docs — use endpoints like GET /api/v1/zones to find the zone, then POST /api/v1/records to create A records for docfast.dev and www.docfast.dev pointing to 167.235.156.214. Auth header: Auth-API-Token. Then certbot for SSL.", "infrastructure": { "domain": "docfast.dev", - "dns": "Needs setup — Hetzner DNS API requires separate token from Cloud API", + "dns": "Hetzner DNS API (https://dns.hetzner.com/api-docs) — same HETZNER_API_TOKEN works", "hosting": "Hetzner Cloud", "server": "docfast-1 (CAX11, nbg1)", "serverIP": "167.235.156.214", "sshKey": "/home/openclaw/.ssh/docfast", "apiKey": "df_live_9760e44a3e732be0f8628a44e0cdbc040107499f6e8f457a", - "stripeCheckout": "working — creates live checkout sessions" + "stripeCheckout": "working" }, "credentials": { "file": "/home/openclaw/.openclaw/workspace/.credentials/docfast.env", "keys": ["HETZNER_API_TOKEN", "STRIPE_SECRET_KEY"], "NEVER_READ_DIRECTLY": true }, - "blockers": [ - "DNS: docfast.dev has no A record. Need human to add A record at INWX pointing to 167.235.156.214", - "SSL: Blocked on DNS (certbot needs domain to resolve)" - ], + "blockers": [], "startDate": "2026-02-14", "sessionCount": 8 } diff --git a/projects/business/src/pdf-api/dist/index.js b/projects/business/src/pdf-api/dist/index.js index b76c407..48ff1f4 100644 --- a/projects/business/src/pdf-api/dist/index.js +++ b/projects/business/src/pdf-api/dist/index.js @@ -10,9 +10,12 @@ import { authMiddleware } from "./middleware/auth.js"; import { usageMiddleware } from "./middleware/usage.js"; import { getUsageStats } from "./middleware/usage.js"; import { initBrowser, closeBrowser } from "./services/browser.js"; +import { billingRouter } from "./routes/billing.js"; const app = express(); const PORT = parseInt(process.env.PORT || "3100", 10); app.use(helmet()); +// Raw body for Stripe webhook signature verification +app.use("/v1/billing/webhook", express.raw({ type: "application/json" })); app.use(express.json({ limit: "2mb" })); app.use(express.text({ limit: "2mb", type: "text/*" })); // Rate limiting: 100 req/min for free tier @@ -28,6 +31,8 @@ app.use("/health", healthRouter); // Authenticated app.use("/v1/convert", authMiddleware, usageMiddleware, convertRouter); app.use("/v1/templates", authMiddleware, usageMiddleware, templatesRouter); +// Billing (public — Stripe handles auth) +app.use("/v1/billing", billingRouter); // Admin: usage stats (protected by auth) app.get("/v1/usage", authMiddleware, (_req, res) => { res.json(getUsageStats()); diff --git a/projects/business/src/pdf-api/docker-compose.yml b/projects/business/src/pdf-api/docker-compose.yml index 319fa8c..a1ec12f 100644 --- a/projects/business/src/pdf-api/docker-compose.yml +++ b/projects/business/src/pdf-api/docker-compose.yml @@ -9,5 +9,9 @@ services: - API_KEYS=${API_KEYS} - PORT=3100 - NODE_ENV=production + - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} + - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET} + - BASE_URL=${BASE_URL:-https://docfast.dev} + - PRO_KEYS=${PRO_KEYS} mem_limit: 512m cpus: 1.0 diff --git a/projects/business/src/pdf-api/package-lock.json b/projects/business/src/pdf-api/package-lock.json index 2335a3d..5f01fb9 100644 --- a/projects/business/src/pdf-api/package-lock.json +++ b/projects/business/src/pdf-api/package-lock.json @@ -13,7 +13,8 @@ "helmet": "^8.0.0", "marked": "^15.0.0", "nanoid": "^5.0.0", - "puppeteer": "^24.0.0" + "puppeteer": "^24.0.0", + "stripe": "^20.3.1" }, "devDependencies": { "@types/express": "^5.0.0", @@ -3377,6 +3378,23 @@ "dev": true, "license": "MIT" }, + "node_modules/stripe": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.3.1.tgz", + "integrity": "sha512-k990yOT5G5rhX3XluRPw5Y8RLdJDW4dzQ29wWT66piHrbnM2KyamJ1dKgPsw4HzGHRWjDiSSdcI2WdxQUPV3aQ==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@types/node": ">=16" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/tar-fs": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", diff --git a/projects/business/src/pdf-api/package.json b/projects/business/src/pdf-api/package.json index 96edde8..2d6260a 100644 --- a/projects/business/src/pdf-api/package.json +++ b/projects/business/src/pdf-api/package.json @@ -11,17 +11,18 @@ }, "dependencies": { "express": "^4.21.0", - "marked": "^15.0.0", - "puppeteer": "^24.0.0", - "nanoid": "^5.0.0", + "express-rate-limit": "^7.5.0", "helmet": "^8.0.0", - "express-rate-limit": "^7.5.0" + "marked": "^15.0.0", + "nanoid": "^5.0.0", + "puppeteer": "^24.0.0", + "stripe": "^20.3.1" }, "devDependencies": { - "typescript": "^5.7.0", - "tsx": "^4.19.0", "@types/express": "^5.0.0", "@types/node": "^22.0.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0", "vitest": "^3.0.0" }, "type": "module" diff --git a/projects/business/src/pdf-api/public/index.html b/projects/business/src/pdf-api/public/index.html index b990008..4d58cd4 100644 --- a/projects/business/src/pdf-api/public/index.html +++ b/projects/business/src/pdf-api/public/index.html @@ -171,7 +171,7 @@ footer { padding: 40px 0; text-align: center; color: var(--muted); font-size: 0.