@absolutejs/billing 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ This file is generated by `absolute-changelog` from the entries in
6
6
  `changelog/`. Edit an entry, not this file — and add new ones under
7
7
  `changelog/unreleased/`.
8
8
 
9
+ ## 0.9.0 — 2026-09-11
10
+
11
+ ### Added
12
+
13
+ - **Add bounded customer billing reports, receipt cursors and reconciled usage projections**
14
+
9
15
  ## 0.8.0 — 2026-09-11
10
16
 
11
17
  ### Added
package/README.md CHANGED
@@ -102,3 +102,5 @@ BSL-1.1 with named carveout against hosted SaaS billing platforms
102
102
  ## Secure credit checkout
103
103
 
104
104
  [Purchase-only handoffs](docs/checkout-handoffs.md) provide expiring, single-use browser capabilities without a full login session.
105
+
106
+ `@absolutejs/billing/reports` provides customer-facing status, receipt pagination, and usage contracts. `parseUsageRange` uses a half-open UTC date interval of at most 90 days (default: the last 30 days including today). Receipt cursors are positions, never authorization: bind every query, reversal join, and cursor to the caller's account. Readers must project through the public helpers, which omit provider costs, payment tokens and vault references. Usage day and feature totals must reconcile exactly; purchased dollars and consumed service credits are different measures. Automatic refill is currently unsupported.
package/changelog.json CHANGED
@@ -2,6 +2,16 @@
2
2
  "contract": 1,
3
3
  "name": "@absolutejs/billing",
