@munchi_oy/license-sdk 0.7.0 → 0.10.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/README.md +5 -3
- package/dist/index.cjs +81 -25
- package/dist/index.d.cts +59 -11
- package/dist/index.d.ts +59 -11
- package/dist/index.js +78 -25
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @munchi_oy/license-sdk
|
|
2
2
|
|
|
3
|
-
Asks the Munchi license service what a venue may use.
|
|
3
|
+
Asks the Munchi license service what a venue may use. One dependency (`zod`, for the event schema), Node 20+, ESM and CommonJS.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
import { LicenseClient } from "@munchi_oy/license-sdk";
|
|
@@ -31,6 +31,8 @@ const snapshot = await license.entitlements(venueId); // full snapshot, null = u
|
|
|
31
31
|
|
|
32
32
|
Call `invalidate(venueId)` when you receive a `license.changed` event.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
A service that runs a store's channels subscribes to `license.access_changed` (topic `one-license-<env>-access`) and validates each message with the exported `accessChangedEventSchema` (type `AccessChangedEvent`, kind `ACCESS_CHANGED`); drop an `id` already applied and a `snapshotVersion` older than the last one applied for the venue. `hasAccess(event)` is false when the store may use nothing (`suspended`, `terminated`, `canceled`: an unpaid store too), the same rule as `can()`; a feature is usable when it is true and the feature is in `features`.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
It also exports the shared names: `PRODUCTS` and `PLANS` (catalog codes), `USAGE_METRICS` (what usage can cost money) and `REQUESTABLE_PRODUCTS` (what a store asks Munchi for in the dashboard: `ari`, `business_account`, `capital`, `foodora`, `bolt`, `card_payments_pos`, `card_payments_kiosk`, `sevenrooms`, `tableonline`, `zenchef`), with `STORE_ONLY_PRODUCTS` and `isStoreOnlyProduct` for the ones only a store asks for, never the whole group (the delivery apps, card payments at the POS or kiosk, and the reservation systems).
|
|
37
|
+
|
|
38
|
+
For a group's company (ADR 0025) it exports `companyIds(country, { businessId, vatId })` (the IDs to store, as written and trimmed; nothing is refused, a well-formed Finnish Y-tunnus is written `NNNNNNN-C` and its VAT ID derived when none is given), `finnishBusinessId(input)` (the Y-tunnus as `NNNNNNN-C`, or `null` when its format or check digit is wrong), `finnishVatId(businessId)` (`FI` and its digits), and the types `LegalEntityChange` (the body of `PATCH /v1/accounts/:accountId/legal-entities/:legalEntityId`) and `LegalEntity` (its response).
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
+
ACCESS_CHANGED: () => ACCESS_CHANGED,
|
|
24
25
|
LicenseClient: () => LicenseClient,
|
|
25
26
|
PLANS: () => PLANS,
|
|
26
27
|
PRODUCTS: () => PRODUCTS,
|
|
@@ -28,9 +29,11 @@ __export(index_exports, {
|
|
|
28
29
|
STORE_ONLY_PRODUCTS: () => STORE_ONLY_PRODUCTS,
|
|
29
30
|
USAGE_METRICS: () => USAGE_METRICS,
|
|
30
31
|
USAGE_METRIC_NAMES: () => USAGE_METRIC_NAMES,
|
|
32
|
+
accessChangedEventSchema: () => accessChangedEventSchema,
|
|
31
33
|
companyIds: () => companyIds,
|
|
32
34
|
finnishBusinessId: () => finnishBusinessId,
|
|
33
35
|
finnishVatId: () => finnishVatId,
|
|
36
|
+
hasAccess: () => hasAccess,
|
|
34
37
|
isStoreOnlyProduct: () => isStoreOnlyProduct
|
|
35
38
|
});
|
|
36
39
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -41,6 +44,9 @@ var PRODUCTS = {
|
|
|
41
44
|
one: "MUN-ONE",
|
|
42
45
|
kiosk: "MUN-ADD-KIOSK",
|
|
43
46
|
mobileApp: "MUN-ADD-APP",
|
|
47
|
+
munchiOrder: "MUN-ADD-ORDER",
|
|
48
|
+
paymentTerminal: "MUN-ADD-TERMINAL",
|
|
49
|
+
hardware: "MUN-ADD-HARDWARE",
|
|
44
50
|
mobileAppSetup: "MUN-SETUP-APP",
|
|
45
51
|
sms: "MUN-SMS",
|
|
46
52
|
loyaltyOverage: "MUN-OVR-LOYALTY"
|
|
@@ -50,6 +56,49 @@ var PLANS = [
|
|
|
50
56
|
PRODUCTS.one
|
|
51
57
|
];
|
|
52
58
|
|
|
59
|
+
// ../license-core/src/events/access-changed.ts
|
|
60
|
+
var import_zod = require("zod");
|
|
61
|
+
var ACCESS_CHANGED = "license.access_changed";
|
|
62
|
+
var NO_ACCESS_STATES = /* @__PURE__ */ new Set([
|
|
63
|
+
"suspended",
|
|
64
|
+
"terminated",
|
|
65
|
+
"canceled"
|
|
66
|
+
]);
|
|
67
|
+
var accessChangedEventSchema = import_zod.z.object({
|
|
68
|
+
kind: import_zod.z.literal(ACCESS_CHANGED),
|
|
69
|
+
/** The event's own id: a redelivered message has the same one. */
|
|
70
|
+
id: import_zod.z.string().min(1),
|
|
71
|
+
venueId: import_zod.z.number().int(),
|
|
72
|
+
locationId: import_zod.z.string().min(1),
|
|
73
|
+
accountId: import_zod.z.string().min(1),
|
|
74
|
+
state: import_zod.z.enum([
|
|
75
|
+
"trial",
|
|
76
|
+
"active",
|
|
77
|
+
"grace",
|
|
78
|
+
"start",
|
|
79
|
+
"suspended",
|
|
80
|
+
"terminated",
|
|
81
|
+
"canceled"
|
|
82
|
+
]),
|
|
83
|
+
/** As in the snapshot: empty when the store is closed or switched off. */
|
|
84
|
+
features: import_zod.z.array(import_zod.z.string().min(1)),
|
|
85
|
+
/** As in the snapshot: how many of something the store may have; `null` = unlimited. */
|
|
86
|
+
limits: import_zod.z.record(import_zod.z.string(), import_zod.z.number().int().min(0).nullable()),
|
|
87
|
+
suspension: import_zod.z.object({
|
|
88
|
+
reason: import_zod.z.string().min(1),
|
|
89
|
+
mode: import_zod.z.enum([
|
|
90
|
+
"read_only",
|
|
91
|
+
"off"
|
|
92
|
+
])
|
|
93
|
+
}).nullable(),
|
|
94
|
+
/** The audit action that changed it, e.g. `license.close` or `license.payment.suspend`. */
|
|
95
|
+
reason: import_zod.z.string().min(1),
|
|
96
|
+
/** The snapshot's `v`: a consumer drops a message older than the last one it applied. */
|
|
97
|
+
snapshotVersion: import_zod.z.number().int().min(0),
|
|
98
|
+
at: import_zod.z.iso.datetime()
|
|
99
|
+
});
|
|
100
|
+
var hasAccess = /* @__PURE__ */ __name((event) => !NO_ACCESS_STATES.has(event.state), "hasAccess");
|
|
101
|
+
|
|
53
102
|
// ../license-core/src/legal-entity.ts
|
|
54
103
|
var Y_TUNNUS = /^(\d{6,7})-(\d)$/;
|
|
55
104
|
var Y_TUNNUS_WEIGHTS = [
|
|
@@ -70,36 +119,24 @@ var finnishBusinessId = /* @__PURE__ */ __name((input) => {
|
|
|
70
119
|
return Number(check) === expected ? `${digits}-${check}` : null;
|
|
71
120
|
}, "finnishBusinessId");
|
|
72
121
|
var finnishVatId = /* @__PURE__ */ __name((businessId) => `FI${businessId.replace("-", "")}`, "finnishVatId");
|
|
73
|
-
var ANY_COUNTRY = {
|
|
74
|
-
businessId: /* @__PURE__ */ __name((input) => input.trim() || null, "businessId"),
|
|
75
|
-
vatId: /* @__PURE__ */ __name((_businessId, input) => input?.trim() || null, "vatId")
|
|
76
|
-
};
|
|
77
|
-
var COMPANY_IDS_BY_COUNTRY = {
|
|
78
|
-
FI: {
|
|
79
|
-
businessId: finnishBusinessId,
|
|
80
|
-
vatId: /* @__PURE__ */ __name((businessId, input) => {
|
|
81
|
-
const vatId = finnishVatId(businessId);
|
|
82
|
-
return input === null || input.replace(/\s/g, "").toUpperCase() === vatId ? vatId : void 0;
|
|
83
|
-
}, "vatId")
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
122
|
var companyIds = /* @__PURE__ */ __name((country, input) => {
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
};
|
|
92
|
-
const vatId = rules.vatId(businessId, input.vatId ?? null);
|
|
93
|
-
if (vatId === void 0) return {
|
|
94
|
-
invalid: "vatId"
|
|
95
|
-
};
|
|
96
|
-
return {
|
|
123
|
+
const businessId = input.businessId.trim();
|
|
124
|
+
const vatId = input.vatId?.trim() || null;
|
|
125
|
+
const finnish = country.toUpperCase() === "FI" ? finnishBusinessId(businessId) : null;
|
|
126
|
+
if (finnish === null) return {
|
|
97
127
|
businessId,
|
|
98
128
|
vatId
|
|
99
129
|
};
|
|
130
|
+
const derived = finnishVatId(finnish);
|
|
131
|
+
const matches = vatId === null || vatId.replace(/\s/g, "").toUpperCase() === derived;
|
|
132
|
+
return {
|
|
133
|
+
businessId: finnish,
|
|
134
|
+
vatId: matches ? derived : vatId
|
|
135
|
+
};
|
|
100
136
|
}, "companyIds");
|
|
101
137
|
|
|
102
138
|
// ../license-core/src/requestable-products.ts
|
|
139
|
+
var import_zod2 = require("zod");
|
|
103
140
|
var REQUESTABLE_PRODUCTS = [
|
|
104
141
|
"ari",
|
|
105
142
|
"business_account",
|
|
@@ -107,16 +144,32 @@ var REQUESTABLE_PRODUCTS = [
|
|
|
107
144
|
"foodora",
|
|
108
145
|
"bolt",
|
|
109
146
|
"card_payments_pos",
|
|
110
|
-
"card_payments_kiosk"
|
|
147
|
+
"card_payments_kiosk",
|
|
148
|
+
"sevenrooms",
|
|
149
|
+
"tableonline",
|
|
150
|
+
"zenchef",
|
|
151
|
+
"payment_terminal",
|
|
152
|
+
"hardware"
|
|
111
153
|
];
|
|
112
154
|
var STORE_ONLY_PRODUCTS = [
|
|
113
155
|
"foodora",
|
|
114
156
|
"bolt",
|
|
115
157
|
"card_payments_pos",
|
|
116
|
-
"card_payments_kiosk"
|
|
158
|
+
"card_payments_kiosk",
|
|
159
|
+
"sevenrooms",
|
|
160
|
+
"tableonline",
|
|
161
|
+
"zenchef",
|
|
162
|
+
"payment_terminal",
|
|
163
|
+
"hardware"
|
|
117
164
|
];
|
|
118
165
|
var storeOnly = new Set(STORE_ONLY_PRODUCTS);
|
|
119
166
|
var isStoreOnlyProduct = /* @__PURE__ */ __name((product) => storeOnly.has(product), "isStoreOnlyProduct");
|
|
167
|
+
var MAX_DETAILS_BYTES = 1e4;
|
|
168
|
+
var productRequestDetails = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.json()).refine((details) => Buffer.byteLength(JSON.stringify(details)) <= MAX_DETAILS_BYTES, {
|
|
169
|
+
message: `details may be at most ${MAX_DETAILS_BYTES} bytes`
|
|
170
|
+
}).refine((details) => !JSON.stringify(details).includes("\\u0000"), {
|
|
171
|
+
message: "details may not contain the null character"
|
|
172
|
+
});
|
|
120
173
|
|
|
121
174
|
// ../license-core/src/usage-metrics.ts
|
|
122
175
|
var USAGE_METRICS = {
|
|
@@ -265,6 +318,7 @@ var LicenseClient = class {
|
|
|
265
318
|
};
|
|
266
319
|
// Annotate the CommonJS export names for ESM import in node:
|
|
267
320
|
0 && (module.exports = {
|
|
321
|
+
ACCESS_CHANGED,
|
|
268
322
|
LicenseClient,
|
|
269
323
|
PLANS,
|
|
270
324
|
PRODUCTS,
|
|
@@ -272,8 +326,10 @@ var LicenseClient = class {
|
|
|
272
326
|
STORE_ONLY_PRODUCTS,
|
|
273
327
|
USAGE_METRICS,
|
|
274
328
|
USAGE_METRIC_NAMES,
|
|
329
|
+
accessChangedEventSchema,
|
|
275
330
|
companyIds,
|
|
276
331
|
finnishBusinessId,
|
|
277
332
|
finnishVatId,
|
|
333
|
+
hasAccess,
|
|
278
334
|
isStoreOnlyProduct
|
|
279
335
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Munchi's product codes, for code that names one (apps, the dashboard). Codes are immutable and
|
|
3
5
|
* never reused; a catalog may still carry products added later, so this is not the full list.
|
|
@@ -7,6 +9,9 @@ declare const PRODUCTS: {
|
|
|
7
9
|
readonly one: "MUN-ONE";
|
|
8
10
|
readonly kiosk: "MUN-ADD-KIOSK";
|
|
9
11
|
readonly mobileApp: "MUN-ADD-APP";
|
|
12
|
+
readonly munchiOrder: "MUN-ADD-ORDER";
|
|
13
|
+
readonly paymentTerminal: "MUN-ADD-TERMINAL";
|
|
14
|
+
readonly hardware: "MUN-ADD-HARDWARE";
|
|
10
15
|
readonly mobileAppSetup: "MUN-SETUP-APP";
|
|
11
16
|
readonly sms: "MUN-SMS";
|
|
12
17
|
readonly loyaltyOverage: "MUN-OVR-LOYALTY";
|
|
@@ -16,6 +21,47 @@ type KnownProductCode = (typeof PRODUCTS)[keyof typeof PRODUCTS];
|
|
|
16
21
|
declare const PLANS: readonly ["MUN-START", "MUN-ONE"];
|
|
17
22
|
type PlanCode = (typeof PLANS)[number];
|
|
18
23
|
|
|
24
|
+
declare const ACCESS_CHANGED = "license.access_changed";
|
|
25
|
+
/**
|
|
26
|
+
* What a store may use, sent on `ACCESS_TOPIC` whenever it changes (closed, suspended, unpaid,
|
|
27
|
+
* paid again, an add-on bought or removed). Only zod, because the SDK ships it to consumers.
|
|
28
|
+
* Unknown fields are ignored, so fields added later don't break an older consumer.
|
|
29
|
+
*/
|
|
30
|
+
declare const accessChangedEventSchema: z.ZodObject<{
|
|
31
|
+
kind: z.ZodLiteral<"license.access_changed">;
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
venueId: z.ZodNumber;
|
|
34
|
+
locationId: z.ZodString;
|
|
35
|
+
accountId: z.ZodString;
|
|
36
|
+
state: z.ZodEnum<{
|
|
37
|
+
start: "start";
|
|
38
|
+
suspended: "suspended";
|
|
39
|
+
terminated: "terminated";
|
|
40
|
+
canceled: "canceled";
|
|
41
|
+
trial: "trial";
|
|
42
|
+
active: "active";
|
|
43
|
+
grace: "grace";
|
|
44
|
+
}>;
|
|
45
|
+
features: z.ZodArray<z.ZodString>;
|
|
46
|
+
limits: z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodNumber>>;
|
|
47
|
+
suspension: z.ZodNullable<z.ZodObject<{
|
|
48
|
+
reason: z.ZodString;
|
|
49
|
+
mode: z.ZodEnum<{
|
|
50
|
+
read_only: "read_only";
|
|
51
|
+
off: "off";
|
|
52
|
+
}>;
|
|
53
|
+
}, z.core.$strip>>;
|
|
54
|
+
reason: z.ZodString;
|
|
55
|
+
snapshotVersion: z.ZodNumber;
|
|
56
|
+
at: z.ZodISODateTime;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
type AccessChangedEvent = z.infer<typeof accessChangedEventSchema>;
|
|
59
|
+
/**
|
|
60
|
+
* May the store use anything now? False when closed, suspended (an unpaid store too: everything
|
|
61
|
+
* off, owner 2026-11-04) or terminated. A feature is usable when this is true and it is in `features`.
|
|
62
|
+
*/
|
|
63
|
+
declare const hasAccess: (event: Pick<AccessChangedEvent, "state">) => boolean;
|
|
64
|
+
|
|
19
65
|
/**
|
|
20
66
|
* A group's company (legal entity), as the dashboard reads and changes it. One company can be the
|
|
21
67
|
* company of several stores of the same group. Zero-dependency on purpose: the SDK ships it.
|
|
@@ -51,14 +97,16 @@ interface LegalEntityChange {
|
|
|
51
97
|
declare const finnishBusinessId: (input: string) => string | null;
|
|
52
98
|
/** A Finnish VAT ID is always `FI` and the business ID's eight digits. */
|
|
53
99
|
declare const finnishVatId: (businessId: string) => string;
|
|
54
|
-
/** The IDs to store
|
|
55
|
-
|
|
100
|
+
/** The IDs to store. Nothing is refused (owner, 2026-11-01: business IDs aren't validated). */
|
|
101
|
+
interface CompanyIds {
|
|
56
102
|
businessId: string;
|
|
57
103
|
vatId: string | null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The company's IDs as written, trimmed. A Finnish company's well-formed Y-tunnus is written as
|
|
107
|
+
* `NNNNNNN-C`, and its VAT ID is derived when none is given or written like it in another case or
|
|
108
|
+
* spacing.
|
|
109
|
+
*/
|
|
62
110
|
declare const companyIds: (country: string, input: {
|
|
63
111
|
businessId: string;
|
|
64
112
|
vatId?: string | null;
|
|
@@ -69,13 +117,13 @@ declare const companyIds: (country: string, input: {
|
|
|
69
117
|
* (ADR 0022). These are the dashboard's product ids, not catalog codes (`MUN-*`); the mobile app
|
|
70
118
|
* has its own request flow (ADR 0021). A new one is added here first.
|
|
71
119
|
*/
|
|
72
|
-
declare const REQUESTABLE_PRODUCTS: readonly ["ari", "business_account", "capital", "foodora", "bolt", "card_payments_pos", "card_payments_kiosk"];
|
|
120
|
+
declare const REQUESTABLE_PRODUCTS: readonly ["ari", "business_account", "capital", "foodora", "bolt", "card_payments_pos", "card_payments_kiosk", "sevenrooms", "tableonline", "zenchef", "payment_terminal", "hardware"];
|
|
73
121
|
type RequestableProduct = (typeof REQUESTABLE_PRODUCTS)[number];
|
|
74
122
|
/**
|
|
75
|
-
* Delivery apps
|
|
76
|
-
* store asks for them, never the whole group.
|
|
123
|
+
* Delivery apps, card payments at the POS or kiosk, reservation systems and hardware are set up one
|
|
124
|
+
* store at a time, so only a store asks for them, never the whole group.
|
|
77
125
|
*/
|
|
78
|
-
declare const STORE_ONLY_PRODUCTS: readonly ["foodora", "bolt", "card_payments_pos", "card_payments_kiosk"];
|
|
126
|
+
declare const STORE_ONLY_PRODUCTS: readonly ["foodora", "bolt", "card_payments_pos", "card_payments_kiosk", "sevenrooms", "tableonline", "zenchef", "payment_terminal", "hardware"];
|
|
79
127
|
declare const isStoreOnlyProduct: (product: RequestableProduct) => boolean;
|
|
80
128
|
|
|
81
129
|
/**
|
|
@@ -228,4 +276,4 @@ declare class LicenseClient {
|
|
|
228
276
|
private report;
|
|
229
277
|
}
|
|
230
278
|
|
|
231
|
-
export { type CompanyIds, type DeniedEvent, type KnownProductCode, type LegalEntity, type LegalEntityChange, LicenseClient, type LicenseClientOptions, PLANS, PRODUCTS, type PlanCode, REQUESTABLE_PRODUCTS, type RequestableProduct, STORE_ONLY_PRODUCTS, type Snapshot, USAGE_METRICS, USAGE_METRIC_NAMES, type UsageMetric, companyIds, finnishBusinessId, finnishVatId, isStoreOnlyProduct };
|
|
279
|
+
export { ACCESS_CHANGED, type AccessChangedEvent, type CompanyIds, type DeniedEvent, type KnownProductCode, type LegalEntity, type LegalEntityChange, LicenseClient, type LicenseClientOptions, PLANS, PRODUCTS, type PlanCode, REQUESTABLE_PRODUCTS, type RequestableProduct, STORE_ONLY_PRODUCTS, type Snapshot, USAGE_METRICS, USAGE_METRIC_NAMES, type UsageMetric, accessChangedEventSchema, companyIds, finnishBusinessId, finnishVatId, hasAccess, isStoreOnlyProduct };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Munchi's product codes, for code that names one (apps, the dashboard). Codes are immutable and
|
|
3
5
|
* never reused; a catalog may still carry products added later, so this is not the full list.
|
|
@@ -7,6 +9,9 @@ declare const PRODUCTS: {
|
|
|
7
9
|
readonly one: "MUN-ONE";
|
|
8
10
|
readonly kiosk: "MUN-ADD-KIOSK";
|
|
9
11
|
readonly mobileApp: "MUN-ADD-APP";
|
|
12
|
+
readonly munchiOrder: "MUN-ADD-ORDER";
|
|
13
|
+
readonly paymentTerminal: "MUN-ADD-TERMINAL";
|
|
14
|
+
readonly hardware: "MUN-ADD-HARDWARE";
|
|
10
15
|
readonly mobileAppSetup: "MUN-SETUP-APP";
|
|
11
16
|
readonly sms: "MUN-SMS";
|
|
12
17
|
readonly loyaltyOverage: "MUN-OVR-LOYALTY";
|
|
@@ -16,6 +21,47 @@ type KnownProductCode = (typeof PRODUCTS)[keyof typeof PRODUCTS];
|
|
|
16
21
|
declare const PLANS: readonly ["MUN-START", "MUN-ONE"];
|
|
17
22
|
type PlanCode = (typeof PLANS)[number];
|
|
18
23
|
|
|
24
|
+
declare const ACCESS_CHANGED = "license.access_changed";
|
|
25
|
+
/**
|
|
26
|
+
* What a store may use, sent on `ACCESS_TOPIC` whenever it changes (closed, suspended, unpaid,
|
|
27
|
+
* paid again, an add-on bought or removed). Only zod, because the SDK ships it to consumers.
|
|
28
|
+
* Unknown fields are ignored, so fields added later don't break an older consumer.
|
|
29
|
+
*/
|
|
30
|
+
declare const accessChangedEventSchema: z.ZodObject<{
|
|
31
|
+
kind: z.ZodLiteral<"license.access_changed">;
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
venueId: z.ZodNumber;
|
|
34
|
+
locationId: z.ZodString;
|
|
35
|
+
accountId: z.ZodString;
|
|
36
|
+
state: z.ZodEnum<{
|
|
37
|
+
start: "start";
|
|
38
|
+
suspended: "suspended";
|
|
39
|
+
terminated: "terminated";
|
|
40
|
+
canceled: "canceled";
|
|
41
|
+
trial: "trial";
|
|
42
|
+
active: "active";
|
|
43
|
+
grace: "grace";
|
|
44
|
+
}>;
|
|
45
|
+
features: z.ZodArray<z.ZodString>;
|
|
46
|
+
limits: z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodNumber>>;
|
|
47
|
+
suspension: z.ZodNullable<z.ZodObject<{
|
|
48
|
+
reason: z.ZodString;
|
|
49
|
+
mode: z.ZodEnum<{
|
|
50
|
+
read_only: "read_only";
|
|
51
|
+
off: "off";
|
|
52
|
+
}>;
|
|
53
|
+
}, z.core.$strip>>;
|
|
54
|
+
reason: z.ZodString;
|
|
55
|
+
snapshotVersion: z.ZodNumber;
|
|
56
|
+
at: z.ZodISODateTime;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
type AccessChangedEvent = z.infer<typeof accessChangedEventSchema>;
|
|
59
|
+
/**
|
|
60
|
+
* May the store use anything now? False when closed, suspended (an unpaid store too: everything
|
|
61
|
+
* off, owner 2026-11-04) or terminated. A feature is usable when this is true and it is in `features`.
|
|
62
|
+
*/
|
|
63
|
+
declare const hasAccess: (event: Pick<AccessChangedEvent, "state">) => boolean;
|
|
64
|
+
|
|
19
65
|
/**
|
|
20
66
|
* A group's company (legal entity), as the dashboard reads and changes it. One company can be the
|
|
21
67
|
* company of several stores of the same group. Zero-dependency on purpose: the SDK ships it.
|
|
@@ -51,14 +97,16 @@ interface LegalEntityChange {
|
|
|
51
97
|
declare const finnishBusinessId: (input: string) => string | null;
|
|
52
98
|
/** A Finnish VAT ID is always `FI` and the business ID's eight digits. */
|
|
53
99
|
declare const finnishVatId: (businessId: string) => string;
|
|
54
|
-
/** The IDs to store
|
|
55
|
-
|
|
100
|
+
/** The IDs to store. Nothing is refused (owner, 2026-11-01: business IDs aren't validated). */
|
|
101
|
+
interface CompanyIds {
|
|
56
102
|
businessId: string;
|
|
57
103
|
vatId: string | null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The company's IDs as written, trimmed. A Finnish company's well-formed Y-tunnus is written as
|
|
107
|
+
* `NNNNNNN-C`, and its VAT ID is derived when none is given or written like it in another case or
|
|
108
|
+
* spacing.
|
|
109
|
+
*/
|
|
62
110
|
declare const companyIds: (country: string, input: {
|
|
63
111
|
businessId: string;
|
|
64
112
|
vatId?: string | null;
|
|
@@ -69,13 +117,13 @@ declare const companyIds: (country: string, input: {
|
|
|
69
117
|
* (ADR 0022). These are the dashboard's product ids, not catalog codes (`MUN-*`); the mobile app
|
|
70
118
|
* has its own request flow (ADR 0021). A new one is added here first.
|
|
71
119
|
*/
|
|
72
|
-
declare const REQUESTABLE_PRODUCTS: readonly ["ari", "business_account", "capital", "foodora", "bolt", "card_payments_pos", "card_payments_kiosk"];
|
|
120
|
+
declare const REQUESTABLE_PRODUCTS: readonly ["ari", "business_account", "capital", "foodora", "bolt", "card_payments_pos", "card_payments_kiosk", "sevenrooms", "tableonline", "zenchef", "payment_terminal", "hardware"];
|
|
73
121
|
type RequestableProduct = (typeof REQUESTABLE_PRODUCTS)[number];
|
|
74
122
|
/**
|
|
75
|
-
* Delivery apps
|
|
76
|
-
* store asks for them, never the whole group.
|
|
123
|
+
* Delivery apps, card payments at the POS or kiosk, reservation systems and hardware are set up one
|
|
124
|
+
* store at a time, so only a store asks for them, never the whole group.
|
|
77
125
|
*/
|
|
78
|
-
declare const STORE_ONLY_PRODUCTS: readonly ["foodora", "bolt", "card_payments_pos", "card_payments_kiosk"];
|
|
126
|
+
declare const STORE_ONLY_PRODUCTS: readonly ["foodora", "bolt", "card_payments_pos", "card_payments_kiosk", "sevenrooms", "tableonline", "zenchef", "payment_terminal", "hardware"];
|
|
79
127
|
declare const isStoreOnlyProduct: (product: RequestableProduct) => boolean;
|
|
80
128
|
|
|
81
129
|
/**
|
|
@@ -228,4 +276,4 @@ declare class LicenseClient {
|
|
|
228
276
|
private report;
|
|
229
277
|
}
|
|
230
278
|
|
|
231
|
-
export { type CompanyIds, type DeniedEvent, type KnownProductCode, type LegalEntity, type LegalEntityChange, LicenseClient, type LicenseClientOptions, PLANS, PRODUCTS, type PlanCode, REQUESTABLE_PRODUCTS, type RequestableProduct, STORE_ONLY_PRODUCTS, type Snapshot, USAGE_METRICS, USAGE_METRIC_NAMES, type UsageMetric, companyIds, finnishBusinessId, finnishVatId, isStoreOnlyProduct };
|
|
279
|
+
export { ACCESS_CHANGED, type AccessChangedEvent, type CompanyIds, type DeniedEvent, type KnownProductCode, type LegalEntity, type LegalEntityChange, LicenseClient, type LicenseClientOptions, PLANS, PRODUCTS, type PlanCode, REQUESTABLE_PRODUCTS, type RequestableProduct, STORE_ONLY_PRODUCTS, type Snapshot, USAGE_METRICS, USAGE_METRIC_NAMES, type UsageMetric, accessChangedEventSchema, companyIds, finnishBusinessId, finnishVatId, hasAccess, isStoreOnlyProduct };
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,9 @@ var PRODUCTS = {
|
|
|
7
7
|
one: "MUN-ONE",
|
|
8
8
|
kiosk: "MUN-ADD-KIOSK",
|
|
9
9
|
mobileApp: "MUN-ADD-APP",
|
|
10
|
+
munchiOrder: "MUN-ADD-ORDER",
|
|
11
|
+
paymentTerminal: "MUN-ADD-TERMINAL",
|
|
12
|
+
hardware: "MUN-ADD-HARDWARE",
|
|
10
13
|
mobileAppSetup: "MUN-SETUP-APP",
|
|
11
14
|
sms: "MUN-SMS",
|
|
12
15
|
loyaltyOverage: "MUN-OVR-LOYALTY"
|
|
@@ -16,6 +19,49 @@ var PLANS = [
|
|
|
16
19
|
PRODUCTS.one
|
|
17
20
|
];
|
|
18
21
|
|
|
22
|
+
// ../license-core/src/events/access-changed.ts
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
var ACCESS_CHANGED = "license.access_changed";
|
|
25
|
+
var NO_ACCESS_STATES = /* @__PURE__ */ new Set([
|
|
26
|
+
"suspended",
|
|
27
|
+
"terminated",
|
|
28
|
+
"canceled"
|
|
29
|
+
]);
|
|
30
|
+
var accessChangedEventSchema = z.object({
|
|
31
|
+
kind: z.literal(ACCESS_CHANGED),
|
|
32
|
+
/** The event's own id: a redelivered message has the same one. */
|
|
33
|
+
id: z.string().min(1),
|
|
34
|
+
venueId: z.number().int(),
|
|
35
|
+
locationId: z.string().min(1),
|
|
36
|
+
accountId: z.string().min(1),
|
|
37
|
+
state: z.enum([
|
|
38
|
+
"trial",
|
|
39
|
+
"active",
|
|
40
|
+
"grace",
|
|
41
|
+
"start",
|
|
42
|
+
"suspended",
|
|
43
|
+
"terminated",
|
|
44
|
+
"canceled"
|
|
45
|
+
]),
|
|
46
|
+
/** As in the snapshot: empty when the store is closed or switched off. */
|
|
47
|
+
features: z.array(z.string().min(1)),
|
|
48
|
+
/** As in the snapshot: how many of something the store may have; `null` = unlimited. */
|
|
49
|
+
limits: z.record(z.string(), z.number().int().min(0).nullable()),
|
|
50
|
+
suspension: z.object({
|
|
51
|
+
reason: z.string().min(1),
|
|
52
|
+
mode: z.enum([
|
|
53
|
+
"read_only",
|
|
54
|
+
"off"
|
|
55
|
+
])
|
|
56
|
+
}).nullable(),
|
|
57
|
+
/** The audit action that changed it, e.g. `license.close` or `license.payment.suspend`. */
|
|
58
|
+
reason: z.string().min(1),
|
|
59
|
+
/** The snapshot's `v`: a consumer drops a message older than the last one it applied. */
|
|
60
|
+
snapshotVersion: z.number().int().min(0),
|
|
61
|
+
at: z.iso.datetime()
|
|
62
|
+
});
|
|
63
|
+
var hasAccess = /* @__PURE__ */ __name((event) => !NO_ACCESS_STATES.has(event.state), "hasAccess");
|
|
64
|
+
|
|
19
65
|
// ../license-core/src/legal-entity.ts
|
|
20
66
|
var Y_TUNNUS = /^(\d{6,7})-(\d)$/;
|
|
21
67
|
var Y_TUNNUS_WEIGHTS = [
|
|
@@ -36,36 +82,24 @@ var finnishBusinessId = /* @__PURE__ */ __name((input) => {
|
|
|
36
82
|
return Number(check) === expected ? `${digits}-${check}` : null;
|
|
37
83
|
}, "finnishBusinessId");
|
|
38
84
|
var finnishVatId = /* @__PURE__ */ __name((businessId) => `FI${businessId.replace("-", "")}`, "finnishVatId");
|
|
39
|
-
var ANY_COUNTRY = {
|
|
40
|
-
businessId: /* @__PURE__ */ __name((input) => input.trim() || null, "businessId"),
|
|
41
|
-
vatId: /* @__PURE__ */ __name((_businessId, input) => input?.trim() || null, "vatId")
|
|
42
|
-
};
|
|
43
|
-
var COMPANY_IDS_BY_COUNTRY = {
|
|
44
|
-
FI: {
|
|
45
|
-
businessId: finnishBusinessId,
|
|
46
|
-
vatId: /* @__PURE__ */ __name((businessId, input) => {
|
|
47
|
-
const vatId = finnishVatId(businessId);
|
|
48
|
-
return input === null || input.replace(/\s/g, "").toUpperCase() === vatId ? vatId : void 0;
|
|
49
|
-
}, "vatId")
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
85
|
var companyIds = /* @__PURE__ */ __name((country, input) => {
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
};
|
|
58
|
-
const vatId = rules.vatId(businessId, input.vatId ?? null);
|
|
59
|
-
if (vatId === void 0) return {
|
|
60
|
-
invalid: "vatId"
|
|
61
|
-
};
|
|
62
|
-
return {
|
|
86
|
+
const businessId = input.businessId.trim();
|
|
87
|
+
const vatId = input.vatId?.trim() || null;
|
|
88
|
+
const finnish = country.toUpperCase() === "FI" ? finnishBusinessId(businessId) : null;
|
|
89
|
+
if (finnish === null) return {
|
|
63
90
|
businessId,
|
|
64
91
|
vatId
|
|
65
92
|
};
|
|
93
|
+
const derived = finnishVatId(finnish);
|
|
94
|
+
const matches = vatId === null || vatId.replace(/\s/g, "").toUpperCase() === derived;
|
|
95
|
+
return {
|
|
96
|
+
businessId: finnish,
|
|
97
|
+
vatId: matches ? derived : vatId
|
|
98
|
+
};
|
|
66
99
|
}, "companyIds");
|
|
67
100
|
|
|
68
101
|
// ../license-core/src/requestable-products.ts
|
|
102
|
+
import { z as z2 } from "zod";
|
|
69
103
|
var REQUESTABLE_PRODUCTS = [
|
|
70
104
|
"ari",
|
|
71
105
|
"business_account",
|
|
@@ -73,16 +107,32 @@ var REQUESTABLE_PRODUCTS = [
|
|
|
73
107
|
"foodora",
|
|
74
108
|
"bolt",
|
|
75
109
|
"card_payments_pos",
|
|
76
|
-
"card_payments_kiosk"
|
|
110
|
+
"card_payments_kiosk",
|
|
111
|
+
"sevenrooms",
|
|
112
|
+
"tableonline",
|
|
113
|
+
"zenchef",
|
|
114
|
+
"payment_terminal",
|
|
115
|
+
"hardware"
|
|
77
116
|
];
|
|
78
117
|
var STORE_ONLY_PRODUCTS = [
|
|
79
118
|
"foodora",
|
|
80
119
|
"bolt",
|
|
81
120
|
"card_payments_pos",
|
|
82
|
-
"card_payments_kiosk"
|
|
121
|
+
"card_payments_kiosk",
|
|
122
|
+
"sevenrooms",
|
|
123
|
+
"tableonline",
|
|
124
|
+
"zenchef",
|
|
125
|
+
"payment_terminal",
|
|
126
|
+
"hardware"
|
|
83
127
|
];
|
|
84
128
|
var storeOnly = new Set(STORE_ONLY_PRODUCTS);
|
|
85
129
|
var isStoreOnlyProduct = /* @__PURE__ */ __name((product) => storeOnly.has(product), "isStoreOnlyProduct");
|
|
130
|
+
var MAX_DETAILS_BYTES = 1e4;
|
|
131
|
+
var productRequestDetails = z2.record(z2.string(), z2.json()).refine((details) => Buffer.byteLength(JSON.stringify(details)) <= MAX_DETAILS_BYTES, {
|
|
132
|
+
message: `details may be at most ${MAX_DETAILS_BYTES} bytes`
|
|
133
|
+
}).refine((details) => !JSON.stringify(details).includes("\\u0000"), {
|
|
134
|
+
message: "details may not contain the null character"
|
|
135
|
+
});
|
|
86
136
|
|
|
87
137
|
// ../license-core/src/usage-metrics.ts
|
|
88
138
|
var USAGE_METRICS = {
|
|
@@ -230,6 +280,7 @@ var LicenseClient = class {
|
|
|
230
280
|
}
|
|
231
281
|
};
|
|
232
282
|
export {
|
|
283
|
+
ACCESS_CHANGED,
|
|
233
284
|
LicenseClient,
|
|
234
285
|
PLANS,
|
|
235
286
|
PRODUCTS,
|
|
@@ -237,8 +288,10 @@ export {
|
|
|
237
288
|
STORE_ONLY_PRODUCTS,
|
|
238
289
|
USAGE_METRICS,
|
|
239
290
|
USAGE_METRIC_NAMES,
|
|
291
|
+
accessChangedEventSchema,
|
|
240
292
|
companyIds,
|
|
241
293
|
finnishBusinessId,
|
|
242
294
|
finnishVatId,
|
|
295
|
+
hasAccess,
|
|
243
296
|
isStoreOnlyProduct
|
|
244
297
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@munchi_oy/license-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Client for the Munchi license service: cached entitlements and feature checks per venue",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
"url": "git+https://github.com/gomunchi/one-license.git",
|
|
34
34
|
"directory": "packages/license-sdk"
|
|
35
35
|
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"zod": "4.6.7"
|
|
38
|
+
},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"@munchi_oy/license-core": "0.1.0",
|
|
38
41
|
"@swc/core": "1.16.12",
|