@usebillow/sdk 0.5.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +274 -0
  4. package/dist/billing-C4RIMgH_.d.ts +1053 -0
  5. package/dist/billing-DZ4rIyg7.d.cts +1053 -0
  6. package/dist/billing-status-BZQN_gm7.d.cts +29 -0
  7. package/dist/billing-status-BZQN_gm7.d.ts +29 -0
  8. package/dist/chunk-CCG4F5FK.js +48 -0
  9. package/dist/chunk-CCG4F5FK.js.map +1 -0
  10. package/dist/chunk-Z6VXPONT.js +1493 -0
  11. package/dist/chunk-Z6VXPONT.js.map +1 -0
  12. package/dist/config.cjs +233 -0
  13. package/dist/config.cjs.map +1 -0
  14. package/dist/config.d.cts +104 -0
  15. package/dist/config.d.ts +104 -0
  16. package/dist/config.js +228 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/credits-C3Fe3TO0.d.cts +315 -0
  19. package/dist/credits-C3Fe3TO0.d.ts +315 -0
  20. package/dist/index.cjs +1560 -0
  21. package/dist/index.cjs.map +1 -0
  22. package/dist/index.d.cts +2379 -0
  23. package/dist/index.d.ts +2379 -0
  24. package/dist/index.js +4 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/ingestion.cjs +259 -0
  27. package/dist/ingestion.cjs.map +1 -0
  28. package/dist/ingestion.d.cts +182 -0
  29. package/dist/ingestion.d.ts +182 -0
  30. package/dist/ingestion.js +252 -0
  31. package/dist/ingestion.js.map +1 -0
  32. package/dist/react.cjs +360 -0
  33. package/dist/react.cjs.map +1 -0
  34. package/dist/react.d.cts +71 -0
  35. package/dist/react.d.ts +71 -0
  36. package/dist/react.js +153 -0
  37. package/dist/react.js.map +1 -0
  38. package/dist/server.cjs +98 -0
  39. package/dist/server.cjs.map +1 -0
  40. package/dist/server.d.cts +54 -0
  41. package/dist/server.d.ts +54 -0
  42. package/dist/server.js +96 -0
  43. package/dist/server.js.map +1 -0
  44. package/dist/status.cjs +60 -0
  45. package/dist/status.cjs.map +1 -0
  46. package/dist/status.d.cts +31 -0
  47. package/dist/status.d.ts +31 -0
  48. package/dist/status.js +3 -0
  49. package/dist/status.js.map +1 -0
  50. package/dist/webhooks.cjs +157 -0
  51. package/dist/webhooks.cjs.map +1 -0
  52. package/dist/webhooks.d.cts +391 -0
  53. package/dist/webhooks.d.ts +391 -0
  54. package/dist/webhooks.js +143 -0
  55. package/dist/webhooks.js.map +1 -0
  56. package/package.json +169 -0
