order3000 0.16.0 → 0.18.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.cjs CHANGED
@@ -34,6 +34,24 @@ class TokenManager {
34
34
  }
35
35
  return this.defaultVenueId;
36
36
  }
37
+ /**
38
+ * Async counterpart to {@link getVenueId}. Fetches the token if it
39
+ * hasn't been loaded yet (which is what populates `defaultVenueId` via
40
+ * `setDefaultVenueId`), then returns the venue id.
41
+ *
42
+ * Prefer this over `getVenueId()` from request paths — a cold SDK
43
+ * instance will otherwise synchronously throw "No venue ID available"
44
+ * before the token has had a chance to load.
45
+ */
46
+ async getVenueIdAsync() {
47
+ if (this.userVenueId) {
48
+ return this.userVenueId;
49
+ }
50
+ if (!this.defaultVenueId) {
51
+ await this.getToken();
52
+ }
53
+ return this.getVenueId();
54
+ }
37
55
  async refreshToken() {
38
56
  if (this.refreshPromise) return this.refreshPromise;
39
57
  this.refreshPromise = fetch(this.tokenEndpoint, {
@@ -85,11 +103,11 @@ class BaseResource {
85
103
  this.baseUrl = baseUrl;
86
104
  this.resourcePath = resourcePath;
87
105
  }
88
- async request(method, path, data) {
106
+ async request(method, path, data, cacheOptions) {
89
107
  const token = await this.tokenManager.getToken();
90
108
  const venueId = this.tokenManager.getVenueId();
91
109
  const url = `${this.baseUrl}/venues/${venueId}${this.resourcePath}${path}`;
92
- const response = await fetch(url, {
110
+ const init = {
93
111
  method,
94
112
  headers: {
95
113
  Authorization: `Bearer ${token}`,
@@ -97,14 +115,75 @@ class BaseResource {
97
115
  },
98
116
  body: data ? JSON.stringify(data) : void 0,
99
117
  signal: AbortSignal.timeout(36e4)
100
- });
118
+ };
119
+ if (cacheOptions?.next !== void 0) {
120
+ init.next = cacheOptions.next;
121
+ } else {
122
+ init.cache = cacheOptions?.cache ?? "no-store";
123
+ }
124
+ const response = await fetch(url, init);
101
125
  if (!response.ok) {
102
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
126
+ let body = "";
127
+ try {
128
+ body = await response.text();
129
+ } catch {
130
+ }
131
+ throw new Error(
132
+ `HTTP ${response.status}: ${response.statusText}${body ? ` \u2014 ${body}` : ""}`
133
+ );
103
134
  }
104
135
  return response.json();
105
136
  }
106
137
  }
107
138
 
139
+ class AreasResource extends BaseResource {
140
+ constructor(tokenManager, baseUrl) {
141
+ super(tokenManager, baseUrl, "/areas");
142
+ }
143
+ /**
144
+ * List bookable + public areas for the venue.
145
+ */
146
+ async list(options) {
147
+ const queryString = this.buildExpandQuery(options?.expand);
148
+ return this.request("GET", queryString);
149
+ }
150
+ /**
151
+ * Fetch a single area by id or accessor.
152
+ */
153
+ async getByAccessor(accessor, options) {
154
+ const queryString = this.buildExpandQuery(options?.expand);
155
+ return this.request("GET", `/${accessor}${queryString}`);
156
+ }
157
+ /**
158
+ * List occupied date/time windows for an area between `from` and `to`.
159
+ */
160
+ async getAvailability(accessor, options) {
161
+ const params = new URLSearchParams();
162
+ if (options?.from) params.set("from", options.from);
163
+ if (options?.to) params.set("to", options.to);
164
+ const qs = params.toString();
165
+ const query = qs ? `?${qs}` : "";
166
+ return this.request(
167
+ "GET",
168
+ `/${accessor}/availability${query}`
169
+ );
170
+ }
171
+ /**
172
+ * Submit a public booking request for an area.
173
+ */
174
+ async submitBookingRequest(accessor, payload) {
175
+ return this.request(
176
+ "POST",
177
+ `/${accessor}/booking-requests`,
178
+ payload
179
+ );
180
+ }
181
+ buildExpandQuery(expand) {
182
+ if (!expand?.length) return "";
183
+ return `?expand=${expand.join(",")}`;
184
+ }
185
+ }
186
+
108
187
  class BasketsResource extends BaseResource {
109
188
  constructor(tokenManager, baseUrl) {
110
189
  super(tokenManager, baseUrl, "/baskets");
@@ -146,6 +225,51 @@ class BasketsResource extends BaseResource {
146
225
  `/items/${basketItemId}?${params.toString()}`
147
226
  );
148
227
  }
228
+ /**
229
+ * Canonical totals for a basket. Clients read every displayed monetary
230
+ * figure (effective total, deposit credit, cash refund) from this
231
+ * endpoint — they never multiply deposit counts by amounts locally.
232
+ *
233
+ * Pass `declaredReturns` as references to depositware templates plus a
234
+ * count. order3000 refuses deposit money amounts on input.
235
+ */
236
+ async getTotals(basketId, body = {}) {
237
+ return this.request(
238
+ "POST",
239
+ `/${basketId}/totals`,
240
+ { declaredReturns: body.declaredReturns ?? [] }
241
+ );
242
+ }
243
+ /**
244
+ * Mark every active basket for the session as `abandoned`.
245
+ *
246
+ * Used by thamarai's "Neu starten" flow: after a non-team order
247
+ * has been placed (its basket converted), the user might want to
248
+ * start a fresh order. A subsequent `add()` call would otherwise
249
+ * reuse any leftover active basket on the same session — which
250
+ * can happen if a prior compose flow left an active row behind.
251
+ * Calling `abandon()` first guarantees the next `add()` inserts a
252
+ * brand-new active basket with exactly one item.
253
+ */
254
+ async abandon(sessionId) {
255
+ return this.request("POST", "/abandon", {
256
+ sessionId
257
+ });
258
+ }
259
+ /**
260
+ * Persist per-template declared-return counts onto the basket.
261
+ *
262
+ * Used by team-order participants so the Lead's breakdown popover
263
+ * sees every member's intent in real time (the standard
264
+ * `useTeamOrderRealtime` postgres-changes subscription on
265
+ * `baskets` then propagates the change to the team).
266
+ *
267
+ * Server validates `sessionId === basket.session_id` — only the
268
+ * basket's owner can write to its declared returns.
269
+ */
270
+ async setDeclaredReturns(basketId, body) {
271
+ return this.request("POST", `/${basketId}/declared-returns`, body);
272
+ }
149
273
  }
150
274
 
151
275
  class EventsResource extends BaseResource {
@@ -205,7 +329,12 @@ class EventsResource extends BaseResource {
205
329
  */
206
330
  async getByAccessor(accessor, options) {
207
331
  const queryString = this.buildQueryString(options);
208
- return this.request("GET", `/${accessor}${queryString}`);
332
+ return this.request(
333
+ "GET",
334
+ `/${accessor}${queryString}`,
335
+ void 0,
336
+ { next: { revalidate: 2700 } }
337
+ );
209
338
  }
210
339
  buildQueryString(options) {
211
340
  if (!options?.expand?.length) return "";
@@ -266,6 +395,18 @@ class OrdersResource extends BaseResource {
266
395
  constructor(tokenManager, baseUrl) {
267
396
  super(tokenManager, baseUrl, "/orders");
268
397
  }
398
+ /**
399
+ * Most recent non-team order for the guest session, with captured
400
+ * basket items + totals + declared returns inlined. `null` when the
401
+ * session has no qualifying order yet (fresh session, or only team
402
+ * orders which go through `teamOrders` instead).
403
+ */
404
+ async getLatest(sessionId) {
405
+ return this.request(
406
+ "GET",
407
+ `/latest?sessionId=${encodeURIComponent(sessionId)}`
408
+ );
409
+ }
269
410
  /**
270
411
  * Place a new order.
271
412
  *
@@ -290,6 +431,14 @@ class OrdersResource extends BaseResource {
290
431
  async confirm(orderId, request) {
291
432
  return this.request("POST", `/${orderId}/confirm`, request);
292
433
  }
434
+ /**
435
+ * Record an additional cash handover amount for a team-order participant
436
+ * who has added items after their initial cash payment. No Stripe
437
+ * interaction — this updates the handover total the Lead will collect.
438
+ */
439
+ async addCashDelta(request) {
440
+ return this.request("POST", "/cash-delta", request);
441
+ }
293
442
  }
294
443
 
295
444
  class PaymentIntentsResource extends BaseResource {
@@ -305,6 +454,24 @@ class PaymentIntentsResource extends BaseResource {
305
454
  async create(request) {
306
455
  return this.request("POST", "", request);
307
456
  }
457
+ /**
458
+ * Create a delta PaymentIntent for a team-order participant who added
459
+ * items after paying. Reuses the saved card from the initial auth so the
460
+ * client only needs one tap (unless Stripe triggers 3DS).
461
+ */
462
+ async createDelta(request) {
463
+ return this.request("POST", "/delta", request);
464
+ }
465
+ /**
466
+ * Release part of a team-order participant's existing Stripe authorisation
467
+ * when they remove items from their basket. Walks payments newest-first,
468
+ * cancelling fully-consumed PIs and `update({ amount })`-ing the last
469
+ * partially-trimmed one. No client-side confirmation — lowering a
470
+ * `requires_capture` PI's amount is a single server round-trip.
471
+ */
472
+ async reduceDelta(request) {
473
+ return this.request("POST", "/delta-reduce", request);
474
+ }
308
475
  }
309
476
 
310
477
  class ReservationsResource extends BaseResource {
@@ -393,6 +560,221 @@ class SubscriptionsResource extends BaseResource {
393
560
  }
394
561
  }
395
562
 
563
+ const paymentMethodEnum = {
564
+ enumValues: ["card", "cash", "voucher"]
565
+ };
566
+ const fulfillmentTypeEnum = {
567
+ enumValues: ["pickup", "delivery"]
568
+ };
569
+ const teamOrderStatusEnum = {
570
+ enumValues: [
571
+ "draft",
572
+ "collecting",
573
+ "locked",
574
+ "placed",
575
+ "ready",
576
+ "completed",
577
+ "cancelled"
578
+ ]
579
+ };
580
+ const participantStatusEnum = {
581
+ enumValues: [
582
+ "browsing",
583
+ "authorized",
584
+ "cash_pending",
585
+ "placed",
586
+ "completed",
587
+ "cancelled"
588
+ ]
589
+ };
590
+ class TeamOrdersResource {
591
+ constructor(tokenManager, baseUrl) {
592
+ this.tokenManager = tokenManager;
593
+ this.baseUrl = baseUrl;
594
+ }
595
+ async request(method, url, data) {
596
+ const token = await this.tokenManager.getToken();
597
+ const response = await fetch(url, {
598
+ method,
599
+ headers: {
600
+ Authorization: `Bearer ${token}`,
601
+ "Content-Type": "application/json"
602
+ },
603
+ body: data ? JSON.stringify(data) : void 0,
604
+ // Bypass Next.js fetch cache — these are live reads/writes, never
605
+ // safe to memoise at the SDK layer.
606
+ cache: "no-store",
607
+ signal: AbortSignal.timeout(36e4)
608
+ });
609
+ if (!response.ok) {
610
+ let body = "";
611
+ try {
612
+ body = await response.text();
613
+ } catch {
614
+ }
615
+ throw new Error(
616
+ `HTTP ${response.status}: ${response.statusText}${body ? ` \u2014 ${body}` : ""}`
617
+ );
618
+ }
619
+ return response.json();
620
+ }
621
+ /**
622
+ * Lead creates a team order from their active basket.
623
+ * Returns the unguessable `publicId` the lead shares with coworkers.
624
+ */
625
+ async create(request) {
626
+ const venueId = await this.tokenManager.getVenueIdAsync();
627
+ return this.request(
628
+ "POST",
629
+ `${this.baseUrl}/venues/${venueId}/team-orders`,
630
+ request
631
+ );
632
+ }
633
+ /**
634
+ * Public read by publicId. Pass the caller's sessionId so the response
635
+ * can set `isLead` correctly for UI gating.
636
+ */
637
+ async get(publicId, sessionId) {
638
+ const query = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
639
+ return this.request(
640
+ "GET",
641
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}${query}`
642
+ );
643
+ }
644
+ /**
645
+ * Participant joins a team order. Idempotent: safe to call on every page
646
+ * mount of the shared URL.
647
+ */
648
+ async join(publicId, request) {
649
+ return this.request(
650
+ "POST",
651
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/join`,
652
+ request
653
+ );
654
+ }
655
+ /**
656
+ * Lead submits the team order. Drops basket-only participants, captures
657
+ * every authorised Stripe PaymentIntent atomically, flips child orders to
658
+ * `pending`. The "Submit anyways" UX is a client-side confirmation dialog
659
+ * that calls the same endpoint — no server variant exists.
660
+ */
661
+ async submit(publicId, request) {
662
+ return this.request(
663
+ "POST",
664
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/submit`,
665
+ request
666
+ );
667
+ }
668
+ /**
669
+ * Lead cancels pre-placement. Voids every authorised Stripe
670
+ * PaymentIntent (no refunds needed — nothing was captured).
671
+ */
672
+ async cancel(publicId, request) {
673
+ return this.request(
674
+ "POST",
675
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/cancel`,
676
+ request
677
+ );
678
+ }
679
+ /**
680
+ * Lead pushes the deadline. A `locked` team flips back to `collecting`
681
+ * when the new deadline is in the future.
682
+ */
683
+ async extendDeadline(publicId, request) {
684
+ return this.request(
685
+ "POST",
686
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/extend-deadline`,
687
+ request
688
+ );
689
+ }
690
+ /**
691
+ * Lead commits (or changes) the shared pickup/delivery time. Required
692
+ * before `submit` — which backfills each child order's `fulfillmentAt`
693
+ * from this value at capture time.
694
+ */
695
+ async setFulfillmentAt(publicId, request) {
696
+ return this.request(
697
+ "POST",
698
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/set-fulfillment-at`,
699
+ request
700
+ );
701
+ }
702
+ /**
703
+ * Lead sets the free-form "where we'll eat" text on the team order.
704
+ * Broadcast to participants via the standard realtime refetch path.
705
+ */
706
+ async setDiningLocation(publicId, request) {
707
+ return this.request(
708
+ "POST",
709
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/set-dining-location`,
710
+ request
711
+ );
712
+ }
713
+ /**
714
+ * Lead sets when the team will start eating. Client sends HH:MM; server
715
+ * computes the delta against `fulfillmentAt` and stores it as
716
+ * `dining_offset_minutes` so the intent survives any later pickup change.
717
+ */
718
+ async setDiningTime(publicId, request) {
719
+ return this.request(
720
+ "POST",
721
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/set-dining-time`,
722
+ request
723
+ );
724
+ }
725
+ /**
726
+ * Toggle a participant's "settled" tick on the team Lead's
727
+ * post-placement settle-up checklist. Writes to
728
+ * `orders.leadSettledAt` — sets it to `NOW()` when `settled` is
729
+ * `true`, clears it back to `null` when `false`.
730
+ *
731
+ * Authorized only for the team Lead's session — server rejects
732
+ * non-Lead callers and `orderId`s that don't belong to this team.
733
+ */
734
+ async setOrderLeadSettled(publicId, request) {
735
+ return this.request(
736
+ "POST",
737
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/lead-settled`,
738
+ request
739
+ );
740
+ }
741
+ /**
742
+ * Any participant proposes an auto-extend when their local clock sees
743
+ * that `fulfillmentAt` has passed while the team order is still
744
+ * collecting. Idempotent on the server side: concurrent callers race
745
+ * but only one write lands.
746
+ */
747
+ async autoExtendFulfillmentIfExpired(publicId, request) {
748
+ return this.request(
749
+ "POST",
750
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/auto-extend-fulfillment-if-expired`,
751
+ request
752
+ );
753
+ }
754
+ /**
755
+ * Lead flips pickup ↔ delivery. Pass `deliveryAddress` when switching
756
+ * TO delivery if no address is already set on the team order.
757
+ */
758
+ async switchFulfillment(publicId, request) {
759
+ return this.request(
760
+ "POST",
761
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/switch-fulfillment`,
762
+ request
763
+ );
764
+ }
765
+ /**
766
+ * Lead replaces the delivery address (inserts a new row in the shared
767
+ * addresses table with type='PHYSICAL' and links it to the team order).
768
+ */
769
+ async updateDeliveryAddress(publicId, request) {
770
+ return this.request(
771
+ "POST",
772
+ `${this.baseUrl}/team-orders/${encodeURIComponent(publicId)}/update-delivery-address`,
773
+ request
774
+ );
775
+ }
776
+ }
777
+
396
778
  var __defProp$2 = Object.defineProperty;
397
779
  var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
398
780
  var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -717,6 +1099,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
717
1099
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
718
1100
  class Order3000Client {
719
1101
  constructor(config) {
1102
+ __publicField(this, "areas");
720
1103
  __publicField(this, "baskets");
721
1104
  __publicField(this, "events");
722
1105
  __publicField(this, "menus");
@@ -724,6 +1107,7 @@ class Order3000Client {
724
1107
  __publicField(this, "paymentIntents");
725
1108
  __publicField(this, "reservations");
726
1109
  __publicField(this, "subscriptions");
1110
+ __publicField(this, "teamOrders");
727
1111
  __publicField(this, "realtime");
728
1112
  __publicField(this, "baseUrl");
729
1113
  __publicField(this, "tokenManager");
@@ -735,6 +1119,7 @@ class Order3000Client {
735
1119
  tokenUrl,
736
1120
  config.venueId
737
1121
  );
1122
+ this.areas = new AreasResource(this.tokenManager, this.baseUrl);
738
1123
  this.baskets = new BasketsResource(this.tokenManager, this.baseUrl);
739
1124
  this.events = new EventsResource(this.tokenManager, this.baseUrl);
740
1125
  this.menus = new MenusResource(this.tokenManager, this.baseUrl);
@@ -751,9 +1136,14 @@ class Order3000Client {
751
1136
  this.tokenManager,
752
1137
  this.baseUrl
753
1138
  );
1139
+ this.teamOrders = new TeamOrdersResource(this.tokenManager, this.baseUrl);
754
1140
  this.realtime = new RealtimeManager(this.tokenManager, this.baseUrl);
755
1141
  }
756
1142
  }
757
1143
 
758
1144
  exports.Order3000Client = Order3000Client;
1145
+ exports.fulfillmentTypeEnum = fulfillmentTypeEnum;
759
1146
  exports.getScheduleInfo = getScheduleInfo;
1147
+ exports.participantStatusEnum = participantStatusEnum;
1148
+ exports.paymentMethodEnum = paymentMethodEnum;
1149
+ exports.teamOrderStatusEnum = teamOrderStatusEnum;