{"_id":"@daviejpg/gate-pay","name":"@daviejpg/gate-pay","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@daviejpg/gate-pay","version":"0.1.0","description":"Add pay-per-call billing to any API with 3 lines of code","type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./hono":{"types":"./dist/adapters/hono.d.ts","import":"./dist/adapters/hono.js"},"./express":{"types":"./dist/adapters/express.d.ts","import":"./dist/adapters/express.js"},"./store":{"types":"./dist/store/index.d.ts","import":"./dist/store/index.js"}},"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","demo":"GATE_MODE=test tsx examples/demo.ts"},"keywords":["api","billing","payments","stripe","middleware","402","monetize"],"license":"MIT","repository":{"type":"git","url":"git+https://github.com/adhd/gate.git"},"homepage":"https://github.com/adhd/gate#readme","bugs":{"url":"https://github.com/adhd/gate/issues"},"author":{"name":"David Wang"},"dependencies":{"stripe":"^17.0.0"},"devDependencies":{"@hono/node-server":"^1.19.11","@types/express":"^5.0.6","@types/node":"^22.0.0","@types/supertest":"^7.2.0","express":"^5.2.1","hono":"^4.12.9","supertest":"^7.2.2","tsup":"^8.0.0","tsx":"^4.21.0","typescript":"^5.7.0","vitest":"^3.0.0"},"peerDependencies":{"express":">=4.0.0","hono":">=4.0.0"},"peerDependenciesMeta":{"hono":{"optional":true},"express":{"optional":true}},"_id":"@daviejpg/gate-pay@0.1.0","gitHead":"389f75a00c93c7b47df44d13fbe99153fd486267","_nodeVersion":"23.6.1","_npmVersion":"11.1.0","dist":{"integrity":"sha512-MTGQ2MZWls6t+7j6G7T5VBw3MLz5DAZgtHeh3rMLYulUl1/UzzwwnjxX39TI9QR6Hpp8sEkjRy7M1ff1GATv/A==","shasum":"f3b4de21b799a6cb2be5e8e6f63ce9859cc3c5b9","tarball":"https://registry.npmjs.org/@daviejpg/gate-pay/-/gate-pay-0.1.0.tgz","fileCount":22,"unpackedSize":91183,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCzIjdyvqg+hFD71pQVMT6YVARyXqehYK4Q0PiJJp0enQIhAInKycB/lep5el4xxUSDaiOy6EU29SoK+yqYYApd3BnA"}]},"_npmUser":{"name":"daviejpg","email":"daviejpg@gmail.com"},"directories":{},"maintainers":[{"name":"daviejpg","email":"daviejpg@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/gate-pay_0.1.0_1774817120733_0.8387547021452191"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-29T20:45:20.640Z","0.1.0":"2026-03-29T20:45:20.892Z","modified":"2026-03-29T20:45:21.048Z"},"maintainers":[{"name":"daviejpg","email":"daviejpg@gmail.com"}],"description":"Add pay-per-call billing to any API with 3 lines of code","homepage":"https://github.com/adhd/gate#readme","keywords":["api","billing","payments","stripe","middleware","402","monetize"],"repository":{"type":"git","url":"git+https://github.com/adhd/gate.git"},"author":{"name":"David Wang"},"bugs":{"url":"https://github.com/adhd/gate/issues"},"license":"MIT","readme":"# gate\n\nAdd pay-per-call billing to any API. Middleware for Hono and Express.\n\nYour API returns a 402 when someone shows up without a key. Browsers get redirected to Stripe Checkout. API clients and agents get JSON with pricing and a checkout URL. After payment, they get a key with credits that decrement on each call.\n\nYou don't build any of this. You add middleware and set a price.\n\n```ts\nimport { Hono } from \"hono\";\nimport { mountGate } from \"@daviejpg/gate-pay/hono\";\n\nconst app = new Hono();\nconst billing = mountGate({\n  credits: { amount: 1000, price: 500 }, // 1000 calls for $5\n});\n\napp.use(\"/api/*\", billing.middleware);\napp.get(\"/api/data\", (c) => c.json({ ok: true }));\n\nconst gateRoutes = new Hono();\nbilling.routes(gateRoutes);\napp.route(\"/__gate\", gateRoutes);\n```\n\n## Install\n\n```bash\nnpm install @daviejpg/gate-pay hono\n```\n\n(Or `express` instead of `hono`. Stripe is bundled as a dependency of gate.)\n\n## How it works\n\nFor any gated route:\n\n1. Valid key with credits: request passes, credits deducted (1 by default, configurable per route via `cost`).\n2. No key, browser client: 302 redirect to `/__gate/buy`.\n3. No key, API/agent client: 402 JSON with `purchase_url` and pricing.\n4. Key with zero credits: 402 JSON with a refill `purchase_url`.\n\nIf the store is unreachable, gate fails open by default (lets requests through). Configurable to fail closed.\n\n## Test mode\n\nSet `GATE_MODE=test` to skip Stripe entirely. No credentials needed.\n\n```bash\n# start your app\nGATE_MODE=test node server.js\n\n# get a 402\ncurl -i http://localhost:3000/api/data\n\n# issue a test key\ncurl \"http://localhost:3000/__gate/success?session_id=cs_test_1\" \\\n  -H \"accept: application/json\"\n# returns: { \"api_key\": \"gate_test_...\", \"credits\": 1000 }\n\n# use it\ncurl http://localhost:3000/api/data -H \"Authorization: Bearer gate_test_...\"\n```\n\n## Express\n\n```ts\nimport express from \"express\";\nimport { mountGate } from \"@daviejpg/gate-pay/express\";\n\nconst app = express();\nconst billing = mountGate({\n  credits: { amount: 1000, price: 500 },\n});\n\n// mount gate routes BEFORE express.json() (webhook needs raw body)\napp.use(\"/__gate\", billing.routes());\n\napp.use(express.json());\napp.use(\"/api\", billing.middleware);\napp.get(\"/api/data\", (_req, res) => res.json({ ok: true }));\n\napp.listen(3000);\n```\n\n## Routes\n\n`mountGate` registers five handlers under the route prefix (default `/__gate`):\n\n| Endpoint          | Method | Auth | Description                                                                                          |\n| ----------------- | ------ | ---- | ---------------------------------------------------------------------------------------------------- |\n| `/__gate/buy`     | GET    | No   | Creates a Stripe Checkout session. Browsers get a 302 redirect; JSON clients get `{ checkout_url }`. |\n| `/__gate/success` | GET    | No   | Verifies payment and issues an API key. Requires `?session_id=...`.                                  |\n| `/__gate/status`  | GET    | Yes  | Returns `{ credits_remaining, created_at, last_used_at }` for the authenticated key.                 |\n| `/__gate/pricing` | GET    | No   | Returns `{ credits, price, currency, formatted }`. No auth needed.                                   |\n| `/__gate/webhook` | POST   | No   | Stripe webhook endpoint. Verifies signature, issues key as backup to `/success`.                     |\n\n## Variable cost\n\nThe default middleware deducts 1 credit per call. For routes that cost more, use `billing.gate()`:\n\n```ts\n// Hono\napp.use(\"/api/expensive/*\", billing.gate({ cost: 10 }));\n```\n\n## Going live\n\n1. Set `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` env vars (or pass them in config).\n2. Set `baseUrl` in config to your public URL (e.g. `https://api.example.com`). Required in live mode.\n3. In Stripe, point a webhook at your `/__gate/webhook` URL.\n4. Remove `GATE_MODE=test`.\n\n## Key formats\n\nGate checks for keys in this order:\n\n- `Authorization: Bearer gate_live_...`\n- `X-API-Key: gate_live_...`\n- `?api_key=gate_live_...`\n\n## Stores\n\nDefault is an in-memory store (fine for dev, gone on restart).\n\nFor production, use `RedisStore`:\n\n```ts\nimport { createClient } from \"redis\";\nimport { RedisStore } from \"@daviejpg/gate-pay/store\";\n\nconst redis = createClient({ url: process.env.REDIS_URL });\nawait redis.connect();\n\nmountGate({\n  credits: { amount: 1000, price: 500 },\n  store: new RedisStore({ client: redis, prefix: \"gate:\" }),\n});\n```\n\nOr implement the `CreditStore` interface with whatever you want (D1, KV, Supabase, Postgres):\n\n```ts\ntype DecrementResult =\n  | { status: \"ok\"; remaining: number }\n  | { status: \"not_found\" }\n  | { status: \"exhausted\" };\n\ninterface CreditStore {\n  get(key: string): Promise<KeyRecord | null>;\n  set(key: string, record: KeyRecord): Promise<void>;\n  decrement(key: string, amount?: number): Promise<DecrementResult>;\n  delete(key: string): Promise<void>;\n}\n```\n\n## Config reference\n\n```ts\ninterface GateConfig {\n  credits: {\n    amount: number; // calls per credit pack\n    price: number; // in cents (500 = $5.00)\n    currency?: string; // default: \"usd\"\n  };\n  stripe?: {\n    secretKey?: string; // or STRIPE_SECRET_KEY env\n    webhookSecret?: string; // or STRIPE_WEBHOOK_SECRET env\n  };\n  store?: CreditStore; // default: in-memory\n  failMode?: \"open\" | \"closed\"; // default: \"open\"\n  baseUrl?: string; // required in live mode, used for checkout callback URLs\n  routePrefix?: string; // default: \"/__gate\"\n  productName?: string; // default: \"API Access\"\n  productDescription?: string;\n}\n```\n\n## Env vars\n\n- `GATE_MODE`: `\"test\"` skips Stripe, stubs checkout URLs, issues test keys. Default: `\"live\"`.\n- `STRIPE_SECRET_KEY`: your Stripe secret key.\n- `STRIPE_WEBHOOK_SECRET`: signing secret for the webhook endpoint.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-104e551b0af5ca6e25eb5bf4999916eb"}