4
4
  "releases": [
5
+ {
6
+ "version": "0.9.0",
7
+ "date": "2026-09-11",
8
+ "changes": [
9
+ {
10
+ "kind": "added",
11
+ "summary": "Add bounded customer billing reports, receipt cursors and reconciled usage projections"
12
+ }
13
+ ]
14
+ },
5
15
  {
6
16
  "version": "0.8.0",
7
17
  "date": "2026-09-11",
@@ -0,0 +1,65 @@
1
+ /** Public customer reports. No payment tokens, vault references, or provider costs. */
2
+ export type BillingStatus = {
3
+ portalAccess: boolean;
4
+ subscription: {
5
+ status: string;
6
+ renewsAt: string | null;
7
+ cancelAtPeriodEnd: boolean;
8
+ } | null;
9
+ credits: {
10
+ remaining: number;
11
+ reserved: number;
12
+ purchased: number;
13
+ promotional: number;
14
+ debt: number;
15
+ };
16
+ automaticRefill: false;
17
+ };
18
+ export type BillingReceipt = {
19
+ id: string;
20
+ issuedAt: string;
21
+ currency: string;
22
+ amountCents: number;
23
+ refundedAmountCents: number;
24
+ source: "credit_purchase" | "initial" | "plan_change" | "renewal";
25
+ status: "paid" | "partially_refunded" | "refunded";
26
+ };
27
+ export type ReceiptCursor = {
28
+ at: string;
29
+ id: string;
30
+ };
31
+ export type ReceiptPageRequest = {
32
+ limit: number;
33
+ cursor: ReceiptCursor | null;
34
+ };
35
+ export type ReceiptPage = {
36
+ receipts: BillingReceipt[];
37
+ nextCursor: string | null;
38
+ };
39
+ export type UsageRange = {
40
+ from: string;
41
+ to: string;
42
+ };
43
+ export type CustomerUsageReport = {
44
+ from: string;
45
+ to: string;
46
+ creditsConsumed: number;
47
+ events: number;
48
+ byDay: {
49
+ day: string;
50
+ credits: number;
51
+ events: number;
52
+ }[];
53
+ byFeature: {
54
+ feature: string;
55
+ credits: number;
56
+ events: number;
57
+ }[];
58
+ };
59
+ export declare const parseUsageRange: (input: unknown, now?: Date) => UsageRange;
60
+ /** Cursor is a position, never authorization. Consumers must filter by account. */
61
+ export declare const encodeReceiptCursor: (cursor: ReceiptCursor) => string;
62
+ export declare const parseReceiptPage: (input: unknown) => ReceiptPageRequest;
63
+ export declare const projectBillingStatus: (value: BillingStatus) => BillingStatus;
64
+ export declare const projectReceiptPage: (value: ReceiptPage, limit: number) => ReceiptPage;
65
+ export declare const projectUsageReport: (value: CustomerUsageReport, range: UsageRange) => CustomerUsageReport;
@@ -0,0 +1,146 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+
17
+ // src/reports.ts
18
+ var DAY_MS = 86400000;
19
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20
+ var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21
+ var only = (input, keys) => {
22
+ if (!record(input) || Object.keys(input).some((key) => !keys.includes(key)))
23
+ throw new Error("Invalid report input");
24
+ return input;
25
+ };
26
+ var integer = (value) => {
27
+ if (!Number.isSafeInteger(value) || value < 0)
28
+ throw new Error("Invalid report amount");
29
+ return value;
30
+ };
31
+ var iso = (value) => {
32
+ if (!Number.isFinite(Date.parse(value)) || new Date(value).toISOString() !== value)
33
+ throw new Error("Invalid report timestamp");
34
+ return value;
35
+ };
36
+ var day = (value) => {
37
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value) || new Date(value + "T00:00:00.000Z").toISOString().slice(0, 10) !== value)
38
+ throw new Error("Expected a UTC calendar date");
39
+ return value;
40
+ };
41
+ var parseUsageRange = (input, now = new Date) => {
42
+ const args = only(input, ["from", "to"]);
43
+ const tomorrow = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) + DAY_MS);
44
+ const to = day(args.to ?? tomorrow.toISOString().slice(0, 10));
45
+ const from = day(args.from ?? new Date(Date.parse(to) - 30 * DAY_MS).toISOString().slice(0, 10));
46
+ const duration = Date.parse(to) - Date.parse(from);
47
+ if (duration <= 0 || duration > 90 * DAY_MS || Date.parse(to) > tomorrow.getTime())
48
+ throw new Error("Choose a range of 1\u201390 UTC days, ending no later than tomorrow");
49
+ return { from, to };
50
+ };
51
+ var encodeReceiptCursor = (cursor) => {
52
+ if (!UUID.test(cursor.id))
53
+ throw new Error("Invalid receipt cursor");
54
+ return Buffer.from(JSON.stringify({ at: iso(cursor.at), id: cursor.id })).toString("base64url");
55
+ };
56
+ var parseReceiptPage = (input) => {
57
+ const args = only(input, ["limit", "cursor"]);
58
+ const limit = args.limit ?? 20;
59
+ if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > 50)
60
+ throw new Error("Receipt limit must be 1\u201350");
61
+ if (args.cursor === undefined || args.cursor === null)
62
+ return { limit, cursor: null };
63
+ if (typeof args.cursor !== "string" || args.cursor.length > 256 || !/^[A-Za-z0-9_-]+$/.test(args.cursor))
64
+ throw new Error("Invalid receipt cursor");
65
+ const decoded = JSON.parse(Buffer.from(args.cursor, "base64url").toString("utf8"));
66
+ if (!record(decoded) || typeof decoded.at !== "string" || typeof decoded.id !== "string" || !UUID.test(decoded.id))
67
+ throw new Error("Invalid receipt cursor");
68
+ return { limit, cursor: { at: iso(decoded.at), id: decoded.id } };
69
+ };
70
+ var projectBillingStatus = (value) => ({
71
+ portalAccess: value.portalAccess === true,
72
+ subscription: value.subscription ? {
73
+ status: String(value.subscription.status).slice(0, 64),
74
+ renewsAt: value.subscription.renewsAt === null ? null : iso(value.subscription.renewsAt),
75
+ cancelAtPeriodEnd: value.subscription.cancelAtPeriodEnd === true
76
+ } : null,
77
+ credits: {
78
+ remaining: integer(value.credits.remaining),
79
+ reserved: integer(value.credits.reserved),
80
+ purchased: integer(value.credits.purchased),
81
+ promotional: integer(value.credits.promotional),
82
+ debt: integer(value.credits.debt)
83
+ },
84
+ automaticRefill: false
85
+ });
86
+ var projectReceiptPage = (value, limit) => {
87
+ if (value.receipts.length > limit)
88
+ throw new Error("Receipt page exceeds its limit");
89
+ if (value.nextCursor !== null)
90
+ parseReceiptPage({ cursor: value.nextCursor });
91
+ return {
92
+ receipts: value.receipts.map((row) => {
93
+ if (!UUID.test(row.id) || !/^[A-Z]{3}$/.test(row.currency) || !["credit_purchase", "initial", "plan_change", "renewal"].includes(row.source) || !["paid", "partially_refunded", "refunded"].includes(row.status) || row.refundedAmountCents > row.amountCents)
94
+ throw new Error("Invalid receipt");
95
+ return {
96
+ id: row.id,
97
+ issuedAt: iso(row.issuedAt),
98
+ currency: row.currency,
99
+ amountCents: integer(row.amountCents),
100
+ refundedAmountCents: integer(row.refundedAmountCents),
101
+ source: row.source,
102
+ status: row.status
103
+ };
104
+ }),
105
+ nextCursor: value.nextCursor
106
+ };
107
+ };
108
+ var projectUsageReport = (value, range) => {
109
+ if (value.from !== range.from || value.to !== range.to || value.byDay.length > 90 || value.byFeature.length > 21)
110
+ throw new Error("Invalid usage report bounds");
111
+ const byDay = value.byDay.map((row) => ({
112
+ day: day(row.day),
113
+ credits: integer(row.credits),
114
+ events: integer(row.events)
115
+ }));
116
+ const byFeature = value.byFeature.map((row) => ({
117
+ feature: row.feature.slice(0, 128),
118
+ credits: integer(row.credits),
119
+ events: integer(row.events)
120
+ }));
121
+ const credits = integer(value.creditsConsumed), events = integer(value.events);
122
+ for (const rows of [byDay, byFeature])
123
+ if (rows.reduce((sum, row) => sum + row.credits, 0) !== credits || rows.reduce((sum, row) => sum + row.events, 0) !== events)
124
+ throw new Error("Usage breakdown does not reconcile");
125
+ if (new Set(byDay.map((row) => row.day)).size !== byDay.length || byDay.some((row) => row.day < range.from || row.day >= range.to))
126
+ throw new Error("Invalid usage days");
127
+ return {
128
+ from: range.from,
129
+ to: range.to,
130
+ creditsConsumed: credits,
131
+ events,
132
+ byDay,
133
+ byFeature
134
+ };
135
+ };
136
+ export {
137
+ encodeReceiptCursor,
138
+ parseReceiptPage,
139
+ parseUsageRange,
140
+ projectBillingStatus,
141
+ projectReceiptPage,
142
+ projectUsageReport
143
+ };
144
+
145
+ //# debugId=1CCF8D983CF6711964756E2164756E21
146
+ //# sourceMappingURL=reports.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/reports.ts"],
4
+ "sourcesContent": [
5
+ "/** Public customer reports. No payment tokens, vault references, or provider costs. */\nexport type BillingStatus = {\n portalAccess: boolean;\n subscription: {\n status: string;\n renewsAt: string | null;\n cancelAtPeriodEnd: boolean;\n } | null;\n credits: {\n remaining: number;\n reserved: number;\n purchased: number;\n promotional: number;\n debt: number;\n };\n automaticRefill: false;\n};\nexport type BillingReceipt = {\n id: string;\n issuedAt: string;\n currency: string;\n amountCents: number;\n refundedAmountCents: number;\n source: \"credit_purchase\" | \"initial\" | \"plan_change\" | \"renewal\";\n status: \"paid\" | \"partially_refunded\" | \"refunded\";\n};\nexport type ReceiptCursor = { at: string; id: string };\nexport type ReceiptPageRequest = {\n limit: number;\n cursor: ReceiptCursor | null;\n};\nexport type ReceiptPage = {\n receipts: BillingReceipt[];\n nextCursor: string | null;\n};\nexport type UsageRange = { from: string; to: string };\nexport type CustomerUsageReport = {\n from: string;\n to: string;\n creditsConsumed: number;\n events: number;\n byDay: { day: string; credits: number; events: number }[];\n byFeature: { feature: string; credits: number; events: number }[];\n};\nconst DAY_MS = 86_400_000;\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst record = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\nconst only = (input: unknown, keys: readonly string[]) => {\n if (!record(input) || Object.keys(input).some((key) => !keys.includes(key)))\n throw new Error(\"Invalid report input\");\n return input;\n};\nconst integer = (value: number) => {\n if (!Number.isSafeInteger(value) || value < 0)\n throw new Error(\"Invalid report amount\");\n return value;\n};\nconst iso = (value: string) => {\n if (\n !Number.isFinite(Date.parse(value)) ||\n new Date(value).toISOString() !== value\n )\n throw new Error(\"Invalid report timestamp\");\n return value;\n};\nconst day = (value: unknown) => {\n if (\n typeof value !== \"string\" ||\n !/^\\d{4}-\\d{2}-\\d{2}$/.test(value) ||\n new Date(value + \"T00:00:00.000Z\").toISOString().slice(0, 10) !== value\n )\n throw new Error(\"Expected a UTC calendar date\");\n return value;\n};\nexport const parseUsageRange = (\n input: unknown,\n now = new Date(),\n): UsageRange => {\n const args = only(input, [\"from\", \"to\"]);\n const tomorrow = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) +\n DAY_MS,\n );\n const to = day(args.to ?? tomorrow.toISOString().slice(0, 10));\n const from = day(\n args.from ??\n new Date(Date.parse(to) - 30 * DAY_MS).toISOString().slice(0, 10),\n );\n const duration = Date.parse(to) - Date.parse(from);\n if (\n duration <= 0 ||\n duration > 90 * DAY_MS ||\n Date.parse(to) > tomorrow.getTime()\n )\n throw new Error(\n \"Choose a range of 1–90 UTC days, ending no later than tomorrow\",\n );\n return { from, to };\n};\n/** Cursor is a position, never authorization. Consumers must filter by account. */\nexport const encodeReceiptCursor = (cursor: ReceiptCursor) => {\n if (!UUID.test(cursor.id)) throw new Error(\"Invalid receipt cursor\");\n return Buffer.from(\n JSON.stringify({ at: iso(cursor.at), id: cursor.id }),\n ).toString(\"base64url\");\n};\nexport const parseReceiptPage = (input: unknown): ReceiptPageRequest => {\n const args = only(input, [\"limit\", \"cursor\"]);\n const limit = args.limit ?? 20;\n if (\n typeof limit !== \"number\" ||\n !Number.isInteger(limit) ||\n limit < 1 ||\n limit > 50\n )\n throw new Error(\"Receipt limit must be 1–50\");\n if (args.cursor === undefined || args.cursor === null)\n return { limit, cursor: null };\n if (\n typeof args.cursor !== \"string\" ||\n args.cursor.length > 256 ||\n !/^[A-Za-z0-9_-]+$/.test(args.cursor)\n )\n throw new Error(\"Invalid receipt cursor\");\n const decoded: unknown = JSON.parse(\n Buffer.from(args.cursor, \"base64url\").toString(\"utf8\"),\n );\n if (\n !record(decoded) ||\n typeof decoded.at !== \"string\" ||\n typeof decoded.id !== \"string\" ||\n !UUID.test(decoded.id)\n )\n throw new Error(\"Invalid receipt cursor\");\n return { limit, cursor: { at: iso(decoded.at), id: decoded.id } };\n};\nexport const projectBillingStatus = (value: BillingStatus): BillingStatus => ({\n portalAccess: value.portalAccess === true,\n subscription: value.subscription\n ? {\n status: String(value.subscription.status).slice(0, 64),\n renewsAt:\n value.subscription.renewsAt === null\n ? null\n : iso(value.subscription.renewsAt),\n cancelAtPeriodEnd: value.subscription.cancelAtPeriodEnd === true,\n }\n : null,\n credits: {\n remaining: integer(value.credits.remaining),\n reserved: integer(value.credits.reserved),\n purchased: integer(value.credits.purchased),\n promotional: integer(value.credits.promotional),\n debt: integer(value.credits.debt),\n },\n automaticRefill: false,\n});\nexport const projectReceiptPage = (\n value: ReceiptPage,\n limit: number,\n): ReceiptPage => {\n if (value.receipts.length > limit)\n throw new Error(\"Receipt page exceeds its limit\");\n if (value.nextCursor !== null) parseReceiptPage({ cursor: value.nextCursor });\n return {\n receipts: value.receipts.map((row) => {\n if (\n !UUID.test(row.id) ||\n !/^[A-Z]{3}$/.test(row.currency) ||\n ![\"credit_purchase\", \"initial\", \"plan_change\", \"renewal\"].includes(\n row.source,\n ) ||\n ![\"paid\", \"partially_refunded\", \"refunded\"].includes(row.status) ||\n row.refundedAmountCents > row.amountCents\n )\n throw new Error(\"Invalid receipt\");\n return {\n id: row.id,\n issuedAt: iso(row.issuedAt),\n currency: row.currency,\n amountCents: integer(row.amountCents),\n refundedAmountCents: integer(row.refundedAmountCents),\n source: row.source,\n status: row.status,\n };\n }),\n nextCursor: value.nextCursor,\n };\n};\nexport const projectUsageReport = (\n value: CustomerUsageReport,\n range: UsageRange,\n): CustomerUsageReport => {\n if (\n value.from !== range.from ||\n value.to !== range.to ||\n value.byDay.length > 90 ||\n value.byFeature.length > 21\n )\n throw new Error(\"Invalid usage report bounds\");\n const byDay = value.byDay.map((row) => ({\n day: day(row.day),\n credits: integer(row.credits),\n events: integer(row.events),\n }));\n const byFeature = value.byFeature.map((row) => ({\n feature: row.feature.slice(0, 128),\n credits: integer(row.credits),\n events: integer(row.events),\n }));\n const credits = integer(value.creditsConsumed),\n events = integer(value.events);\n for (const rows of [byDay, byFeature])\n if (\n rows.reduce((sum, row) => sum + row.credits, 0) !== credits ||\n rows.reduce((sum, row) => sum + row.events, 0) !== events\n )\n throw new Error(\"Usage breakdown does not reconcile\");\n if (\n new Set(byDay.map((row) => row.day)).size !== byDay.length ||\n byDay.some((row) => row.day < range.from || row.day >= range.to)\n )\n throw new Error(\"Invalid usage days\");\n return {\n from: range.from,\n to: range.to,\n creditsConsumed: credits,\n events,\n byDay,\n byFeature,\n };\n};\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "1CCF8D983CF6711964756E2164756E21",
9
+ "names": []
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/billing",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Provider-neutral pricing and invoice computation used by the hosted AbsoluteJS.ai platform. Converts metered usage into exact integer-micro line items with tiers and allowances.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,6 +52,11 @@
52
52
  "types": "./dist/checkoutHandoff.d.ts",
53
53
  "import": "./dist/checkoutHandoff.js",
54
54
  "default": "./dist/checkoutHandoff.js"
55
+ },
56
+ "./reports": {
57
+ "types": "./dist/reports.d.ts",
58
+ "import": "./dist/reports.js",
59
+ "default": "./dist/reports.js"
55
60
  }
56
61
  },
57
62
  "publishConfig": {
@@ -66,7 +71,7 @@
66
71
  "docs/checkout-handoffs.md"
67
72
  ],
68
73
  "scripts": {
69
- "build": "rm -rf dist && bun build src/index.ts src/ledger.ts src/prepaid.ts src/prepaidPostgres.ts src/creditWork.ts src/checkoutHandoff.ts src/manifest.ts --root ./src --outdir dist --sourcemap --target=bun && tsc --project tsconfig.build.json && absolute-manifest emit",
74
+ "build": "rm -rf dist && bun build src/index.ts src/ledger.ts src/prepaid.ts src/prepaidPostgres.ts src/creditWork.ts src/checkoutHandoff.ts src/reports.ts src/manifest.ts --root ./src --outdir dist --sourcemap --target=bun && tsc --project tsconfig.build.json && absolute-manifest emit",
70
75
  "test": "bun test tests/",
71
76
  "typecheck": "tsc --noEmit",
72
77
  "format": "prettier --write \"./**/*.{ts,json,md}\"",