package/dist/react.js ADDED
@@ -0,0 +1,153 @@
1
+ "use client";
2
+ import { BillowPublishable } from './chunk-Z6VXPONT.js';
3
+ import './chunk-CCG4F5FK.js';
4
+ import { createContext, useCallback, useState, useEffect, useMemo, useContext } from 'react';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ var BillowContext = createContext(null);
8
+ function BillowProvider({
9
+ children,
10
+ basePath = "/api/billow",
11
+ fetcher
12
+ }) {
13
+ const base = basePath.replace(/\/$/, "");
14
+ const doFetch = fetcher ?? globalThis.fetch;
15
+ const request = useCallback(
16
+ async (method, path, body) => {
17
+ const init = { method };
18
+ if (body) {
19
+ init.headers = { "content-type": "application/json" };
20
+ init.body = JSON.stringify(body);
21
+ }
22
+ const res = await doFetch(`${base}${path}`, init);
23
+ const data = await res.json().catch(() => ({}));
24
+ if (!res.ok) {
25
+ throw new Error(data.error?.message ?? `billow ${method} ${path} failed (${res.status})`);
26
+ }
27
+ return data;
28
+ },
29
+ [base, doFetch]
30
+ );
31
+ const [entitlements, setEntitlements] = useState([]);
32
+ const [isLoading, setIsLoading] = useState(true);
33
+ const [error, setError] = useState(null);
34
+ const refresh = useCallback(async () => {
35
+ setIsLoading(true);
36
+ setError(null);
37
+ try {
38
+ const data = await request("GET", "/entitlements");
39
+ setEntitlements(data.entitlements);
40
+ } catch (err) {
41
+ setError(err instanceof Error ? err : new Error(String(err)));
42
+ } finally {
43
+ setIsLoading(false);
44
+ }
45
+ }, [request]);
46
+ useEffect(() => {
47
+ void refresh();
48
+ }, [refresh]);
49
+ const value = useMemo(
50
+ () => ({ request, entitlements, isLoading, error, refresh }),
51
+ [request, entitlements, isLoading, error, refresh]
52
+ );
53
+ return /* @__PURE__ */ jsx(BillowContext.Provider, { value, children });
54
+ }
55
+ function useBillowContext() {
56
+ const ctx = useContext(BillowContext);
57
+ if (!ctx) throw new Error("billow: wrap your app in <BillowProvider>");
58
+ return ctx;
59
+ }
60
+ function useEntitlements() {
61
+ const { entitlements, isLoading, error, refresh } = useBillowContext();
62
+ return { entitlements, isLoading, error, refresh };
63
+ }
64
+ function useCheck(featureId) {
65
+ const { entitlements, isLoading, error } = useBillowContext();
66
+ const match = entitlements.find((e) => e.feature === featureId);
67
+ return {
68
+ feature: featureId,
69
+ allowed: match?.allowed ?? false,
70
+ balance: match?.balance ?? null,
71
+ isLoading,
72
+ error
73
+ };
74
+ }
75
+ function useAttach() {
76
+ const { request, refresh } = useBillowContext();
77
+ return useCallback(
78
+ async (input, opts = {}) => {
79
+ const result = await request("POST", "/attach", input);
80
+ await refresh();
81
+ const loc = globalThis.location;
82
+ if (opts.redirect && result.url && loc) loc.href = result.url;
83
+ return result;
84
+ },
85
+ [request, refresh]
86
+ );
87
+ }
88
+ function useTrack() {
89
+ const { request, refresh } = useBillowContext();
90
+ return useCallback(
91
+ async (input) => {
92
+ const result = await request("POST", "/track", input);
93
+ await refresh();
94
+ return result;
95
+ },
96
+ [request, refresh]
97
+ );
98
+ }
99
+ function useProducts() {
100
+ const { request } = useBillowContext();
101
+ const [products, setProducts] = useState([]);
102
+ const [isLoading, setIsLoading] = useState(true);
103
+ const [error, setError] = useState(null);
104
+ useEffect(() => {
105
+ let active = true;
106
+ setIsLoading(true);
107
+ request("GET", "/products").then((r) => {
108
+ if (!active) return;
109
+ setProducts(r.products);
110
+ setError(null);
111
+ }).catch((err) => {
112
+ if (active) setError(err instanceof Error ? err : new Error(String(err)));
113
+ }).finally(() => {
114
+ if (active) setIsLoading(false);
115
+ });
116
+ return () => {
117
+ active = false;
118
+ };
119
+ }, [request]);
120
+ return { products, isLoading, error };
121
+ }
122
+ function usePublishableProducts(config) {
123
+ const { publishableKey, baseUrl } = config;
124
+ const [products, setProducts] = useState([]);
125
+ const [isLoading, setIsLoading] = useState(true);
126
+ const [error, setError] = useState(null);
127
+ useEffect(() => {
128
+ let active = true;
129
+ setIsLoading(true);
130
+ const client = new BillowPublishable(publishableKey, baseUrl ? { baseUrl } : {});
131
+ client.products.list().then(
132
+ (list) => {
133
+ if (!active) return;
134
+ setProducts(list);
135
+ setError(null);
136
+ setIsLoading(false);
137
+ },
138
+ (e) => {
139
+ if (!active) return;
140
+ setError(e instanceof Error ? e.message : String(e));
141
+ setIsLoading(false);
142
+ }
143
+ );
144
+ return () => {
145
+ active = false;
146
+ };
147
+ }, [publishableKey, baseUrl]);
148
+ return { products, isLoading, error };
149
+ }
150
+
151
+ export { BillowProvider, useAttach, useCheck, useEntitlements, useProducts, usePublishableProducts, useTrack };
152
+ //# sourceMappingURL=react.js.map
153
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react.tsx"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["\"use client\";\n\n/**\n * React adapter (ARCHITECTURE §6: \"framework adapters — React hooks first\"). These\n * hooks talk to the same-origin billow handler (`@usebillow/sdk/server`), never to billow\n * directly — the secret key stays on your server and the customer is resolved from\n * your session. Wrap your app in `<BillowProvider>` and use the hooks below.\n *\n * <BillowProvider>\n * <App />\n * </BillowProvider>\n *\n * const { allowed } = useCheck(\"projects\");\n * const attach = useAttach();\n * <button onClick={() => attach({ productId: \"pro\" }, { redirect: true })}>Upgrade</button>\n */\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\nimport type { AttachResult, CheckResult, EntitlementView, Product, TrackResult } from \"./index\";\nimport { BillowPublishable } from \"./index\";\n\ninterface BillowContextValue {\n request: <T>(method: string, path: string, body?: unknown) => Promise<T>;\n entitlements: EntitlementView[];\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n}\n\nconst BillowContext = createContext<BillowContextValue | null>(null);\n\nexport interface BillowProviderProps {\n children: ReactNode;\n /** Where the billow handler is mounted. Default `/api/billow`. */\n basePath?: string;\n /** Custom fetch (SSR/testing). Defaults to the global `fetch`. */\n fetcher?: typeof fetch;\n}\n\nexport function BillowProvider({\n children,\n basePath = \"/api/billow\",\n fetcher,\n}: BillowProviderProps) {\n const base = basePath.replace(/\\/$/, \"\");\n const doFetch = fetcher ?? (globalThis.fetch as typeof fetch);\n\n const request = useCallback(\n async <T,>(method: string, path: string, body?: unknown): Promise<T> => {\n const init: RequestInit = { method };\n if (body) {\n init.headers = { \"content-type\": \"application/json\" };\n init.body = JSON.stringify(body);\n }\n const res = await doFetch(`${base}${path}`, init);\n const data = (await res.json().catch(() => ({}))) as { error?: { message?: string } };\n if (!res.ok) {\n throw new Error(data.error?.message ?? `billow ${method} ${path} failed (${res.status})`);\n }\n return data as T;\n },\n [base, doFetch],\n );\n\n const [entitlements, setEntitlements] = useState<EntitlementView[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const refresh = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const data = await request<{ entitlements: EntitlementView[] }>(\"GET\", \"/entitlements\");\n setEntitlements(data.entitlements);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [request]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const value = useMemo<BillowContextValue>(\n () => ({ request, entitlements, isLoading, error, refresh }),\n [request, entitlements, isLoading, error, refresh],\n );\n\n return <BillowContext.Provider value={value}>{children}</BillowContext.Provider>;\n}\n\nfunction useBillowContext(): BillowContextValue {\n const ctx = useContext(BillowContext);\n if (!ctx) throw new Error(\"billow: wrap your app in <BillowProvider>\");\n return ctx;\n}\n\n/** The identified customer's live entitlements (one per granted feature), with a `refresh`. */\nexport function useEntitlements(): {\n entitlements: EntitlementView[];\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n} {\n const { entitlements, isLoading, error, refresh } = useBillowContext();\n return { entitlements, isLoading, error, refresh };\n}\n\n/**\n * Gate a feature, derived client-side from the cached entitlements (no round-trip).\n * `error` is set when the entitlements fetch failed — distinguish that from a real\n * `allowed: false` (otherwise an auth/network failure looks like a denied feature).\n */\nexport function useCheck(\n featureId: string,\n): CheckResult & { isLoading: boolean; error: Error | null } {\n const { entitlements, isLoading, error } = useBillowContext();\n const match = entitlements.find((e) => e.feature === featureId);\n return {\n feature: featureId,\n allowed: match?.allowed ?? false,\n balance: match?.balance ?? null,\n isLoading,\n error,\n };\n}\n\n/** Start a subscription → checkout URL. Pass `{ redirect: true }` to navigate to it. */\nexport function useAttach(): (\n input: { productId: string; couponCode?: string },\n opts?: { redirect?: boolean },\n) => Promise<AttachResult> {\n const { request, refresh } = useBillowContext();\n return useCallback(\n async (input, opts = {}) => {\n const result = await request<AttachResult>(\"POST\", \"/attach\", input);\n await refresh();\n const loc = (globalThis as { location?: { href: string } }).location;\n if (opts.redirect && result.url && loc) loc.href = result.url;\n return result;\n },\n [request, refresh],\n );\n}\n\n/**\n * Record usage from the browser. Requires the handler's `allowClientTrack` — most\n * apps should `track` server-side instead (a browser caller can forge usage).\n */\nexport function useTrack(): (input: {\n featureId: string;\n value?: number;\n idempotencyKey?: string;\n}) => Promise<TrackResult> {\n const { request, refresh } = useBillowContext();\n return useCallback(\n async (input) => {\n const result = await request<TrackResult>(\"POST\", \"/track\", input);\n await refresh();\n return result;\n },\n [request, refresh],\n );\n}\n\n/** Fetch the public product catalog (for a pricing table). */\nexport function useProducts(): { products: Product[]; isLoading: boolean; error: Error | null } {\n const { request } = useBillowContext();\n const [products, setProducts] = useState<Product[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n let active = true;\n setIsLoading(true);\n request<{ products: Product[] }>(\"GET\", \"/products\")\n .then((r) => {\n if (!active) return;\n setProducts(r.products);\n setError(null);\n })\n .catch((err) => {\n if (active) setError(err instanceof Error ? err : new Error(String(err)));\n })\n .finally(() => {\n if (active) setIsLoading(false);\n });\n return () => {\n active = false;\n };\n }, [request]);\n\n return { products, isLoading, error };\n}\n\nexport interface PublishableConfig {\n /** A publishable key (`bl_<env>_pk_…`) — safe to ship in a frontend bundle. */\n publishableKey: string;\n /** The Billow API base URL. Defaults to the hosted API, `https://api.usebillow.com`. */\n baseUrl?: string;\n}\n\n/**\n * Drop-in catalog reader for a pure-SPA pricing table: fetches the public products +\n * prices directly from the browser with a publishable key (CORS-enabled), no backend\n * handler required. Reads only public data — customer entitlements/checkout still need\n * the server handler or a portal session.\n */\nexport function usePublishableProducts(config: PublishableConfig): {\n products: Product[];\n isLoading: boolean;\n error: string | null;\n} {\n const { publishableKey, baseUrl } = config;\n const [products, setProducts] = useState<Product[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let active = true;\n setIsLoading(true);\n const client = new BillowPublishable(publishableKey, baseUrl ? { baseUrl } : {});\n client.products.list().then(\n (list) => {\n if (!active) return;\n setProducts(list);\n setError(null);\n setIsLoading(false);\n },\n (e: unknown) => {\n if (!active) return;\n setError(e instanceof Error ? e.message : String(e));\n setIsLoading(false);\n },\n );\n return () => {\n active = false;\n };\n }, [publishableKey, baseUrl]);\n\n return { products, isLoading, error };\n}\n"]}
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ // src/server.ts
4
+ function createBillowHandler(billow, opts) {
5
+ const basePath = (opts.basePath ?? "/api/billow").replace(/\/$/, "");
6
+ return async function handler(req) {
7
+ const route = subRoute(new URL(req.url).pathname, basePath);
8
+ const method = req.method.toUpperCase();
9
+ if (route === null) {
10
+ return json({ error: { code: "not_found", message: "Not a billow route" } }, 404);
11
+ }
12
+ try {
13
+ if (method === "GET" && route === "/products") {
14
+ return json({ products: await billow.products.list() });
15
+ }
16
+ const customerId = await opts.identify(req);
17
+ if (!customerId) {
18
+ return json({ error: { code: "unauthenticated", message: "No customer" } }, 401);
19
+ }
20
+ if (method === "GET" && route === "/entitlements") {
21
+ return json(await billow.entitlements(customerId));
22
+ }
23
+ if (method === "POST" && route === "/check") {
24
+ const body = await readJson(req);
25
+ return json(await billow.check({ customerId, featureId: String(body.featureId) }));
26
+ }
27
+ if (method === "POST" && route === "/attach") {
28
+ const body = await readJson(req);
29
+ const input = {
30
+ customerId,
31
+ productId: String(body.productId)
32
+ };
33
+ if (body.couponCode) input.couponCode = String(body.couponCode);
34
+ if (opts.checkoutReturnUrl) input.redirectionUrl = opts.checkoutReturnUrl;
35
+ return json(await billow.attach(input));
36
+ }
37
+ if (method === "POST" && route === "/track") {
38
+ if (!opts.allowClientTrack) {
39
+ return json({ error: { code: "not_found", message: "track is server-only" } }, 404);
40
+ }
41
+ const body = await readJson(req);
42
+ const input = {
43
+ customerId,
44
+ featureId: String(body.featureId)
45
+ };
46
+ if (typeof body.value === "number") input.value = body.value;
47
+ if (body.idempotencyKey) input.idempotencyKey = String(body.idempotencyKey);
48
+ return json(await billow.track(input));
49
+ }
50
+ return json(
51
+ { error: { code: "not_found", message: `No billow route ${method} ${route}` } },
52
+ 404
53
+ );
54
+ } catch (err) {
55
+ return json(
56
+ { error: { code: errorCode(err), message: errorMessage(err) } },
57
+ errorStatus(err)
58
+ );
59
+ }
60
+ };
61
+ }
62
+ function subRoute(pathname, basePath) {
63
+ if (pathname === basePath) return "/";
64
+ if (!pathname.startsWith(`${basePath}/`)) return null;
65
+ return pathname.slice(basePath.length).replace(/\/+$/, "") || "/";
66
+ }
67
+ function json(data, status = 200) {
68
+ return new Response(JSON.stringify(data), {
69
+ status,
70
+ headers: { "content-type": "application/json" }
71
+ });
72
+ }
73
+ async function readJson(req) {
74
+ try {
75
+ return await req.json();
76
+ } catch {
77
+ return {};
78
+ }
79
+ }
80
+ function errorStatus(err) {
81
+ if (err && typeof err === "object" && "status" in err) {
82
+ const status = Number(err.status);
83
+ if (Number.isInteger(status) && status >= 400 && status <= 599) return status;
84
+ }
85
+ return 500;
86
+ }
87
+ function errorCode(err) {
88
+ if (err && typeof err === "object" && "code" in err)
89
+ return String(err.code);
90
+ return "error";
91
+ }
92
+ function errorMessage(err) {
93
+ return err instanceof Error ? err.message : "billow handler error";
94
+ }
95
+
96
+ exports.createBillowHandler = createBillowHandler;
97
+ //# sourceMappingURL=server.cjs.map
98
+ //# sourceMappingURL=server.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts"],"names":[],"mappings":";;;AA+CO,SAAS,mBAAA,CAAoB,QAAgB,IAAA,EAAqC;AACvF,EAAA,MAAM,YAAY,IAAA,CAAK,QAAA,IAAY,aAAA,EAAe,OAAA,CAAQ,OAAO,EAAE,CAAA;AAEnE,EAAA,OAAO,eAAe,QAAQ,GAAA,EAAiC;AAC7D,IAAA,MAAM,KAAA,GAAQ,SAAS,IAAI,GAAA,CAAI,IAAI,GAAG,CAAA,CAAE,UAAU,QAAQ,CAAA;AAC1D,IAAA,MAAM,MAAA,GAAS,GAAA,CAAI,MAAA,CAAO,WAAA,EAAY;AACtC,IAAA,IAAI,UAAU,IAAA,EAAM;AAClB,MAAA,OAAO,IAAA,CAAK,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,aAAa,OAAA,EAAS,oBAAA,EAAqB,EAAE,EAAG,GAAG,CAAA;AAAA,IAClF;AAEA,IAAA,IAAI;AAEF,MAAA,IAAI,MAAA,KAAW,KAAA,IAAS,KAAA,KAAU,WAAA,EAAa;AAC7C,QAAA,OAAO,IAAA,CAAK,EAAE,QAAA,EAAU,MAAM,OAAO,QAAA,CAAS,IAAA,IAAQ,CAAA;AAAA,MACxD;AAGA,MAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA;AAC1C,MAAA,IAAI,CAAC,UAAA,EAAY;AACf,QAAA,OAAO,IAAA,CAAK,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,mBAAmB,OAAA,EAAS,aAAA,EAAc,EAAE,EAAG,GAAG,CAAA;AAAA,MACjF;AAEA,MAAA,IAAI,MAAA,KAAW,KAAA,IAAS,KAAA,KAAU,eAAA,EAAiB;AACjD,QAAA,OAAO,IAAA,CAAK,MAAM,MAAA,CAAO,YAAA,CAAa,UAAU,CAAC,CAAA;AAAA,MACnD;AACA,MAAA,IAAI,MAAA,KAAW,MAAA,IAAU,KAAA,KAAU,QAAA,EAAU;AAC3C,QAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAkC,GAAG,CAAA;AACxD,QAAA,OAAO,IAAA,CAAK,MAAM,MAAA,CAAO,KAAA,CAAM,EAAE,UAAA,EAAY,SAAA,EAAW,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA,EAAG,CAAC,CAAA;AAAA,MACnF;AACA,MAAA,IAAI,MAAA,KAAW,MAAA,IAAU,KAAA,KAAU,SAAA,EAAW;AAC5C,QAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAwD,GAAG,CAAA;AAC9E,QAAA,MAAM,KAAA,GAA6C;AAAA,UACjD,UAAA;AAAA,UACA,SAAA,EAAW,MAAA,CAAO,IAAA,CAAK,SAAS;AAAA,SAClC;AAGA,QAAA,IAAI,KAAK,UAAA,EAAY,KAAA,CAAM,UAAA,GAAa,MAAA,CAAO,KAAK,UAAU,CAAA;AAC9D,QAAA,IAAI,IAAA,CAAK,iBAAA,EAAmB,KAAA,CAAM,cAAA,GAAiB,IAAA,CAAK,iBAAA;AACxD,QAAA,OAAO,IAAA,CAAK,MAAM,MAAA,CAAO,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,MACxC;AACA,MAAA,IAAI,MAAA,KAAW,MAAA,IAAU,KAAA,KAAU,QAAA,EAAU;AAC3C,QAAA,IAAI,CAAC,KAAK,gBAAA,EAAkB;AAC1B,UAAA,OAAO,IAAA,CAAK,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,aAAa,OAAA,EAAS,sBAAA,EAAuB,EAAE,EAAG,GAAG,CAAA;AAAA,QACpF;AACA,QAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAIhB,GAAG,CAAA;AACN,QAAA,MAAM,KAAA,GAA4C;AAAA,UAChD,UAAA;AAAA,UACA,SAAA,EAAW,MAAA,CAAO,IAAA,CAAK,SAAS;AAAA,SAClC;AACA,QAAA,IAAI,OAAO,IAAA,CAAK,KAAA,KAAU,QAAA,EAAU,KAAA,CAAM,QAAQ,IAAA,CAAK,KAAA;AACvD,QAAA,IAAI,KAAK,cAAA,EAAgB,KAAA,CAAM,cAAA,GAAiB,MAAA,CAAO,KAAK,cAAc,CAAA;AAC1E,QAAA,OAAO,IAAA,CAAK,MAAM,MAAA,CAAO,KAAA,CAAM,KAAK,CAAC,CAAA;AAAA,MACvC;AAEA,MAAA,OAAO,IAAA;AAAA,QACL,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,WAAA,EAAa,OAAA,EAAS,CAAA,gBAAA,EAAmB,MAAM,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,EAAG,EAAE;AAAA,QAC9E;AAAA,OACF;AAAA,IACF,SAAS,GAAA,EAAK;AACZ,MAAA,OAAO,IAAA;AAAA,QACL,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,SAAA,CAAU,GAAG,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,GAAG,CAAA,EAAE,EAAE;AAAA,QAC9D,YAAY,GAAG;AAAA,OACjB;AAAA,IACF;AAAA,EACF,CAAA;AACF;AAGA,SAAS,QAAA,CAAS,UAAkB,QAAA,EAAiC;AACnE,EAAA,IAAI,QAAA,KAAa,UAAU,OAAO,GAAA;AAClC,EAAA,IAAI,CAAC,QAAA,CAAS,UAAA,CAAW,GAAG,QAAQ,CAAA,CAAA,CAAG,GAAG,OAAO,IAAA;AACjD,EAAA,OAAO,QAAA,CAAS,MAAM,QAAA,CAAS,MAAM,EAAE,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA,IAAK,GAAA;AAChE;AAEA,SAAS,IAAA,CAAK,IAAA,EAAe,MAAA,GAAS,GAAA,EAAe;AACnD,EAAA,OAAO,IAAI,QAAA,CAAS,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,EAAG;AAAA,IACxC,MAAA;AAAA,IACA,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA;AAAmB,GAC/C,CAAA;AACH;AAEA,eAAe,SAAsC,GAAA,EAA0B;AAC7E,EAAA,IAAI;AACF,IAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AAAA,EACzB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAC;AAAA,EACV;AACF;AAEA,SAAS,YAAY,GAAA,EAAsB;AACzC,EAAA,IAAI,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,YAAY,GAAA,EAAK;AACrD,IAAA,MAAM,MAAA,GAAS,MAAA,CAAQ,GAAA,CAA4B,MAAM,CAAA;AACzD,IAAA,IAAI,MAAA,CAAO,UAAU,MAAM,CAAA,IAAK,UAAU,GAAA,IAAO,MAAA,IAAU,KAAK,OAAO,MAAA;AAAA,EACzE;AACA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,UAAU,GAAA,EAAsB;AACvC,EAAA,IAAI,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,MAAA,IAAU,GAAA;AAC9C,IAAA,OAAO,MAAA,CAAQ,IAA0B,IAAI,CAAA;AAC/C,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,aAAa,GAAA,EAAsB;AAC1C,EAAA,OAAO,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,sBAAA;AAC9C","file":"server.cjs","sourcesContent":["/**\n * Backend handler for the browser-facing surface (the React hooks talk to this,\n * never to billow directly). Mount it at `/api/billow/*` in your app; it holds the\n * secret key server-side and resolves the customer from *your* session via\n * `identify` — so the browser can never choose which customer it is.\n *\n * // Next.js app/api/billow/[...billow]/route.ts\n * import { Billow } from \"@usebillow/sdk\";\n * import { createBillowHandler } from \"@usebillow/sdk/server\";\n * const billow = new Billow(process.env.BILLOW_SECRET_KEY!);\n * const handler = createBillowHandler(billow, {\n * identify: async (req) => (await getSession(req))?.customerId ?? null,\n * });\n * export { handler as GET, handler as POST };\n *\n * Exposed (all customer-scoped except the public catalog):\n * GET /products — catalog for pricing tables (no customer needed)\n * GET /entitlements — the identified customer's live entitlements\n * POST /check — gate one feature for the identified customer\n * POST /attach — start a subscription the customer pays for\n *\n * **Not** exposed by default: `track`. Recording usage from the browser would let\n * a user forge their own balance (`value: -999999`) — it is a server-trust verb.\n * Call `billow.track(...)` from your backend when the metered event happens. If you\n * accept the risk for a low-stakes counter, opt in with `allowClientTrack: true`.\n */\nimport type { Billow } from \"./index\";\n\nexport interface BillowHandlerOptions {\n /**\n * Resolve the **trusted** customer id from the request (your own session/auth).\n * Return `null` when unauthenticated — customer-scoped routes then 401.\n */\n identify: (req: Request) => Promise<string | null> | string | null;\n /** Path the handler is mounted under. Default `/api/billow`. */\n basePath?: string;\n /** Fixed post-checkout return URL (the client cannot override it). */\n checkoutReturnUrl?: string;\n /**\n * Allow the browser to POST `/track`. Off by default — see the file header.\n * Only enable for usage a malicious client forging is acceptable for.\n */\n allowClientTrack?: boolean;\n}\n\ntype Handler = (req: Request) => Promise<Response>;\n\nexport function createBillowHandler(billow: Billow, opts: BillowHandlerOptions): Handler {\n const basePath = (opts.basePath ?? \"/api/billow\").replace(/\\/$/, \"\");\n\n return async function handler(req: Request): Promise<Response> {\n const route = subRoute(new URL(req.url).pathname, basePath);\n const method = req.method.toUpperCase();\n if (route === null) {\n return json({ error: { code: \"not_found\", message: \"Not a billow route\" } }, 404);\n }\n\n try {\n // Public catalog — no customer needed (pricing tables).\n if (method === \"GET\" && route === \"/products\") {\n return json({ products: await billow.products.list() });\n }\n\n // Everything else is scoped to the server-identified customer.\n const customerId = await opts.identify(req);\n if (!customerId) {\n return json({ error: { code: \"unauthenticated\", message: \"No customer\" } }, 401);\n }\n\n if (method === \"GET\" && route === \"/entitlements\") {\n return json(await billow.entitlements(customerId));\n }\n if (method === \"POST\" && route === \"/check\") {\n const body = await readJson<{ featureId?: unknown }>(req);\n return json(await billow.check({ customerId, featureId: String(body.featureId) }));\n }\n if (method === \"POST\" && route === \"/attach\") {\n const body = await readJson<{ productId?: unknown; couponCode?: unknown }>(req);\n const input: Parameters<typeof billow.attach>[0] = {\n customerId,\n productId: String(body.productId),\n };\n // Only a user-entered promo is forwarded; trial/card policy is the\n // merchant's, not the client's — set those in a server-side route.\n if (body.couponCode) input.couponCode = String(body.couponCode);\n if (opts.checkoutReturnUrl) input.redirectionUrl = opts.checkoutReturnUrl;\n return json(await billow.attach(input));\n }\n if (method === \"POST\" && route === \"/track\") {\n if (!opts.allowClientTrack) {\n return json({ error: { code: \"not_found\", message: \"track is server-only\" } }, 404);\n }\n const body = await readJson<{\n featureId?: unknown;\n value?: unknown;\n idempotencyKey?: unknown;\n }>(req);\n const input: Parameters<typeof billow.track>[0] = {\n customerId,\n featureId: String(body.featureId),\n };\n if (typeof body.value === \"number\") input.value = body.value;\n if (body.idempotencyKey) input.idempotencyKey = String(body.idempotencyKey);\n return json(await billow.track(input));\n }\n\n return json(\n { error: { code: \"not_found\", message: `No billow route ${method} ${route}` } },\n 404,\n );\n } catch (err) {\n return json(\n { error: { code: errorCode(err), message: errorMessage(err) } },\n errorStatus(err),\n );\n }\n };\n}\n\n/** The path under `basePath`, or `null` when the request isn't under it at all. */\nfunction subRoute(pathname: string, basePath: string): string | null {\n if (pathname === basePath) return \"/\";\n if (!pathname.startsWith(`${basePath}/`)) return null;\n return pathname.slice(basePath.length).replace(/\\/+$/, \"\") || \"/\";\n}\n\nfunction json(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function readJson<T = Record<string, unknown>>(req: Request): Promise<T> {\n try {\n return (await req.json()) as T;\n } catch {\n return {} as T;\n }\n}\n\nfunction errorStatus(err: unknown): number {\n if (err && typeof err === \"object\" && \"status\" in err) {\n const status = Number((err as { status: unknown }).status);\n if (Number.isInteger(status) && status >= 400 && status <= 599) return status;\n }\n return 500;\n}\n\nfunction errorCode(err: unknown): string {\n if (err && typeof err === \"object\" && \"code\" in err)\n return String((err as { code: unknown }).code);\n return \"error\";\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : \"billow handler error\";\n}\n"]}
@@ -0,0 +1,54 @@
1
+ import { Billow } from './index.cjs';
2
+ import './billing-DZ4rIyg7.cjs';
3
+ import 'zod';
4
+ import './billing-status-BZQN_gm7.cjs';
5
+ import './status.cjs';
6
+ import './credits-C3Fe3TO0.cjs';
7
+
8
+ /**
9
+ * Backend handler for the browser-facing surface (the React hooks talk to this,
10
+ * never to billow directly). Mount it at `/api/billow/*` in your app; it holds the
11
+ * secret key server-side and resolves the customer from *your* session via
12
+ * `identify` — so the browser can never choose which customer it is.
13
+ *
14
+ * // Next.js app/api/billow/[...billow]/route.ts
15
+ * import { Billow } from "@usebillow/sdk";
16
+ * import { createBillowHandler } from "@usebillow/sdk/server";
17
+ * const billow = new Billow(process.env.BILLOW_SECRET_KEY!);
18
+ * const handler = createBillowHandler(billow, {
19
+ * identify: async (req) => (await getSession(req))?.customerId ?? null,
20
+ * });
21
+ * export { handler as GET, handler as POST };
22
+ *
23
+ * Exposed (all customer-scoped except the public catalog):
24
+ * GET /products — catalog for pricing tables (no customer needed)
25
+ * GET /entitlements — the identified customer's live entitlements
26
+ * POST /check — gate one feature for the identified customer
27
+ * POST /attach — start a subscription the customer pays for
28
+ *
29
+ * **Not** exposed by default: `track`. Recording usage from the browser would let
30
+ * a user forge their own balance (`value: -999999`) — it is a server-trust verb.
31
+ * Call `billow.track(...)` from your backend when the metered event happens. If you
32
+ * accept the risk for a low-stakes counter, opt in with `allowClientTrack: true`.
33
+ */
34
+
35
+ interface BillowHandlerOptions {
36
+ /**
37
+ * Resolve the **trusted** customer id from the request (your own session/auth).
38
+ * Return `null` when unauthenticated — customer-scoped routes then 401.
39
+ */
40
+ identify: (req: Request) => Promise<string | null> | string | null;
41
+ /** Path the handler is mounted under. Default `/api/billow`. */
42
+ basePath?: string;
43
+ /** Fixed post-checkout return URL (the client cannot override it). */
44
+ checkoutReturnUrl?: string;
45
+ /**
46
+ * Allow the browser to POST `/track`. Off by default — see the file header.
47
+ * Only enable for usage a malicious client forging is acceptable for.
48
+ */
49
+ allowClientTrack?: boolean;
50
+ }
51
+ type Handler = (req: Request) => Promise<Response>;
52
+ declare function createBillowHandler(billow: Billow, opts: BillowHandlerOptions): Handler;
53
+
54
+ export { type BillowHandlerOptions, createBillowHandler };
@@ -0,0 +1,54 @@
1
+ import { Billow } from './index.js';
2
+ import './billing-C4RIMgH_.js';
3
+ import 'zod';
4
+ import './billing-status-BZQN_gm7.js';
5
+ import './status.js';
6
+ import './credits-C3Fe3TO0.js';
7
+
8
+ /**
9
+ * Backend handler for the browser-facing surface (the React hooks talk to this,
10
+ * never to billow directly). Mount it at `/api/billow/*` in your app; it holds the
11
+ * secret key server-side and resolves the customer from *your* session via
12
+ * `identify` — so the browser can never choose which customer it is.
13
+ *
14
+ * // Next.js app/api/billow/[...billow]/route.ts
15
+ * import { Billow } from "@usebillow/sdk";
16
+ * import { createBillowHandler } from "@usebillow/sdk/server";
17
+ * const billow = new Billow(process.env.BILLOW_SECRET_KEY!);
18
+ * const handler = createBillowHandler(billow, {
19
+ * identify: async (req) => (await getSession(req))?.customerId ?? null,
20
+ * });
21
+ * export { handler as GET, handler as POST };
22
+ *
23
+ * Exposed (all customer-scoped except the public catalog):
24
+ * GET /products — catalog for pricing tables (no customer needed)
25
+ * GET /entitlements — the identified customer's live entitlements
26
+ * POST /check — gate one feature for the identified customer
27
+ * POST /attach — start a subscription the customer pays for
28
+ *
29
+ * **Not** exposed by default: `track`. Recording usage from the browser would let
30
+ * a user forge their own balance (`value: -999999`) — it is a server-trust verb.
31
+ * Call `billow.track(...)` from your backend when the metered event happens. If you
32
+ * accept the risk for a low-stakes counter, opt in with `allowClientTrack: true`.
33
+ */
34
+
35
+ interface BillowHandlerOptions {
36
+ /**
37
+ * Resolve the **trusted** customer id from the request (your own session/auth).
38
+ * Return `null` when unauthenticated — customer-scoped routes then 401.
39
+ */
40
+ identify: (req: Request) => Promise<string | null> | string | null;
41
+ /** Path the handler is mounted under. Default `/api/billow`. */
42
+ basePath?: string;
43
+ /** Fixed post-checkout return URL (the client cannot override it). */
44
+ checkoutReturnUrl?: string;
45
+ /**
46
+ * Allow the browser to POST `/track`. Off by default — see the file header.
47
+ * Only enable for usage a malicious client forging is acceptable for.
48
+ */
49
+ allowClientTrack?: boolean;
50
+ }
51
+ type Handler = (req: Request) => Promise<Response>;
52
+ declare function createBillowHandler(billow: Billow, opts: BillowHandlerOptions): Handler;
53
+
54
+ export { type BillowHandlerOptions, createBillowHandler };
package/dist/server.js ADDED
@@ -0,0 +1,96 @@
1
+ // src/server.ts
2
+ function createBillowHandler(billow, opts) {
3
+ const basePath = (opts.basePath ?? "/api/billow").replace(/\/$/, "");
4
+ return async function handler(req) {
5
+ const route = subRoute(new URL(req.url).pathname, basePath);
6
+ const method = req.method.toUpperCase();
7
+ if (route === null) {
8
+ return json({ error: { code: "not_found", message: "Not a billow route" } }, 404);
9
+ }
10
+ try {
11
+ if (method === "GET" && route === "/products") {
12
+ return json({ products: await billow.products.list() });
13
+ }
14
+ const customerId = await opts.identify(req);
15
+ if (!customerId) {
16
+ return json({ error: { code: "unauthenticated", message: "No customer" } }, 401);
17
+ }
18
+ if (method === "GET" && route === "/entitlements") {
19
+ return json(await billow.entitlements(customerId));
20
+ }
21
+ if (method === "POST" && route === "/check") {
22
+ const body = await readJson(req);
23
+ return json(await billow.check({ customerId, featureId: String(body.featureId) }));
24
+ }
25
+ if (method === "POST" && route === "/attach") {
26
+ const body = await readJson(req);
27
+ const input = {
28
+ customerId,
29
+ productId: String(body.productId)
30
+ };
31
+ if (body.couponCode) input.couponCode = String(body.couponCode);
32
+ if (opts.checkoutReturnUrl) input.redirectionUrl = opts.checkoutReturnUrl;
33
+ return json(await billow.attach(input));
34
+ }
35
+ if (method === "POST" && route === "/track") {
36
+ if (!opts.allowClientTrack) {
37
+ return json({ error: { code: "not_found", message: "track is server-only" } }, 404);
38
+ }
39
+ const body = await readJson(req);
40
+ const input = {
41
+ customerId,
42
+ featureId: String(body.featureId)
43
+ };
44
+ if (typeof body.value === "number") input.value = body.value;
45
+ if (body.idempotencyKey) input.idempotencyKey = String(body.idempotencyKey);
46
+ return json(await billow.track(input));
47
+ }
48
+ return json(
49
+ { error: { code: "not_found", message: `No billow route ${method} ${route}` } },
50
+ 404
51
+ );
52
+ } catch (err) {
53
+ return json(
54
+ { error: { code: errorCode(err), message: errorMessage(err) } },
55
+ errorStatus(err)
56
+ );
57
+ }
58
+ };
59
+ }
60
+ function subRoute(pathname, basePath) {
61
+ if (pathname === basePath) return "/";
62
+ if (!pathname.startsWith(`${basePath}/`)) return null;
63
+ return pathname.slice(basePath.length).replace(/\/+$/, "") || "/";
64
+ }
65
+ function json(data, status = 200) {
66
+ return new Response(JSON.stringify(data), {
67
+ status,
68
+ headers: { "content-type": "application/json" }
69
+ });
70
+ }
71
+ async function readJson(req) {
72
+ try {
73
+ return await req.json();
74
+ } catch {
75
+ return {};
76
+ }
77
+ }
78
+ function errorStatus(err) {
79
+ if (err && typeof err === "object" && "status" in err) {
80
+ const status = Number(err.status);
81
+ if (Number.isInteger(status) && status >= 400 && status <= 599) return status;
82
+ }
83
+ return 500;
84
+ }
85
+ function errorCode(err) {
86
+ if (err && typeof err === "object" && "code" in err)
87
+ return String(err.code);
88
+ return "error";
89
+ }
90
+ function errorMessage(err) {
91
+ return err instanceof Error ? err.message : "billow handler error";
92
+ }
93
+
94
+ export { createBillowHandler };
95
+ //# sourceMappingURL=server.js.map
96
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["/**\n * Backend handler for the browser-facing surface (the React hooks talk to this,\n * never to billow directly). Mount it at `/api/billow/*` in your app; it holds the\n * secret key server-side and resolves the customer from *your* session via\n * `identify` — so the browser can never choose which customer it is.\n *\n * // Next.js app/api/billow/[...billow]/route.ts\n * import { Billow } from \"@usebillow/sdk\";\n * import { createBillowHandler } from \"@usebillow/sdk/server\";\n * const billow = new Billow(process.env.BILLOW_SECRET_KEY!);\n * const handler = createBillowHandler(billow, {\n * identify: async (req) => (await getSession(req))?.customerId ?? null,\n * });\n * export { handler as GET, handler as POST };\n *\n * Exposed (all customer-scoped except the public catalog):\n * GET /products — catalog for pricing tables (no customer needed)\n * GET /entitlements — the identified customer's live entitlements\n * POST /check — gate one feature for the identified customer\n * POST /attach — start a subscription the customer pays for\n *\n * **Not** exposed by default: `track`. Recording usage from the browser would let\n * a user forge their own balance (`value: -999999`) — it is a server-trust verb.\n * Call `billow.track(...)` from your backend when the metered event happens. If you\n * accept the risk for a low-stakes counter, opt in with `allowClientTrack: true`.\n */\nimport type { Billow } from \"./index\";\n\nexport interface BillowHandlerOptions {\n /**\n * Resolve the **trusted** customer id from the request (your own session/auth).\n * Return `null` when unauthenticated — customer-scoped routes then 401.\n */\n identify: (req: Request) => Promise<string | null> | string | null;\n /** Path the handler is mounted under. Default `/api/billow`. */\n basePath?: string;\n /** Fixed post-checkout return URL (the client cannot override it). */\n checkoutReturnUrl?: string;\n /**\n * Allow the browser to POST `/track`. Off by default — see the file header.\n * Only enable for usage a malicious client forging is acceptable for.\n */\n allowClientTrack?: boolean;\n}\n\ntype Handler = (req: Request) => Promise<Response>;\n\nexport function createBillowHandler(billow: Billow, opts: BillowHandlerOptions): Handler {\n const basePath = (opts.basePath ?? \"/api/billow\").replace(/\\/$/, \"\");\n\n return async function handler(req: Request): Promise<Response> {\n const route = subRoute(new URL(req.url).pathname, basePath);\n const method = req.method.toUpperCase();\n if (route === null) {\n return json({ error: { code: \"not_found\", message: \"Not a billow route\" } }, 404);\n }\n\n try {\n // Public catalog — no customer needed (pricing tables).\n if (method === \"GET\" && route === \"/products\") {\n return json({ products: await billow.products.list() });\n }\n\n // Everything else is scoped to the server-identified customer.\n const customerId = await opts.identify(req);\n if (!customerId) {\n return json({ error: { code: \"unauthenticated\", message: \"No customer\" } }, 401);\n }\n\n if (method === \"GET\" && route === \"/entitlements\") {\n return json(await billow.entitlements(customerId));\n }\n if (method === \"POST\" && route === \"/check\") {\n const body = await readJson<{ featureId?: unknown }>(req);\n return json(await billow.check({ customerId, featureId: String(body.featureId) }));\n }\n if (method === \"POST\" && route === \"/attach\") {\n const body = await readJson<{ productId?: unknown; couponCode?: unknown }>(req);\n const input: Parameters<typeof billow.attach>[0] = {\n customerId,\n productId: String(body.productId),\n };\n // Only a user-entered promo is forwarded; trial/card policy is the\n // merchant's, not the client's — set those in a server-side route.\n if (body.couponCode) input.couponCode = String(body.couponCode);\n if (opts.checkoutReturnUrl) input.redirectionUrl = opts.checkoutReturnUrl;\n return json(await billow.attach(input));\n }\n if (method === \"POST\" && route === \"/track\") {\n if (!opts.allowClientTrack) {\n return json({ error: { code: \"not_found\", message: \"track is server-only\" } }, 404);\n }\n const body = await readJson<{\n featureId?: unknown;\n value?: unknown;\n idempotencyKey?: unknown;\n }>(req);\n const input: Parameters<typeof billow.track>[0] = {\n customerId,\n featureId: String(body.featureId),\n };\n if (typeof body.value === \"number\") input.value = body.value;\n if (body.idempotencyKey) input.idempotencyKey = String(body.idempotencyKey);\n return json(await billow.track(input));\n }\n\n return json(\n { error: { code: \"not_found\", message: `No billow route ${method} ${route}` } },\n 404,\n );\n } catch (err) {\n return json(\n { error: { code: errorCode(err), message: errorMessage(err) } },\n errorStatus(err),\n );\n }\n };\n}\n\n/** The path under `basePath`, or `null` when the request isn't under it at all. */\nfunction subRoute(pathname: string, basePath: string): string | null {\n if (pathname === basePath) return \"/\";\n if (!pathname.startsWith(`${basePath}/`)) return null;\n return pathname.slice(basePath.length).replace(/\\/+$/, \"\") || \"/\";\n}\n\nfunction json(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function readJson<T = Record<string, unknown>>(req: Request): Promise<T> {\n try {\n return (await req.json()) as T;\n } catch {\n return {} as T;\n }\n}\n\nfunction errorStatus(err: unknown): number {\n if (err && typeof err === \"object\" && \"status\" in err) {\n const status = Number((err as { status: unknown }).status);\n if (Number.isInteger(status) && status >= 400 && status <= 599) return status;\n }\n return 500;\n}\n\nfunction errorCode(err: unknown): string {\n if (err && typeof err === \"object\" && \"code\" in err)\n return String((err as { code: unknown }).code);\n return \"error\";\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : \"billow handler error\";\n}\n"]}
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ // src/billing-status.ts
4
+ var CHARGE_STATUSES = [
5
+ "pending",
6
+ "processing",
7
+ "succeeded",
8
+ "failed",
9
+ "requires_reconciliation",
10
+ "disputed"
11
+ ];
12
+ var INVOICE_STATUSES = ["draft", "open", "paid", "uncollectible", "void"];
13
+ var REFUND_STATUSES = ["pending", "succeeded", "failed"];
14
+ var PAYMENT_METHOD_STATUSES = ["active", "expired", "removed"];
15
+ var WEBHOOK_DELIVERY_STATUSES = ["delivered", "failed"];
16
+
17
+ // src/subscription-status.ts
18
+ var SUBSCRIPTION_STATUSES = [
19
+ "incomplete",
20
+ "incomplete_expired",
21
+ "trialing",
22
+ "active",
23
+ "past_due",
24
+ "paused",
25
+ "canceled",
26
+ "unpaid"
27
+ ];
28
+ var ACTIVE_SUBSCRIPTION_STATUSES = [
29
+ "trialing",
30
+ "active",
31
+ "past_due"
32
+ ];
33
+ var TERMINAL_SUBSCRIPTION_STATUSES = [
34
+ "incomplete_expired",
35
+ "canceled"
36
+ ];
37
+ var NEVER_ACTIVATED_SUBSCRIPTION_STATUSES = [
38
+ "incomplete",
39
+ "incomplete_expired"
40
+ ];
41
+ function grantsAccess(status) {
42
+ return ACTIVE_SUBSCRIPTION_STATUSES.includes(status);
43
+ }
44
+ function isTerminal(status) {
45
+ return TERMINAL_SUBSCRIPTION_STATUSES.includes(status);
46
+ }
47
+
48
+ exports.ACTIVE_SUBSCRIPTION_STATUSES = ACTIVE_SUBSCRIPTION_STATUSES;
49
+ exports.CHARGE_STATUSES = CHARGE_STATUSES;
50
+ exports.INVOICE_STATUSES = INVOICE_STATUSES;
51
+ exports.NEVER_ACTIVATED_SUBSCRIPTION_STATUSES = NEVER_ACTIVATED_SUBSCRIPTION_STATUSES;
52
+ exports.PAYMENT_METHOD_STATUSES = PAYMENT_METHOD_STATUSES;
53
+ exports.REFUND_STATUSES = REFUND_STATUSES;
54
+ exports.SUBSCRIPTION_STATUSES = SUBSCRIPTION_STATUSES;
55
+ exports.TERMINAL_SUBSCRIPTION_STATUSES = TERMINAL_SUBSCRIPTION_STATUSES;
56
+ exports.WEBHOOK_DELIVERY_STATUSES = WEBHOOK_DELIVERY_STATUSES;
57
+ exports.grantsAccess = grantsAccess;
58
+ exports.isTerminal = isTerminal;
59
+ //# sourceMappingURL=status.cjs.map
60
+ //# sourceMappingURL=status.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/billing-status.ts","../src/subscription-status.ts"],"names":[],"mappings":";;;AASO,IAAM,eAAA,GAAkB;AAAA,EAC7B,SAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,yBAAA;AAAA,EACA;AACF;AAIO,IAAM,mBAAmB,CAAC,OAAA,EAAS,MAAA,EAAQ,MAAA,EAAQ,iBAAiB,MAAM;AAI1E,IAAM,eAAA,GAAkB,CAAC,SAAA,EAAW,WAAA,EAAa,QAAQ;AAIzD,IAAM,uBAAA,GAA0B,CAAC,QAAA,EAAU,SAAA,EAAW,SAAS;AAS/D,IAAM,yBAAA,GAA4B,CAAC,WAAA,EAAa,QAAQ;;;ACxBxD,IAAM,qBAAA,GAAwB;AAAA,EACnC,YAAA;AAAA,EACA,oBAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF;AAQO,IAAM,4BAAA,GAA8D;AAAA,EACzE,UAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF;AAGO,IAAM,8BAAA,GAAgE;AAAA,EAC3E,oBAAA;AAAA,EACA;AACF;AAGO,IAAM,qCAAA,GAAuE;AAAA,EAClF,YAAA;AAAA,EACA;AACF;AAGO,SAAS,aAAa,MAAA,EAAqC;AAChE,EAAA,OAAO,4BAAA,CAA6B,SAAS,MAAM,CAAA;AACrD;AAGO,SAAS,WAAW,MAAA,EAAqC;AAC9D,EAAA,OAAO,8BAAA,CAA+B,SAAS,MAAM,CAAA;AACvD","file":"status.cjs","sourcesContent":["/**\n * Charge / invoice / refund / payment-method / webhook-delivery status vocabularies,\n * mirrored for the SDK (which cannot import the server-side `@billow/core`). The DB-enum\n * ones (charge, invoice, refund, payment method) mirror core enums and are parity-tested\n * against it (see billing-status.test.ts); webhook-delivery status is a free-text column\n * server-side with a fixed vocabulary, documented here.\n */\n\n/** A charge's lifecycle status — mirrors `@billow/core` `CHARGE_STATUSES`. */\nexport const CHARGE_STATUSES = [\n \"pending\",\n \"processing\",\n \"succeeded\",\n \"failed\",\n \"requires_reconciliation\",\n \"disputed\",\n] as const;\nexport type ChargeStatus = (typeof CHARGE_STATUSES)[number];\n\n/** An invoice's lifecycle status — mirrors `@billow/core` `INVOICE_STATUSES`. */\nexport const INVOICE_STATUSES = [\"draft\", \"open\", \"paid\", \"uncollectible\", \"void\"] as const;\nexport type InvoiceStatus = (typeof INVOICE_STATUSES)[number];\n\n/** A refund's lifecycle status — mirrors `@billow/core` `REFUND_STATUSES`. */\nexport const REFUND_STATUSES = [\"pending\", \"succeeded\", \"failed\"] as const;\nexport type RefundStatus = (typeof REFUND_STATUSES)[number];\n\n/** A saved payment method's status — mirrors `@billow/core` `PAYMENT_METHOD_STATUSES`. */\nexport const PAYMENT_METHOD_STATUSES = [\"active\", \"expired\", \"removed\"] as const;\nexport type PaymentMethodStatus = (typeof PAYMENT_METHOD_STATUSES)[number];\n\n/**\n * A webhook delivery attempt's status. Not a DB enum (a free-text column); the dispatcher\n * inserts one row per attempt with exactly `delivered` (a 2xx) or `failed` (anything else) —\n * see `packages/server/src/jobs/outbox.ts`. (Retry/backoff state lives on the outbox event,\n * a separate table, not on the delivery record.)\n */\nexport const WEBHOOK_DELIVERY_STATUSES = [\"delivered\", \"failed\"] as const;\nexport type WebhookDeliveryStatus = (typeof WEBHOOK_DELIVERY_STATUSES)[number];\n","/**\n * The subscription status vocabulary + access predicate, mirrored from @billow/core\n * (`enums.ts` + `billing/subscription-state.ts`) so SDK consumers can type and branch on\n * billow's own model instead of hand-maintaining a union that drifts (a real bug: an app\n * copy once carried a non-existent `ended` status - that string is a webhook event type,\n * not a subscription status).\n *\n * Mirrored, not imported: @billow/core is a server package the published SDK cannot depend\n * on (the same reason @billow/contracts mirrors core's pure logic). The parity test\n * `subscription-status.test.ts` imports @billow/core and fails CI on any drift.\n */\n\n/** The 8 canonical subscription statuses, in lifecycle order. */\nexport const SUBSCRIPTION_STATUSES = [\n \"incomplete\",\n \"incomplete_expired\",\n \"trialing\",\n \"active\",\n \"past_due\",\n \"paused\",\n \"canceled\",\n \"unpaid\",\n] as const;\n\nexport type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];\n\n/**\n * Statuses that currently grant access (a live commitment): `trialing`, `active`, and\n * `past_due` (access is retained through dunning). Note `paused` does NOT grant access.\n */\nexport const ACTIVE_SUBSCRIPTION_STATUSES: readonly SubscriptionStatus[] = [\n \"trialing\",\n \"active\",\n \"past_due\",\n];\n\n/** Statuses a subscription can never leave: `incomplete_expired` and `canceled`. */\nexport const TERMINAL_SUBSCRIPTION_STATUSES: readonly SubscriptionStatus[] = [\n \"incomplete_expired\",\n \"canceled\",\n];\n\n/** Statuses in which a subscription never activated (no access was ever granted). */\nexport const NEVER_ACTIVATED_SUBSCRIPTION_STATUSES: readonly SubscriptionStatus[] = [\n \"incomplete\",\n \"incomplete_expired\",\n];\n\n/** Whether access should currently be granted for a subscription in this state. */\nexport function grantsAccess(status: SubscriptionStatus): boolean {\n return ACTIVE_SUBSCRIPTION_STATUSES.includes(status);\n}\n\n/** Whether this is a terminal status the subscription can never leave. */\nexport function isTerminal(status: SubscriptionStatus): boolean {\n return TERMINAL_SUBSCRIPTION_STATUSES.includes(status);\n}\n"]}
@@ -0,0 +1,31 @@
1
+ export { a as CHARGE_STATUSES, C as ChargeStatus, b as INVOICE_STATUSES, I as InvoiceStatus, c as PAYMENT_METHOD_STATUSES, P as PaymentMethodStatus, d as REFUND_STATUSES, R as RefundStatus, e as WEBHOOK_DELIVERY_STATUSES, W as WebhookDeliveryStatus } from './billing-status-BZQN_gm7.cjs';
2
+
3
+ /**
4
+ * The subscription status vocabulary + access predicate, mirrored from @billow/core
5
+ * (`enums.ts` + `billing/subscription-state.ts`) so SDK consumers can type and branch on
6
+ * billow's own model instead of hand-maintaining a union that drifts (a real bug: an app
7
+ * copy once carried a non-existent `ended` status - that string is a webhook event type,
8
+ * not a subscription status).
9
+ *
10
+ * Mirrored, not imported: @billow/core is a server package the published SDK cannot depend
11
+ * on (the same reason @billow/contracts mirrors core's pure logic). The parity test
12
+ * `subscription-status.test.ts` imports @billow/core and fails CI on any drift.
13
+ */
14
+ /** The 8 canonical subscription statuses, in lifecycle order. */
15
+ declare const SUBSCRIPTION_STATUSES: readonly ["incomplete", "incomplete_expired", "trialing", "active", "past_due", "paused", "canceled", "unpaid"];
16
+ type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];
17
+ /**
18
+ * Statuses that currently grant access (a live commitment): `trialing`, `active`, and
19
+ * `past_due` (access is retained through dunning). Note `paused` does NOT grant access.
20
+ */
21
+ declare const ACTIVE_SUBSCRIPTION_STATUSES: readonly SubscriptionStatus[];
22
+ /** Statuses a subscription can never leave: `incomplete_expired` and `canceled`. */
23
+ declare const TERMINAL_SUBSCRIPTION_STATUSES: readonly SubscriptionStatus[];
24
+ /** Statuses in which a subscription never activated (no access was ever granted). */
25
+ declare const NEVER_ACTIVATED_SUBSCRIPTION_STATUSES: readonly SubscriptionStatus[];
26
+ /** Whether access should currently be granted for a subscription in this state. */
27
+ declare function grantsAccess(status: SubscriptionStatus): boolean;
28
+ /** Whether this is a terminal status the subscription can never leave. */
29
+ declare function isTerminal(status: SubscriptionStatus): boolean;
30
+
31
+ export { ACTIVE_SUBSCRIPTION_STATUSES, NEVER_ACTIVATED_SUBSCRIPTION_STATUSES, SUBSCRIPTION_STATUSES, type SubscriptionStatus, TERMINAL_SUBSCRIPTION_STATUSES, grantsAccess, isTerminal };