{"_id":"@birtalanrobert/vouchers","name":"@birtalanrobert/vouchers","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@birtalanrobert/vouchers","version":"1.0.0","description":"Stored value: gift vouchers, prepaid packages and balances, as an append-only ledger","license":"AGPL-3.0-only","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./nestjs":{"types":"./dist/nestjs/index.d.ts","default":"./dist/nestjs/index.js"},"./package.json":"./package.json"},"publishConfig":{"access":"public"},"dependencies":{"@birtalanrobert/http":"^2.0.0","@birtalanrobert/tenancy":"^1.1.0","@birtalanrobert/database":"^1.0.0","@birtalanrobert/workflow":"^1.2.0"},"peerDependencies":{"@nestjs/common":"^11.0.0","typeorm":"^1.0.0"},"repository":{"type":"git","url":"git+https://github.com/birtalanrobert/mortar.git","directory":"packages/vouchers"},"author":{"name":"Robert Birtalan"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json --noEmit"},"_id":"@birtalanrobert/vouchers@1.0.0","bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"homepage":"https://github.com/birtalanrobert/mortar#readme","_integrity":"sha512-9lk4bQrkAMnUmztg0dY2ZfdadfesRMoesjNaQg+7PLrZL+MOjonZX6109I0A4+2MEJ4Zofr3gtjNaU/y7gikqw==","_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/a3e00e60cbe535f0de85bc0bf492af91/birtalanrobert-vouchers-1.0.0.tgz","_from":"file:birtalanrobert-vouchers-1.0.0.tgz","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-9lk4bQrkAMnUmztg0dY2ZfdadfesRMoesjNaQg+7PLrZL+MOjonZX6109I0A4+2MEJ4Zofr3gtjNaU/y7gikqw==","shasum":"c00535c8b5174e3e31b922ed637d2b4ea1f76fae","tarball":"https://registry.npmjs.org/@birtalanrobert/vouchers/-/vouchers-1.0.0.tgz","fileCount":41,"unpackedSize":208133,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC6jVMJ/brEiICK3rzCLvSCgsiiDypGwPtHpfAkfQ0FyAIhAKvBDnj1i296N/A7+EoOO9UIhu/Gng8VthrgzpDxuYtA"}]},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"directories":{},"maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vouchers_1.0.0_1788763212147_0.38068361214109747"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-07T06:40:11.975Z","1.0.0":"2026-09-07T06:40:12.286Z","modified":"2026-09-07T06:40:12.492Z"},"maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"description":"Stored value: gift vouchers, prepaid packages and balances, as an append-only ledger","homepage":"https://github.com/birtalanrobert/mortar#readme","repository":{"type":"git","url":"git+https://github.com/birtalanrobert/mortar.git","directory":"packages/vouchers"},"author":{"name":"Robert Birtalan"},"bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"license":"AGPL-3.0-only","readme":"# @birtalanrobert/vouchers\n\nStored value: gift vouchers, prepaid packages and balances.\n\nMoney a customer has already handed over and has not yet used. It is the\nbusiness's cash and the customer's entitlement at the same time — which is why\nthis is a **ledger and not a counter**.\n\nTwo specifications call for it in the same words: project 02 (gift vouchers and\n\"ten sessions\" packages) and project 06 (gift balances and prepaid top-ups). It\nis built for both.\n\n## Why a ledger\n\nA balance stored as a number cannot answer the question that always gets asked:\n_where did the other four go?_ And it breaks in the two ordinary cases a ledger\nsurvives — a redemption written twice, and a refund.\n\nSo every change is an append-only entry, and the balance is their sum. The\n`balance` column exists only as a cache written in the same transaction, with\n`CHECK (balance >= 0)` behind it: the constraint is the guarantee, the ledger is\nthe explanation.\n\n## Counting without a database\n\n```ts\nimport { balanceOf, canRedeem, applicable, expiryFrom } from '@birtalanrobert/vouchers';\n\nbalanceOf([\n  { kind: 'issued', amount: 20_000 },\n  { kind: 'redeemed', amount: 5_000 },\n]); // 15_000\n\n// A voucher for 200 against a bill of 350 pays 200 and the customer pays the rest.\napplicable(voucher, 35_000, Date.now()); // 20_000\n\n// Null means never, and that is the default.\nexpiryFrom(Date.now(), null); // null\n```\n\nThe root entry point is **framework-free and browser-safe**: a console counts a\nbalance while somebody types, and a booking page shows what a voucher is worth.\nEverything needing TypeORM or Nest is behind `@birtalanrobert/vouchers/nestjs`.\n\n## Issuing and redeeming\n\n```ts\nimport { VouchersService } from '@birtalanrobert/vouchers/nestjs';\n\nconst voucher = await vouchers.issue(tenantId, {\n  denomination: 'units',\n  amount: 10,\n  subject: `service:${massageId}`, // a package is for what it was sold for\n  holderId: customer.id,\n});\n\nawait vouchers.redeem(tenantId, {\n  voucherId: voucher.id,\n  amount: 1,\n  subject: `booking:${booking.id}`,\n  serviceSubject: `service:${massageId}`,\n});\n\n// An appointment cancelled gives its session back.\nawait vouchers.release(tenantId, { voucherId: voucher.id, subject: `booking:${booking.id}` });\n```\n\n## Two things it deliberately does not do\n\n- **Invent an expiry.** Rules for stored value differ by jurisdiction and\n  several treat an unused voucher as the customer's money for years. Null means\n  never; a business that knows its own rule sets one.\n- **Take a foreign key to your rows.** `subject` is a string in the consuming\n  product's own words — `booking:<id>`, `order:<id>` — because a key to one\n  product's tables is exactly what would stop this one being shared.\n\n## Codes\n\nCrockford's base32, twelve characters in four groups of three. Its excluded\nletters and its substitutions (I and L read as 1, O as 0) are the mistakes\npeople actually make reading a code aloud. `normaliseCode` applies them, so a\ncustomer reading off a photograph is not told their voucher does not exist\nbecause they typed a letter O.\n\nA voucher is a bearer instrument: whoever holds the card may spend it. What\nprotects it is the rate limit in front of the lookup, not the entropy — though\n32^12 is enough that guessing is not a strategy.\n","readmeFilename":"README.md","_rev":"1-3576005b8be3216766fc223c857089be"}