order3000 0.15.0 → 0.17.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/dist/client.mjs CHANGED
@@ -32,6 +32,24 @@ class TokenManager {
32
32
  }
33
33
  return this.defaultVenueId;
34
34
  }
35
+ /**
36
+ * Async counterpart to {@link getVenueId}. Fetches the token if it
37
+ * hasn't been loaded yet (which is what populates `defaultVenueId` via
38
+ * `setDefaultVenueId`), then returns the venue id.
39
+ *
40
+ * Prefer this over `getVenueId()` from request paths — a cold SDK
41
+ * instance will otherwise synchronously throw "No venue ID available"
42
+ * before the token has had a chance to load.
43
+ */
44
+ async getVenueIdAsync() {
45
+ if (this.userVenueId) {
46
+ return this.userVenueId;
47
+ }
48
+ if (!this.defaultVenueId) {
49
+ await this.getToken();
50
+ }
51
+ return this.getVenueId();
52
+ }
35
53
  async refreshToken() {
36
54
  if (this.refreshPromise) return this.refreshPromise;
37
55
  this.refreshPromise = fetch(this.tokenEndpoint, {
@@ -94,15 +112,71 @@ class BaseResource {
94
112
  "Content-Type": "application/json"
95
113
  },
96
114
  body: data ? JSON.stringify(data) : void 0,
115
+ cache: "no-store",
97
116
  signal: AbortSignal.timeout(36e4)
98
117
  });
99
118
  if (!response.ok) {
100
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
119
+ let body = "";
120
+ try {
121
+ body = await response.text();
122
+ } catch {
123
+ }
124
+ throw new Error(
125
+ `HTTP ${response.status}: ${response.statusText}${body ? ` \u2014 ${body}` : ""}`
126
+ );
101
127
  }
102
128
  return response.json();
103
129
  }
104
130
  }
105
131
 
132
+ class AreasResource extends BaseResource {
133
+ constructor(tokenManager, baseUrl) {
134
+ super(tokenManager, baseUrl, "/areas");
135
+ }
136
+ /**
137
+ * List bookable + public areas for the venue.
138
+ */
139
+ async list(options) {
140
+ const queryString = this.buildExpandQuery(options?.expand);
141
+ return this.request("GET", queryString);
142
+ }
143
+ /**
144
+ * Fetch a single area by id or accessor.
145
+ */
146
+ async getByAccessor(accessor, options) {
147
+ const queryString = this.buildExpandQuery(options?.expand);
148
+ return this.request("GET", `/${accessor}${queryString}`);
149
+ }
150
+ /**
151
+ * List occupied date/time windows for an area between `from` and `to`.
152
+ */
153
+ async getAvailability(accessor, options) {
154
+ const params = new URLSearchParams();
155
+ if (options?.from) params.set("from", options.from);
156
+ if (options?.to) params.set("to", options.to);
157
+ const qs = params.toString();
158
+ const query = qs ? `?${qs}` : "";
159
+ return this.request(
160
+ "GET",
161
+ `/${accessor}/availability${query}`
162
+ );
163
+ }
164
+ /**
165
+ * Submit a public booking request for an area.
166
+ */
167
+ async submitBookingRequest(accessor, payload) {
168
+ return this.request(
169
+ "POST",
170
+ `/${accessor}/booking-requests`,
171
+ payload
172
+ );
173
+ }
174
+ buildExpandQuery(expand) {
175
+ if (!expand?.length) return "";
176
+ return `?expand=${expand.join(",")}`;
177
+ }
178
+ }
179
+
106
180
  class BasketsResource extends BaseResource {
107
181
  constructor(tokenManager, baseUrl) {
108
182
  super(tokenManager, baseUrl, "/baskets");
@@ -144,6 +218,51 @@ class BasketsResource extends BaseResource {
144
218
  `/items/${basketItemId}?${params.toString()}`
145
219
  );
146
220
  }
221
+ /**
222
+ * Canonical totals for a basket. Clients read every displayed monetary
223
+ * figure (effective total, deposit credit, cash refund) from this
224
+ * endpoint — they never multiply deposit counts by amounts locally.
225
+ *
226
+ * Pass `declaredReturns` as references to depositware templates plus a
227
+ * count. order3000 refuses deposit money amounts on input.
228
+ */
229
+ async getTotals(basketId, body = {}) {
230
+ return this.request(
231
+ "POST",
232
+ `/${basketId}/totals`,
233
+ { declaredReturns: body.declaredReturns ?? [] }
234
+ );
235
+ }
236
+ /**
237
+ * Mark every active basket for the session as `abandoned`.
238
+ *
239
+ * Used by thamarai's "Neu starten" flow: after a non-team order
240
+ * has been placed (its basket converted), the user might want to
241
+ * start a fresh order. A subsequent `add()` call would otherwise
242
+ * reuse any leftover active basket on the same session — which
243
+ * can happen if a prior compose flow left an active row behind.
244
+ * Calling `abandon()` first guarantees the next `add()` inserts a
245
+ * brand-new active basket with exactly one item.
246
+ */
247
+ async abandon(sessionId) {
248
+ return this.request("POST", "/abandon", {
249
+ sessionId
250
+ });
251
+ }
252
+ /**
253
+ * Persist per-template declared-return counts onto the basket.
254
+ *
255
+ * Used by team-order participants so the Lead's breakdown popover
256
+ * sees every member's intent in real time (the standard
257
+ * `useTeamOrderRealtime` postgres-changes subscription on
258
+ * `baskets` then propagates the change to the team).
259
+ *
260
+ * Server validates `sessionId === basket.session_id` — only the
261
+ * basket's owner can write to its declared returns.
262
+ */
263
+ async setDeclaredReturns(basketId, body) {
264
+ return this.request("POST", `/${basketId}/declared-returns`, body);
265
+ }
147
266
  }
