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