@munchi_oy/license-sdk 0.1.0 → 0.2.1

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 CHANGED
@@ -13,7 +13,8 @@ const license = new LicenseClient({
13
13
  onError: (e) => logger.error(e, "license service unreachable"),
14
14
  });
15
15
 
16
- if (await license.can(venueId, "kiosk")) { /* … */ }
16
+ if (await license.can(venueId, "kiosk")) { /* may use it now */ }
17
+ if (await license.canView(venueId, "reports")) { /* may still see its data (read-only suspension) */ }
17
18
  const kiosks = await license.limit(venueId, "kiosks"); // number, null = unlimited
18
19
  const snapshot = await license.entitlements(venueId); // full snapshot, null = unknown venue
19
20
  ```
@@ -22,8 +23,10 @@ const snapshot = await license.entitlements(venueId); // full snapshot, null = u
22
23
  |---|---|
23
24
  | Cache per venue, then revalidate with ETag (usually a 304) | `cacheTtlMs` 60 s, and always when the snapshot's `validUntil` passes |
24
25
  | Service unreachable | keeps the last snapshot for `maxStaleMs` (24 h); with nothing cached, `can()` allows |
26
+ | Venue unknown to the license service | allows, and calls `onDenied` with `unknown_venue` (never blocks a venue we forgot to set up) |
27
+ | Suspended | `can()` is false; `canView()` stays true for a `read_only` suspension, false for `off` |
25
28
  | Request timeout / retries | `timeoutMs` 2 s, one retry on network errors and 5xx |
26
29
  | Concurrent calls for one venue | share one request |
27
- | `mode: "enforce"` | `can()` returns `false` for features the venue doesn't have |
30
+ | `mode: "enforce"` | `can()` returns `false` for features the venue doesn't have, or while it is suspended |
28
31
 
29
32
  Call `invalidate(venueId)` when you receive a `license.changed` event.
package/dist/index.cjs CHANGED
@@ -57,16 +57,40 @@ var LicenseClient = class {
57
57
  if (cached && Date.now() - cached.checkedAt < this.options.maxStaleMs) return cached.snapshot;
58
58
  return void 0;
59
59
  }
60
- /** Never blocks a venue because the license service is down: unknown state allows. */
60
+ /**
61
+ * May the venue use the feature now (create orders, take payments…)? False while suspended.
62
+ * Never blocks because of us: when the service is down, or doesn't know the venue yet
63
+ * (reported through `onDenied` as `unknown_venue`), it allows.
64
+ */
61
65
  async can(venueId, feature) {
66
+ return this.check(venueId, feature, false);
67
+ }
68
+ /**
69
+ * May the venue still see the feature's existing data? Like `can()`, but also true during a
70
+ * `read_only` suspension, when nothing new is allowed but history stays visible.
71
+ */
72
+ async canView(venueId, feature) {
73
+ return this.check(venueId, feature, true);
74
+ }
75
+ async check(venueId, feature, view) {
62
76
  const snapshot = await this.entitlements(venueId);
63
77
  if (snapshot === void 0) return true;
64
- const allowed = snapshot?.features.includes(feature) ?? false;
65
- if (allowed) return true;
78
+ if (snapshot === null) {
79
+ this.options.onDenied?.({
80
+ venueId,
81
+ feature,
82
+ reason: "unknown_venue"
83
+ });
84
+ return true;
85
+ }
86
+ const readOnly = snapshot.suspension?.mode === "read_only";
87
+ const active = snapshot.state !== "suspended" || view && readOnly;
88
+ const licensed = snapshot.features.includes(feature);
89
+ if (licensed && active) return true;
66
90
  this.options.onDenied?.({
67
91
  venueId,
68
92
  feature,
69
- reason: snapshot === null ? "unknown_venue" : "not_licensed"
93
+ reason: licensed ? "suspended" : "not_licensed"
70
94
  });
71
95
  return this.options.mode === "report";
72
96
  }
package/dist/index.d.cts CHANGED
@@ -1,15 +1,5 @@
1
1
  type ProductCode = string;
2
2
  type FeatureCode = string;
3
- interface Rate {
4
- bps: number;
5
- fixedMinor: number;
6
- }
7
- interface Rates {
8
- pos: Rate;
9
- online: Rate;
10
- kiosk: Rate;
11
- includeTips: boolean;
12
- }
13
3
 
14
4
  type LicenseState = "trial" | "active" | "grace" | "start" | "suspended" | "terminated" | "canceled";
15
5
  type PaymentMethodStatus = "ok" | "missing" | "failing";
@@ -40,6 +30,29 @@ interface SnapshotItem {
40
30
  priceSource: PriceSource;
41
31
  }
42
32
  type RateSource = "catalog" | "contract" | "license";
33
+ interface ChannelRate {
34
+ bps: number;
35
+ fixedMinor: number;
36
+ source: RateSource;
37
+ }
38
+ type IsvSource = "calculated" | "manual";
39
+ interface Isv {
40
+ bps: number;
41
+ fixedMinor: number;
42
+ source: IsvSource;
43
+ }
44
+ /** A card-present channel: the fee the customer pays and Munchi's share of it on Viva. */
45
+ interface CardChannelRate extends ChannelRate {
46
+ /** `null` when it can't be calculated: no Viva cost in the catalog, or a fee below it. */
47
+ isv: Isv | null;
48
+ }
49
+ interface SnapshotRates {
50
+ /** `null` when card payments at the venue don't go through Munchi (`paymentsMode: "external"`). */
51
+ pos: CardChannelRate | null;
52
+ kiosk: CardChannelRate | null;
53
+ online: ChannelRate;
54
+ includeTips: boolean;
55
+ }
43
56
  interface Snapshot {
44
57
  schemaVersion: number;
45
58
  v: number;
@@ -54,9 +67,7 @@ interface Snapshot {
54
67
  items: SnapshotItem[];
55
68
  features: FeatureCode[];
56
69
  limits: Record<string, number | null>;
57
- rates: Rates & {
58
- source: RateSource;
59
- };
70
+ rates: SnapshotRates;
60
71
  settings: Record<string, boolean>;
61
72
  usage: Record<string, SnapshotUsage>;
62
73
  paymentMethod: PaymentMethodStatus;
@@ -89,7 +100,7 @@ interface LicenseClientOptions {
89
100
  interface DeniedEvent {
90
101
  venueId: number;
91
102
  feature: string;
92
- reason: "not_licensed" | "unknown_venue";
103
+ reason: "not_licensed" | "suspended" | "unknown_venue";
93
104
  }
94
105
  declare class LicenseClient {
95
106
  private readonly cache;
@@ -101,8 +112,18 @@ declare class LicenseClient {
101
112
  * `undefined` means the service couldn't be reached and nothing usable is cached.
102
113
  */
103
114
  entitlements(venueId: number): Promise<Snapshot | null | undefined>;
104
- /** Never blocks a venue because the license service is down: unknown state allows. */
115
+ /**
116
+ * May the venue use the feature now (create orders, take payments…)? False while suspended.
117
+ * Never blocks because of us: when the service is down, or doesn't know the venue yet
118
+ * (reported through `onDenied` as `unknown_venue`), it allows.
119
+ */
105
120
  can(venueId: number, feature: string): Promise<boolean>;
121
+ /**
122
+ * May the venue still see the feature's existing data? Like `can()`, but also true during a
123
+ * `read_only` suspension, when nothing new is allowed but history stays visible.
124
+ */
125
+ canView(venueId: number, feature: string): Promise<boolean>;
126
+ private check;
106
127
  /** `null` = unlimited, `undefined` = no such limit or no snapshot. */
107
128
  limit(venueId: number, key: string): Promise<number | null | undefined>;
108
129
  /** Drop the cached snapshot, e.g. after a `license.changed` event. */
package/dist/index.d.ts CHANGED
@@ -1,15 +1,5 @@
1
1
  type ProductCode = string;
2
2
  type FeatureCode = string;
3
- interface Rate {
4
- bps: number;
5
- fixedMinor: number;
6
- }
7
- interface Rates {
8
- pos: Rate;
9
- online: Rate;
10
- kiosk: Rate;
11
- includeTips: boolean;
12
- }
13
3
 
14
4
  type LicenseState = "trial" | "active" | "grace" | "start" | "suspended" | "terminated" | "canceled";
15
5
  type PaymentMethodStatus = "ok" | "missing" | "failing";
@@ -40,6 +30,29 @@ interface SnapshotItem {
40
30
  priceSource: PriceSource;
41
31
  }
42
32
  type RateSource = "catalog" | "contract" | "license";
33
+ interface ChannelRate {
34
+ bps: number;
35
+ fixedMinor: number;
36
+ source: RateSource;
37
+ }
38
+ type IsvSource = "calculated" | "manual";
39
+ interface Isv {
40
+ bps: number;
41
+ fixedMinor: number;
42
+ source: IsvSource;
43
+ }
44
+ /** A card-present channel: the fee the customer pays and Munchi's share of it on Viva. */
45
+ interface CardChannelRate extends ChannelRate {
46
+ /** `null` when it can't be calculated: no Viva cost in the catalog, or a fee below it. */
47
+ isv: Isv | null;
48
+ }
49
+ interface SnapshotRates {
50
+ /** `null` when card payments at the venue don't go through Munchi (`paymentsMode: "external"`). */
51
+ pos: CardChannelRate | null;
52
+ kiosk: CardChannelRate | null;
53
+ online: ChannelRate;
54
+ includeTips: boolean;
55
+ }
43
56
  interface Snapshot {
44
57
  schemaVersion: number;
45
58
  v: number;
@@ -54,9 +67,7 @@ interface Snapshot {
54
67
  items: SnapshotItem[];
55
68
  features: FeatureCode[];
56
69
  limits: Record<string, number | null>;
57
- rates: Rates & {
58
- source: RateSource;
59
- };
70
+ rates: SnapshotRates;
60
71
  settings: Record<string, boolean>;
61
72
  usage: Record<string, SnapshotUsage>;
62
73
  paymentMethod: PaymentMethodStatus;
@@ -89,7 +100,7 @@ interface LicenseClientOptions {
89
100
  interface DeniedEvent {
90
101
  venueId: number;
91
102
  feature: string;
92
- reason: "not_licensed" | "unknown_venue";
103
+ reason: "not_licensed" | "suspended" | "unknown_venue";
93
104
  }
94
105
  declare class LicenseClient {
95
106
  private readonly cache;
@@ -101,8 +112,18 @@ declare class LicenseClient {
101
112
  * `undefined` means the service couldn't be reached and nothing usable is cached.
102
113
  */
103
114
  entitlements(venueId: number): Promise<Snapshot | null | undefined>;
104
- /** Never blocks a venue because the license service is down: unknown state allows. */
115
+ /**
116
+ * May the venue use the feature now (create orders, take payments…)? False while suspended.
117
+ * Never blocks because of us: when the service is down, or doesn't know the venue yet
118
+ * (reported through `onDenied` as `unknown_venue`), it allows.
119
+ */
105
120
  can(venueId: number, feature: string): Promise<boolean>;
121
+ /**
122
+ * May the venue still see the feature's existing data? Like `can()`, but also true during a
123
+ * `read_only` suspension, when nothing new is allowed but history stays visible.
124
+ */
125
+ canView(venueId: number, feature: string): Promise<boolean>;
126
+ private check;
106
127
  /** `null` = unlimited, `undefined` = no such limit or no snapshot. */
107
128
  limit(venueId: number, key: string): Promise<number | null | undefined>;
108
129
  /** Drop the cached snapshot, e.g. after a `license.changed` event. */
package/dist/index.js CHANGED
@@ -33,16 +33,40 @@ var LicenseClient = class {
33
33
  if (cached && Date.now() - cached.checkedAt < this.options.maxStaleMs) return cached.snapshot;
34
34
  return void 0;
35
35
  }
36
- /** Never blocks a venue because the license service is down: unknown state allows. */
36
+ /**
37
+ * May the venue use the feature now (create orders, take payments…)? False while suspended.
38
+ * Never blocks because of us: when the service is down, or doesn't know the venue yet
39
+ * (reported through `onDenied` as `unknown_venue`), it allows.
40
+ */
37
41
  async can(venueId, feature) {
42
+ return this.check(venueId, feature, false);
43
+ }
44
+ /**
45
+ * May the venue still see the feature's existing data? Like `can()`, but also true during a
46
+ * `read_only` suspension, when nothing new is allowed but history stays visible.
47
+ */
48
+ async canView(venueId, feature) {
49
+ return this.check(venueId, feature, true);
50
+ }
51
+ async check(venueId, feature, view) {
38
52
  const snapshot = await this.entitlements(venueId);
39
53
  if (snapshot === void 0) return true;
40
- const allowed = snapshot?.features.includes(feature) ?? false;
41
- if (allowed) return true;
54
+ if (snapshot === null) {
55
+ this.options.onDenied?.({
56
+ venueId,
57
+ feature,
58
+ reason: "unknown_venue"
59
+ });
60
+ return true;
61
+ }
62
+ const readOnly = snapshot.suspension?.mode === "read_only";
63
+ const active = snapshot.state !== "suspended" || view && readOnly;
64
+ const licensed = snapshot.features.includes(feature);
65
+ if (licensed && active) return true;
42
66
  this.options.onDenied?.({
43
67
  venueId,
44
68
  feature,
45
- reason: snapshot === null ? "unknown_venue" : "not_licensed"
69
+ reason: licensed ? "suspended" : "not_licensed"
46
70
  });
47
71
  return this.options.mode === "report";
48
72
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@munchi_oy/license-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
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",