148
267
 
149
268
  class EventsResource extends BaseResource {
@@ -264,6 +383,18 @@ class OrdersResource extends BaseResource {
264
383
  constructor(tokenManager, baseUrl) {
265
384
  super(tokenManager, baseUrl, "/orders");
266
385
  }
386
+ /**
387
+ * Most recent non-team order for the guest session, with captured
388
+ * basket items + totals + declared returns inlined. `null` when the
389
+ * session has no qualifying order yet (fresh session, or only team
390
+ * orders which go through `teamOrders` instead).
391
+ */
392
+ async getLatest(sessionId) {
393
+ return this.request(
394
+ "GET",
395
+ `/latest?sessionId=${encodeURIComponent(sessionId)}`
396
+ );
397
+ }
267
398
  /**
268
399
  * Place a new order.
269
400
  *
@@ -288,6 +419,14 @@ class OrdersResource extends BaseResource {
288
419
  async confirm(orderId, request) {
289
420
  return this.request("POST", `/${orderId}/confirm`, request);
290
421
  }
422
+ /**
423
+ * Record an additional cash handover amount for a team-order participant
424
+ * who has added items after their initial cash payment. No Stripe
425
+ * interaction — this updates the handover total the Lead will collect.
426
+ */
427
+ async addCashDelta(request) {
428
+ return this.request("POST", "/cash-delta", request);
429
+ }
291
430
  }
292
431
 
293
432
  class PaymentIntentsResource extends BaseResource {
@@ -303,6 +442,24 @@ class PaymentIntentsResource extends BaseResource {
303
442
  async create(request) {
304
443
  return this.request("POST", "", request);
305
444
  }
445
+ /**
446
+ * Create a delta PaymentIntent for a team-order participant who added
447
+ * items after paying. Reuses the saved card from the initial auth so the
448
+ * client only needs one tap (unless Stripe triggers 3DS).
449
+ */
450
+ async createDelta(request) {
451
+ return this.request("POST", "/delta", request);
452
+ }
453
+ /**
454
+ * Release part of a team-order participant's existing Stripe authorisation
455
+ * when they remove items from their basket. Walks payments newest-first,
456
+ * cancelling fully-consumed PIs and `update({ amount })`-ing the last
457
+ * partially-trimmed one. No client-side confirmation — lowering a
458
+ * `requires_capture` PI's amount is a single server round-trip.
459
+ */
460
+ async reduceDelta(request) {
461
+ return this.request("POST", "/delta-reduce", request);
462
+ }
306
463
  }
307
464
 
308
465
  class ReservationsResource extends BaseResource {
@@ -391,6 +548,205 @@ class SubscriptionsResource extends BaseResource {
391
548
  }
392
549
  }
393
550
 
551
+ const paymentMethodEnum = {
552
+ enumValues: ["card", "cash", "voucher"]
553
+ };
554
+ const fulfillmentTypeEnum = {
555
+ enumValues: ["pickup", "delivery"]
556
+ };
557
+ const teamOrderStatusEnum = {
558
+ enumValues: [
559
+ "draft",
560
+ "collecting",
561
+ "locked",
562
+ "placed",
563
+ "ready",
564
+ "completed",
565
+ "cancelled"
566
+ ]
567
+ };
568
+ const participantStatusEnum = {
569
+ enumValues: [
570
+ "browsing",
571
+ "authorized",
572
+ "cash_pending",
573
+ "placed",
574
+ "completed",
575
+ "cancelled"
576
+ ]
577
+ };
578
+ class TeamOrdersResource {
579
+ constructor(tokenManager, baseUrl) {
580
+ this.tokenManager = tokenManager;
581
+ this.baseUrl = baseUrl;
582
+ }
583
+ async request(method, url, data) {
584
+ const token = await this.tokenManager.getToken();
585
+ const response = await fetch(url, {
586
+ method,
587
+ headers: {
588
+ Authorization: `Bearer ${token}`,
589
+ "Content-Type": "application/json"
590
+ },
591
+ body: data ? JSON.stringify(data) : void 0,
592
+ // Bypass Next.js fetch cache — these are live reads/writes, never
593
+ // safe to memoise at the SDK layer.
594
+ cache: "no-store",
595
+ signal: AbortSignal.timeout(36e4)
596
+ });
597
+ if (!response.ok) {
598
+ let body = "";
599
+ try {
600
+ body = await response.text();
601
+ } catch {
602
+ }
603
+ throw new Error(
604
+ `HTTP ${response.status}: ${response.statusText}${body ? ` \u2014 ${body}` : ""}`
605
+ );
606
+ }
607
+ return response.json();
608
+ }
609
+ /**
610
+ * Lead creates a team order from their active basket.
611
+ * Returns the unguessable `publicId` the lead shares with coworkers.
612
+ */
613
+ async create(request) {
614
+ const venueId = await this.tokenManager.getVenueIdAsync();
615
+ return this.request(
616
+ "POST",
617
+ `${this.baseUrl}/venues/${venueId}/team-orders`,
618
+ request
619
+ );
620
+ }
621
+ /**
622
+ * Public read by publicId. Pass the caller's sessionId so the response
623
+ * can set `isLead` correctly for UI gating.
624
+ */
625
+ async get(publicId, sessionId) {
626
+ const query = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
627
+ return this.request(
628
+ "GET",
629
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}${query}`
630
+ );
631
+ }
632
+ /**
633
+ * Participant joins a team order. Idempotent: safe to call on every page
634
+ * mount of the shared URL.
635
+ */
636
+ async join(publicId, request) {
637
+ return this.request(
638
+ "POST",
639
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/join`,
640
+ request
641
+ );
642
+ }
643
+ /**
644
+ * Lead submits the team order. Drops basket-only participants, captures
645
+ * every authorised Stripe PaymentIntent atomically, flips child orders to
646
+ * `pending`. The "Submit anyways" UX is a client-side confirmation dialog
647
+ * that calls the same endpoint — no server variant exists.
648
+ */
649
+ async submit(publicId, request) {
650
+ return this.request(
651
+ "POST",
652
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/submit`,
653
+ request
654
+ );
655
+ }
656
+ /**
657
+ * Lead cancels pre-placement. Voids every authorised Stripe
658
+ * PaymentIntent (no refunds needed — nothing was captured).
659
+ */
660
+ async cancel(publicId, request) {
661
+ return this.request(
662
+ "POST",
663
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/cancel`,
664
+ request
665
+ );
666
+ }
667
+ /**
668
+ * Lead pushes the deadline. A `locked` team flips back to `collecting`
669
+ * when the new deadline is in the future.
670
+ */
671
+ async extendDeadline(publicId, request) {
672
+ return this.request(
673
+ "POST",
674
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/extend-deadline`,
675
+ request
676
+ );
677
+ }
678
+ /**
679
+ * Lead commits (or changes) the shared pickup/delivery time. Required
680
+ * before `submit` — which backfills each child order's `fulfillmentAt`
681
+ * from this value at capture time.
682
+ */
683
+ async setFulfillmentAt(publicId, request) {
684
+ return this.request(
685
+ "POST",
686
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/set-fulfillment-at`,
687
+ request
688
+ );
689
+ }
690
+ /**
691
+ * Lead sets the free-form "where we'll eat" text on the team order.
692
+ * Broadcast to participants via the standard realtime refetch path.
693
+ */
694
+ async setDiningLocation(publicId, request) {
695
+ return this.request(
696
+ "POST",
697
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/set-dining-location`,
698
+ request
699
+ );
700
+ }
701
+ /**
702
+ * Lead sets when the team will start eating. Client sends HH:MM; server
703
+ * computes the delta against `fulfillmentAt` and stores it as
704
+ * `dining_offset_minutes` so the intent survives any later pickup change.
705
+ */
706
+ async setDiningTime(publicId, request) {
707
+ return this.request(
708
+ "POST",
709
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/set-dining-time`,
710
+ request
711
+ );
712
+ }
713
+ /**
714
+ * Any participant proposes an auto-extend when their local clock sees
715
+ * that `fulfillmentAt` has passed while the team order is still
716
+ * collecting. Idempotent on the server side: concurrent callers race
717
+ * but only one write lands.
718
+ */
719
+ async autoExtendFulfillmentIfExpired(publicId, request) {
720
+ return this.request(
721
+ "POST",
722
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/auto-extend-fulfillment-if-expired`,
723
+ request
724
+ );
725
+ }
726
+ /**
727
+ * Lead flips pickup ↔ delivery. Pass `deliveryAddress` when switching
728
+ * TO delivery if no address is already set on the team order.
729
+ */
730
+ async switchFulfillment(publicId, request) {
731
+ return this.request(
732
+ "POST",
733
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/switch-fulfillment`,
734
+ request
735
+ );
736
+ }
737
+ /**
738
+ * Lead replaces the delivery address (inserts a new row in the shared
739
+ * addresses table with type='PHYSICAL' and links it to the team order).
740
+ */
741
+ async updateDeliveryAddress(publicId, request) {
742
+ return this.request(
743
+ "POST",
744
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/update-delivery-address`,
745
+ request
746
+ );
747
+ }
748
+ }
749
+
394
750
  var __defProp$2 = Object.defineProperty;
395
751
  var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
396
752
  var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -715,6 +1071,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
715
1071
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
716
1072
  class Order3000Client {
717
1073
  constructor(config) {
1074
+ __publicField(this, "areas");
718
1075
  __publicField(this, "baskets");
719
1076
  __publicField(this, "events");
720
1077
  __publicField(this, "menus");
@@ -722,6 +1079,7 @@ class Order3000Client {
722
1079
  __publicField(this, "paymentIntents");
723
1080
  __publicField(this, "reservations");
724
1081
  __publicField(this, "subscriptions");
1082
+ __publicField(this, "teamOrders");
725
1083
  __publicField(this, "realtime");
726
1084
  __publicField(this, "baseUrl");
727
1085
  __publicField(this, "tokenManager");
@@ -733,6 +1091,7 @@ class Order3000Client {
733
1091
  tokenUrl,
734
1092
  config.venueId
735
1093
  );
1094
+ this.areas = new AreasResource(this.tokenManager, this.baseUrl);
736
1095
  this.baskets = new BasketsResource(this.tokenManager, this.baseUrl);
737
1096
  this.events = new EventsResource(this.tokenManager, this.baseUrl);
738
1097
  this.menus = new MenusResource(this.tokenManager, this.baseUrl);
@@ -749,8 +1108,9 @@ class Order3000Client {
749
1108
  this.tokenManager,
750
1109
  this.baseUrl
751
1110
  );
1111
+ this.teamOrders = new TeamOrdersResource(this.tokenManager, this.baseUrl);
752
1112
  this.realtime = new RealtimeManager(this.tokenManager, this.baseUrl);
753
1113
  }
754
1114
  }
755
1115
 
756
- export { Order3000Client, getScheduleInfo };
1116
+ export { Order3000Client, fulfillmentTypeEnum, getScheduleInfo, participantStatusEnum, paymentMethodEnum, teamOrderStatusEnum };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "order3000",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "order3000 node.js sdk",
5
5
  "main": "./dist/client.cjs",
6
6
  "module": "./dist/client.mjs",
@@ -32,8 +32,8 @@
32
32
  "prettier": "^3.7.4",
33
33
  "tsx": "^4.19.2",
34
34
  "typescript": "^5.9.3",
35
- "@kolaveri/eslint-config": "1.4.2",
36
35
  "@kolaveri/prettier-config": "1.0.6",
36
+ "@kolaveri/eslint-config": "1.5.0",
37
37
  "@kolaveri/tsconfig": "1.0.1"
38
38
  },
39
39
  "files": [