@frontdesk-africa/store-js 0.2.0 → 0.3.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 CHANGED
@@ -88,6 +88,10 @@ const checkout = await store.createCheckout(
88
88
  redirect(checkout.hostedUrl)
89
89
  ```
90
90
 
91
+ Every `variantRef` must be a live one from the catalogue. An unknown or unpublished ref is refused
92
+ with `400 VALIDATION_ERROR` listing the bad refs in `error.details.invalid`, so a typo fails on your
93
+ server instead of in front of your buyer.
94
+
91
95
  Then, when the buyer comes back:
92
96
 
93
97
  ```ts
@@ -152,15 +156,39 @@ the check will fail in a way that looks like a key mismatch.
152
156
  Payloads are thin — refs only. Re-fetch through the API for detail, so a replayed delivery can never
153
157
  present stale figures as current.
154
158
 
159
+ ## Orders + keeping a local copy in sync
160
+
161
+ `orders()` (server only, secret key) lists your own orders as a **delta read**: pass the largest
162
+ `updatedAt` you have seen back as `updatedSince` and upsert by `ref` — the `>=` comparison repeats
163
+ the boundary row on purpose so a same-second tie can never drop one. `products({ updatedSince })`
164
+ works the same way for the catalogue.
165
+
166
+ ```ts
167
+ let cursor = loadCursor() // null on first run = full backfill
168
+ for (;;) {
169
+ const page = await store.orders(cursor ? { updatedSince: cursor, limit: 200 } : { limit: 200 })
170
+ for (const order of page) upsertByRef(order)
171
+ if (page.length < 200) break
172
+ cursor = page[page.length - 1].updatedAt
173
+ }
174
+ ```
175
+
176
+ Deletions never show up as absence in a delta page: they arrive as the `product.deleted` and
177
+ `order.deleted` webhooks — remove those refs from your copy when they land. FrontDesk stays the
178
+ source of truth; on any conflict, its row wins.
179
+
180
+ Confirming a single purchase is still `getCheckout(ref)`, not a scan of `orders()`.
181
+
155
182
  ## Test mode
156
183
 
157
- A `fd_sk_test_…` key opens a test checkout. The hosted page shows a test banner and a
158
- **Simulate a successful payment** button, so you can build the whole loop with no bank account and no
159
- spend.
184
+ A `fd_sk_test_…` key opens a test checkout that completes with a **real charge on the provider's
185
+ sandbox** pay it with a Paystack test card (the popup offers a Success option). There is exactly
186
+ one way to finish a checkout in either mode.
160
187
 
161
- A simulated payment completes the session, sets a synthetic `test_ord_…` ref, and fires
162
- `checkout.completed` with `"test": true`. It creates **no order**, credits nobody and posts no ledger
163
- entry so do not try to fetch that order ref. Test mode proves your integration, not fulfilment.
188
+ The order it creates is REAL and marked `test` everywhere: no money moves, nothing is held from
189
+ stock, and `checkout.completed` fires with the real `orderRef`. Test orders show under a Test badge
190
+ in the portal, only a test key can read them (`orders()` never mixes envs), and they are
191
+ hard-deleted after 90 days — the `order.deleted` webhook tells your mirror when that happens.
164
192
 
165
193
  ---
166
194
 
package/dist/index.d.mts CHANGED
@@ -852,6 +852,9 @@ interface PublicProductSummary {
852
852
  ratingAvg?: number;
853
853
  /** Published review count behind `ratingAvg`. Absent under the floor, for the same reason. */
854
854
  ratingCount?: number;
855
+ /** Last change to the product or its variants (ISO). On GET /v1/store/products this is the
856
+ * `updatedSince` delta cursor: pass the largest value you have seen back to get what changed. */
857
+ updatedAt?: string;
855
858
  }
856
859
  type CollectionCoverKind = 'image' | 'video';
857
860
  /** Buyer-facing collection in the storefront payload / public endpoints. */
@@ -890,6 +893,14 @@ interface DeliveryZone {
890
893
  * payloads and on single-currency stores. */
891
894
  prices?: ZoneFee[];
892
895
  }
896
+ /**
897
+ * Store order lifecycle — the union of the standard product-cart axis (pending → paid → fulfilled) and
898
+ * the package-BOOKING axis (requested → awaiting_payment → confirmed → in_progress → completed). One
899
+ * table (store_orders) carries both since Unified Products; the DB enum matches this set.
900
+ */
901
+ type StoreOrderStatus = 'pending' | 'awaiting_payment' | 'requested' | 'accepted' | 'declined' | 'confirmed' | 'in_progress' | 'paid' | 'completed' | 'fulfilled' | 'cancelled' | 'expired' | 'refunded' | 'archived'
902
+ /** Held while a Save For It plan funds toward this product (no stock reserved); flips to `paid` on completion. */
903
+ | 'saving';
893
904
  interface StoreCheckoutItem {
894
905
  variantRef: string;
895
906
  quantity: number;
@@ -909,6 +920,14 @@ interface StoreShippingAddress {
909
920
  lat?: number | null;
910
921
  lng?: number | null;
911
922
  }
923
+ /** `awaiting_stock` = paid, but the item was sold out when the Save For It plan completed. The merchant
924
+ * owes a unit they do not have; the order must stay OUT of the ship-now queue until they restock, at
925
+ * which point it clears back to `unfulfilled` on its own. */
926
+ /** `preordered` = the buyer deliberately bought something the merchant does not have yet (PO-1). Kept
927
+ * distinct from `awaiting_stock` because the merchant's queue has to tell "a saver's plan completed and
928
+ * I owe them" apart from "I pre-sold this on purpose", and because a part-paid pre-order must NOT rejoin
929
+ * the ship-now queue on restock. */
930
+ type StoreOrderFulfillmentStatus = 'unfulfilled' | 'fulfilled' | 'shipped' | 'delivered' | 'awaiting_stock' | 'preordered';
912
931
 
913
932
  /**
914
933
  * Packages — sellable offerings + priced tiers (options). Spec §13.1, §10.18 (PV-012/054).
@@ -996,7 +1015,12 @@ interface StoreTicketItem {
996
1015
  */
997
1016
  interface StoreEventCheckoutInput {
998
1017
  tickets: StoreTicketItem[];
999
- /** Products already attached to this event. Not the general catalogue. */
1018
+ /**
1019
+ * 🔴 NOT SUPPORTED YET — a non-empty array is refused with 400. It was declared with D18 and never
1020
+ * wired: the hosted ticket page has no product concept, so anything sent here reached the buyer as
1021
+ * neither a line nor a charge. Kept on the type because the contract is frozen (D13). Sell
1022
+ * merchandise with a separate POST /v1/store/checkouts until the hosted page can carry it.
1023
+ */
1000
1024
  products?: StoreCheckoutItem[] | null;
1001
1025
  contact: StoreCheckoutContact;
1002
1026
  /** Buyer DOB, when the event uses order-level age checks. YYYY-MM-DD. */
@@ -1072,6 +1096,64 @@ interface StoreCheckoutSessionView {
1072
1096
  createdAt: string;
1073
1097
  }
1074
1098
 
1099
+ /**
1100
+ * GET /v1/store/orders — the merchant's own store orders, over their SECRET key.
1101
+ *
1102
+ * Built for delta sync (the Supabase mirror pulls through `updatedSince`), which is why the shape
1103
+ * is a snapshot a mirror can upsert whole: refs, money in minor units, line snapshots that survive
1104
+ * product edits. Live and test orders never mix — the key's env picks the set, exactly like
1105
+ * checkouts. What is NOT here is deliberate: no provider references, no ledger refs, no wallet or
1106
+ * payout figures — the standing constraint (no money surface behind an installed key) applies to
1107
+ * this read exactly as it does to mirrors.
1108
+ */
1109
+ interface StoreOrderRecordLine {
1110
+ /** Snapshot refs: null when the product/variant was deleted after purchase. */
1111
+ productRef: string | null;
1112
+ productName: string;
1113
+ variantRef: string | null;
1114
+ variantName: string;
1115
+ imageUrl: string | null;
1116
+ quantity: number;
1117
+ unitPriceMinor: number;
1118
+ subtotalMinor: number;
1119
+ }
1120
+ interface StoreOrderRecord {
1121
+ ref: string;
1122
+ /** The short human reference shown to the buyer (receipts, support). */
1123
+ shortReference: string;
1124
+ /** The full lifecycle union covers BOTH axes: product carts (pending → paid → fulfilled) and
1125
+ * package bookings (requested → awaiting_payment → confirmed → in_progress → completed).
1126
+ * `saving` (an in-progress savings plan) never appears here — it is not an order yet. */
1127
+ status: Exclude<StoreOrderStatus, 'saving'>;
1128
+ fulfillmentStatus: StoreOrderFulfillmentStatus;
1129
+ /** Paid with a test key: no real money moved, hard-deleted after 90 days (`order.deleted`). */
1130
+ test: boolean;
1131
+ currency: string;
1132
+ subtotalMinor: number;
1133
+ deliveryFeeMinor: number;
1134
+ totalMinor: number;
1135
+ buyer: {
1136
+ name: string;
1137
+ email: string | null;
1138
+ phone: string | null;
1139
+ };
1140
+ needsDelivery: boolean;
1141
+ shipping: {
1142
+ address1: string | null;
1143
+ address2: string | null;
1144
+ city: string | null;
1145
+ state: string | null;
1146
+ country: string | null;
1147
+ zoneRef: string | null;
1148
+ zoneName: string | null;
1149
+ } | null;
1150
+ lines: StoreOrderRecordLine[];
1151
+ paidAt: string | null;
1152
+ createdAt: string;
1153
+ /** The delta cursor: pass the largest value you have seen back as `updatedSince`. */
1154
+ updatedAt: string;
1155
+ }
1156
+
1075
1157
  /**
1076
1158
  * `POST /v1/store/customers/link` — attach the caller's own signed-in user to this workspace's
1077
1159
  * contacts (Supabase Phase 1c; design doc §7.6).
@@ -2399,7 +2481,16 @@ declare function createStoreClient(opts: StoreClientOptions): {
2399
2481
  idempotencyKey?: string;
2400
2482
  }) => Promise<T>;
2401
2483
  storefront: () => Promise<Storefront>;
2402
- products: () => Promise<PublicProductSummary[]>;
2484
+ /**
2485
+ * Every public product. With `updatedSince` (ISO 8601) it becomes a delta read for keeping a
2486
+ * local copy fresh: ordered oldest change first, `limit` pages it (max 200), and each summary's
2487
+ * `updatedAt` is the next cursor. The comparison is >= so the boundary row repeats — upsert by
2488
+ * ref. Deleted products arrive as the `product.deleted` webhook, not as an absence here.
2489
+ */
2490
+ products: (params?: {
2491
+ updatedSince?: string;
2492
+ limit?: number;
2493
+ }) => Promise<PublicProductSummary[]>;
2403
2494
  product: (slug: string) => Promise<PublicProduct>;
2404
2495
  productSlots: (slug: string, opts: {
2405
2496
  variantRef: string;
@@ -2462,6 +2553,17 @@ declare function createStoreClient(opts: StoreClientOptions): {
2462
2553
  * the contact existed before — it cannot be used to probe who is already a customer.
2463
2554
  */
2464
2555
  linkCustomer: (input: StoreCustomerLinkInput) => Promise<StoreCustomerLinkView>;
2556
+ /**
2557
+ * Your own store orders, as a delta read. SERVER ONLY — needs a secret key, and the key's env
2558
+ * picks the set (a live key never sees test orders). Page forward on `updatedSince` using each
2559
+ * order's `updatedAt` as the next cursor and upsert by ref (the >= comparison repeats the
2560
+ * boundary row). A page shorter than `limit` means you are caught up. This is a sync and
2561
+ * back-office read: to confirm a single purchase, keep using `getCheckout`.
2562
+ */
2563
+ orders: (params?: {
2564
+ updatedSince?: string;
2565
+ limit?: number;
2566
+ }) => Promise<StoreOrderRecord[]>;
2465
2567
  };
2466
2568
  type StoreClient = ReturnType<typeof createStoreClient>;
2467
2569
  /**
package/dist/index.d.ts CHANGED
@@ -852,6 +852,9 @@ interface PublicProductSummary {
852
852
  ratingAvg?: number;
853
853
  /** Published review count behind `ratingAvg`. Absent under the floor, for the same reason. */
854
854
  ratingCount?: number;
855
+ /** Last change to the product or its variants (ISO). On GET /v1/store/products this is the
856
+ * `updatedSince` delta cursor: pass the largest value you have seen back to get what changed. */
857
+ updatedAt?: string;
855
858
  }
856
859
  type CollectionCoverKind = 'image' | 'video';
857
860
  /** Buyer-facing collection in the storefront payload / public endpoints. */
@@ -890,6 +893,14 @@ interface DeliveryZone {
890
893
  * payloads and on single-currency stores. */
891
894
  prices?: ZoneFee[];
892
895
  }
896
+ /**
897
+ * Store order lifecycle — the union of the standard product-cart axis (pending → paid → fulfilled) and
898
+ * the package-BOOKING axis (requested → awaiting_payment → confirmed → in_progress → completed). One
899
+ * table (store_orders) carries both since Unified Products; the DB enum matches this set.
900
+ */
901
+ type StoreOrderStatus = 'pending' | 'awaiting_payment' | 'requested' | 'accepted' | 'declined' | 'confirmed' | 'in_progress' | 'paid' | 'completed' | 'fulfilled' | 'cancelled' | 'expired' | 'refunded' | 'archived'
902
+ /** Held while a Save For It plan funds toward this product (no stock reserved); flips to `paid` on completion. */
903
+ | 'saving';
893
904
  interface StoreCheckoutItem {
894
905
  variantRef: string;
895
906
  quantity: number;
@@ -909,6 +920,14 @@ interface StoreShippingAddress {
909
920
  lat?: number | null;
910
921
  lng?: number | null;
911
922
  }
923
+ /** `awaiting_stock` = paid, but the item was sold out when the Save For It plan completed. The merchant
924
+ * owes a unit they do not have; the order must stay OUT of the ship-now queue until they restock, at
925
+ * which point it clears back to `unfulfilled` on its own. */
926
+ /** `preordered` = the buyer deliberately bought something the merchant does not have yet (PO-1). Kept
927
+ * distinct from `awaiting_stock` because the merchant's queue has to tell "a saver's plan completed and
928
+ * I owe them" apart from "I pre-sold this on purpose", and because a part-paid pre-order must NOT rejoin
929
+ * the ship-now queue on restock. */
930
+ type StoreOrderFulfillmentStatus = 'unfulfilled' | 'fulfilled' | 'shipped' | 'delivered' | 'awaiting_stock' | 'preordered';
912
931
 
913
932
  /**
914
933
  * Packages — sellable offerings + priced tiers (options). Spec §13.1, §10.18 (PV-012/054).
@@ -996,7 +1015,12 @@ interface StoreTicketItem {
996
1015
  */
997
1016
  interface StoreEventCheckoutInput {
998
1017
  tickets: StoreTicketItem[];
999
- /** Products already attached to this event. Not the general catalogue. */
1018
+ /**
1019
+ * 🔴 NOT SUPPORTED YET — a non-empty array is refused with 400. It was declared with D18 and never
1020
+ * wired: the hosted ticket page has no product concept, so anything sent here reached the buyer as
1021
+ * neither a line nor a charge. Kept on the type because the contract is frozen (D13). Sell
1022
+ * merchandise with a separate POST /v1/store/checkouts until the hosted page can carry it.
1023
+ */
1000
1024
  products?: StoreCheckoutItem[] | null;
1001
1025
  contact: StoreCheckoutContact;
1002
1026
  /** Buyer DOB, when the event uses order-level age checks. YYYY-MM-DD. */
@@ -1072,6 +1096,64 @@ interface StoreCheckoutSessionView {
1072
1096
  createdAt: string;
1073
1097
  }
1074
1098
 
1099
+ /**
1100
+ * GET /v1/store/orders — the merchant's own store orders, over their SECRET key.
1101
+ *
1102
+ * Built for delta sync (the Supabase mirror pulls through `updatedSince`), which is why the shape
1103
+ * is a snapshot a mirror can upsert whole: refs, money in minor units, line snapshots that survive
1104
+ * product edits. Live and test orders never mix — the key's env picks the set, exactly like
1105
+ * checkouts. What is NOT here is deliberate: no provider references, no ledger refs, no wallet or
1106
+ * payout figures — the standing constraint (no money surface behind an installed key) applies to
1107
+ * this read exactly as it does to mirrors.
1108
+ */
1109
+ interface StoreOrderRecordLine {
1110
+ /** Snapshot refs: null when the product/variant was deleted after purchase. */
1111
+ productRef: string | null;
1112
+ productName: string;
1113
+ variantRef: string | null;
1114
+ variantName: string;
1115
+ imageUrl: string | null;
1116
+ quantity: number;
1117
+ unitPriceMinor: number;
1118
+ subtotalMinor: number;
1119
+ }
1120
+ interface StoreOrderRecord {
1121
+ ref: string;
1122
+ /** The short human reference shown to the buyer (receipts, support). */
1123
+ shortReference: string;
1124
+ /** The full lifecycle union covers BOTH axes: product carts (pending → paid → fulfilled) and
1125
+ * package bookings (requested → awaiting_payment → confirmed → in_progress → completed).
1126
+ * `saving` (an in-progress savings plan) never appears here — it is not an order yet. */
1127
+ status: Exclude<StoreOrderStatus, 'saving'>;
1128
+ fulfillmentStatus: StoreOrderFulfillmentStatus;
1129
+ /** Paid with a test key: no real money moved, hard-deleted after 90 days (`order.deleted`). */
1130
+ test: boolean;
1131
+ currency: string;
1132
+ subtotalMinor: number;
1133
+ deliveryFeeMinor: number;
1134
+ totalMinor: number;
1135
+ buyer: {
1136
+ name: string;
1137
+ email: string | null;
1138
+ phone: string | null;
1139
+ };
1140
+ needsDelivery: boolean;
1141
+ shipping: {
1142
+ address1: string | null;
1143
+ address2: string | null;
1144
+ city: string | null;
1145
+ state: string | null;
1146
+ country: string | null;
1147
+ zoneRef: string | null;
1148
+ zoneName: string | null;
1149
+ } | null;
1150
+ lines: StoreOrderRecordLine[];
1151
+ paidAt: string | null;
1152
+ createdAt: string;
1153
+ /** The delta cursor: pass the largest value you have seen back as `updatedSince`. */
1154
+ updatedAt: string;
1155
+ }
1156
+
1075
1157
  /**
1076
1158
  * `POST /v1/store/customers/link` — attach the caller's own signed-in user to this workspace's
1077
1159
  * contacts (Supabase Phase 1c; design doc §7.6).
@@ -2399,7 +2481,16 @@ declare function createStoreClient(opts: StoreClientOptions): {
2399
2481
  idempotencyKey?: string;
2400
2482
  }) => Promise<T>;
2401
2483
  storefront: () => Promise<Storefront>;
2402
- products: () => Promise<PublicProductSummary[]>;
2484
+ /**
2485
+ * Every public product. With `updatedSince` (ISO 8601) it becomes a delta read for keeping a
2486
+ * local copy fresh: ordered oldest change first, `limit` pages it (max 200), and each summary's
2487
+ * `updatedAt` is the next cursor. The comparison is >= so the boundary row repeats — upsert by
2488
+ * ref. Deleted products arrive as the `product.deleted` webhook, not as an absence here.
2489
+ */
2490
+ products: (params?: {
2491
+ updatedSince?: string;
2492
+ limit?: number;
2493
+ }) => Promise<PublicProductSummary[]>;
2403
2494
  product: (slug: string) => Promise<PublicProduct>;
2404
2495
  productSlots: (slug: string, opts: {
2405
2496
  variantRef: string;
@@ -2462,6 +2553,17 @@ declare function createStoreClient(opts: StoreClientOptions): {
2462
2553
  * the contact existed before — it cannot be used to probe who is already a customer.
2463
2554
  */
2464
2555
  linkCustomer: (input: StoreCustomerLinkInput) => Promise<StoreCustomerLinkView>;
2556
+ /**
2557
+ * Your own store orders, as a delta read. SERVER ONLY — needs a secret key, and the key's env
2558
+ * picks the set (a live key never sees test orders). Page forward on `updatedSince` using each
2559
+ * order's `updatedAt` as the next cursor and upsert by ref (the >= comparison repeats the
2560
+ * boundary row). A page shorter than `limit` means you are caught up. This is a sync and
2561
+ * back-office read: to confirm a single purchase, keep using `getCheckout`.
2562
+ */
2563
+ orders: (params?: {
2564
+ updatedSince?: string;
2565
+ limit?: number;
2566
+ }) => Promise<StoreOrderRecord[]>;
2465
2567
  };
2466
2568
  type StoreClient = ReturnType<typeof createStoreClient>;
2467
2569
  /**
package/dist/index.js CHANGED
@@ -83,7 +83,19 @@ function createStoreClient(opts) {
83
83
  request,
84
84
  // ------------------------------------------------------------- read (fd_pk_)
85
85
  storefront: /* @__PURE__ */ __name(() => request("/store/storefront"), "storefront"),
86
- products: /* @__PURE__ */ __name(() => request("/store/products"), "products"),
86
+ /**
87
+ * Every public product. With `updatedSince` (ISO 8601) it becomes a delta read for keeping a
88
+ * local copy fresh: ordered oldest change first, `limit` pages it (max 200), and each summary's
89
+ * `updatedAt` is the next cursor. The comparison is >= so the boundary row repeats — upsert by
90
+ * ref. Deleted products arrive as the `product.deleted` webhook, not as an absence here.
91
+ */
92
+ products: /* @__PURE__ */ __name((params) => {
93
+ const q = new URLSearchParams();
94
+ if (params?.updatedSince) q.set("updatedSince", params.updatedSince);
95
+ if (params?.limit) q.set("limit", String(params.limit));
96
+ const qs = q.toString();
97
+ return request(`/store/products${qs ? `?${qs}` : ""}`);
98
+ }, "products"),
87
99
  product: /* @__PURE__ */ __name((slug) => request(`/store/products/${enc(slug)}`), "product"),
88
100
  productSlots: /* @__PURE__ */ __name((slug, opts2) => request(`/store/products/${enc(slug)}/slots?variantRef=${enc(opts2.variantRef)}&date=${enc(opts2.date)}`), "productSlots"),
89
101
  collections: /* @__PURE__ */ __name(() => request("/store/collections"), "collections"),
@@ -160,7 +172,21 @@ function createStoreClient(opts) {
160
172
  linkCustomer: /* @__PURE__ */ __name((input) => request("/store/customers/link", {
161
173
  method: "POST",
162
174
  body: JSON.stringify(input)
163
- }), "linkCustomer")
175
+ }), "linkCustomer"),
176
+ /**
177
+ * Your own store orders, as a delta read. SERVER ONLY — needs a secret key, and the key's env
178
+ * picks the set (a live key never sees test orders). Page forward on `updatedSince` using each
179
+ * order's `updatedAt` as the next cursor and upsert by ref (the >= comparison repeats the
180
+ * boundary row). A page shorter than `limit` means you are caught up. This is a sync and
181
+ * back-office read: to confirm a single purchase, keep using `getCheckout`.
182
+ */
183
+ orders: /* @__PURE__ */ __name((params) => {
184
+ const q = new URLSearchParams();
185
+ if (params?.updatedSince) q.set("updatedSince", params.updatedSince);
186
+ if (params?.limit) q.set("limit", String(params.limit));
187
+ const qs = q.toString();
188
+ return request(`/store/orders${qs ? `?${qs}` : ""}`);
189
+ }, "orders")
164
190
  };
165
191
  }
166
192
  __name(createStoreClient, "createStoreClient");
package/dist/index.mjs CHANGED
@@ -49,7 +49,19 @@ function createStoreClient(opts) {
49
49
  request,
50
50
  // ------------------------------------------------------------- read (fd_pk_)
51
51
  storefront: /* @__PURE__ */ __name(() => request("/store/storefront"), "storefront"),
52
- products: /* @__PURE__ */ __name(() => request("/store/products"), "products"),
52
+ /**
53
+ * Every public product. With `updatedSince` (ISO 8601) it becomes a delta read for keeping a
54
+ * local copy fresh: ordered oldest change first, `limit` pages it (max 200), and each summary's
55
+ * `updatedAt` is the next cursor. The comparison is >= so the boundary row repeats — upsert by
56
+ * ref. Deleted products arrive as the `product.deleted` webhook, not as an absence here.
57
+ */
58
+ products: /* @__PURE__ */ __name((params) => {
59
+ const q = new URLSearchParams();
60
+ if (params?.updatedSince) q.set("updatedSince", params.updatedSince);
61
+ if (params?.limit) q.set("limit", String(params.limit));
62
+ const qs = q.toString();
63
+ return request(`/store/products${qs ? `?${qs}` : ""}`);
64
+ }, "products"),
53
65
  product: /* @__PURE__ */ __name((slug) => request(`/store/products/${enc(slug)}`), "product"),
54
66
  productSlots: /* @__PURE__ */ __name((slug, opts2) => request(`/store/products/${enc(slug)}/slots?variantRef=${enc(opts2.variantRef)}&date=${enc(opts2.date)}`), "productSlots"),
55
67
  collections: /* @__PURE__ */ __name(() => request("/store/collections"), "collections"),
@@ -126,7 +138,21 @@ function createStoreClient(opts) {
126
138
  linkCustomer: /* @__PURE__ */ __name((input) => request("/store/customers/link", {
127
139
  method: "POST",
128
140
  body: JSON.stringify(input)
129
- }), "linkCustomer")
141
+ }), "linkCustomer"),
142
+ /**
143
+ * Your own store orders, as a delta read. SERVER ONLY — needs a secret key, and the key's env
144
+ * picks the set (a live key never sees test orders). Page forward on `updatedSince` using each
145
+ * order's `updatedAt` as the next cursor and upsert by ref (the >= comparison repeats the
146
+ * boundary row). A page shorter than `limit` means you are caught up. This is a sync and
147
+ * back-office read: to confirm a single purchase, keep using `getCheckout`.
148
+ */
149
+ orders: /* @__PURE__ */ __name((params) => {
150
+ const q = new URLSearchParams();
151
+ if (params?.updatedSince) q.set("updatedSince", params.updatedSince);
152
+ if (params?.limit) q.set("limit", String(params.limit));
153
+ const qs = q.toString();
154
+ return request(`/store/orders${qs ? `?${qs}` : ""}`);
155
+ }, "orders")
130
156
  };
131
157
  }
132
158
  __name(createStoreClient, "createStoreClient");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontdesk-africa/store-js",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Typed client for the FrontDesk Storefront API: catalogue, events, forms, hosted checkout and signed webhooks.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://api.frontdesk.africa/v1/store/docs",