@doany-ai/sdk 0.2.9-alpha.0 → 0.3.0-alpha.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.
@@ -1,3 +1,5 @@
1
+ import { actorOf, nameOf, orderAccessHeaders, rememberOrderToken, session } from "./order-access.js";
2
+ import { queryOf, seg } from "./project.js";
1
3
  // Shared by every client the page creates: two clients placing the same order
2
4
  // at once are still one attempt, and neither may release a key the other is
3
5
  // still waiting on. Entries are named by app, actor and request.
@@ -23,40 +25,6 @@ function newKey() {
23
25
  * that ordering the same things again later is a new order.
24
26
  */
25
27
  const ATTEMPT_TTL_MS = 15 * 60 * 1000;
26
- /** cyrb53: a short, stable name for a payload, so the payload itself (the buyer's
27
- * details) is not what gets written as a storage key. A collision would only
28
- * reuse a key for a different payload, which the backend refuses with 409. */
29
- function nameOf(text) {
30
- let h1 = 0xdeadbeef;
31
- let h2 = 0x41c6ce57;
32
- for (let i = 0; i < text.length; i++) {
33
- const ch = text.charCodeAt(i);
34
- h1 = Math.imul(h1 ^ ch, 2654435761);
35
- h2 = Math.imul(h2 ^ ch, 1597334677);
36
- }
37
- h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
38
- h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
39
- return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
40
- }
41
- /** Who the backend will take this request to be from: it keeps idempotency
42
- * keys per signed-in account and per guest, so an unresolved attempt belongs
43
- * to whoever made it and nobody else may reuse or clear it. */
44
- function actorOf(axios) {
45
- var _a, _b, _c;
46
- const header = (_c = (_b = (_a = axios.defaults) === null || _a === void 0 ? void 0 : _a.headers) === null || _b === void 0 ? void 0 : _b.common) === null || _c === void 0 ? void 0 : _c["Authorization"];
47
- if (typeof header !== "string" || !header.startsWith("Bearer "))
48
- return "guest";
49
- try {
50
- const payload = header.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
51
- const sub = JSON.parse(atob(payload)).sub;
52
- if (typeof sub === "string" && sub)
53
- return `user:${sub}`;
54
- }
55
- catch (_d) {
56
- /* not a JWT we can read */
57
- }
58
- return `token:${nameOf(header)}`;
59
- }
60
28
  /**
61
29
  * The request exactly as it will be sent, in one canonical form: keys sorted,
62
30
  * `quantity` filled in with its default, `variant_id` lower-cased.
@@ -104,7 +72,7 @@ function tidyBuyer(buyer) {
104
72
  }
105
73
  return out;
106
74
  }
107
- function wireOf(params) {
75
+ function wireOf(params, appId) {
108
76
  const items = Array.isArray(params === null || params === void 0 ? void 0 : params.items)
109
77
  ? params.items.map((item) => item && typeof item === "object"
110
78
  ? {
@@ -116,15 +84,9 @@ function wireOf(params) {
116
84
  }
117
85
  : item)
118
86
  : params === null || params === void 0 ? void 0 : params.items;
119
- return canonical({ ...params, items, buyer: tidyBuyer(params === null || params === void 0 ? void 0 : params.buyer) });
120
- }
121
- function session() {
122
- try {
123
- return typeof window !== "undefined" ? window.sessionStorage : null;
124
- }
125
- catch (_a) {
126
- return null; // storage can be blocked (private mode, sandboxed iframe)
127
- }
87
+ // The site the order is placed on, unless the page names another (or none).
88
+ const app_id = (params === null || params === void 0 ? void 0 : params.app_id) === undefined ? appId : params.app_id;
89
+ return canonical({ ...params, app_id, items, buyer: tidyBuyer(params === null || params === void 0 ? void 0 : params.buyer) });
128
90
  }
129
91
  /**
130
92
  * Creates the orders module for the Doany SDK.
@@ -138,12 +100,17 @@ function session() {
138
100
  * the same order — also after a page reload, for 15 minutes. Once the
139
101
  * server has answered, the next identical order is a new one.
140
102
  * - **A guest can reopen their order.** The order's access token is kept for
141
- * the browser session and sent with `get` / `cancel` for that order.
103
+ * the browser session and sent with `get` / `cancel` for that order, and
104
+ * with `payments.checkout` / `payments.getForOrder` (order-access.ts).
142
105
  *
143
106
  * @internal
144
107
  */
145
- export function createOrdersModule(axios, appId) {
146
- const baseURL = `/apps/${appId}/orders`;
108
+ export function createOrdersModule(axios, appId, project, {
109
+ // Off for the service role: a backend function's runtime serves many
110
+ // invocations under the same app key, and state shared between them would
111
+ // hand one invocation another's order, or swallow an order as a duplicate.
112
+ rememberAttempts = true, } = {}) {
113
+ const orders = (suffix = "") => project.path(`/orders${suffix}`);
147
114
  // Keys of orders whose outcome is unknown. In memory, and mirrored to
148
115
  // sessionStorage so that reloading the page after a failed request — the
149
116
  // usual way people recover — still retries with the same key.
@@ -194,137 +161,136 @@ export function createOrdersModule(axios, appId) {
194
161
  }
195
162
  },
196
163
  };
197
- // A kept token belongs to whoever placed the order. It is stored under that
198
- // actor and looked up under the current one — plus `guest`, so an order placed
199
- // before signing in can still be opened after. What it never does is follow
200
- // the tab to another account, or stay usable once its account signed out.
201
- const tokenKey = (actor, orderId) => { var _a, _b; return `doany_order_token_${appId}_${nameOf(`${(_b = (_a = axios.defaults) === null || _a === void 0 ? void 0 : _a.baseURL) !== null && _b !== void 0 ? _b : ""}|${actor}`)}_${orderId}`; };
202
- function remember(actor, orderId, token) {
203
- var _a;
204
- if (!token)
205
- return;
206
- try {
207
- (_a = session()) === null || _a === void 0 ? void 0 : _a.setItem(tokenKey(actor, orderId), token);
208
- }
209
- catch (_b) {
210
- /* best effort */
211
- }
212
- }
213
- function accessHeaders(orderId, options) {
214
- var _a, _b;
215
- let token = options === null || options === void 0 ? void 0 : options.accessToken;
216
- if (!token) {
217
- try {
218
- const store = session();
219
- token =
220
- (_b = (_a = store === null || store === void 0 ? void 0 : store.getItem(tokenKey(actorOf(axios), orderId))) !== null && _a !== void 0 ? _a : store === null || store === void 0 ? void 0 : store.getItem(tokenKey("guest", orderId))) !== null && _b !== void 0 ? _b : undefined;
221
- }
222
- catch (_c) {
223
- token = undefined;
224
- }
225
- }
226
- return token ? { "X-Doany-Access-Token": token } : {};
227
- }
164
+ const accessHeaders = (orderId, accessToken) => orderAccessHeaders(axios, appId, orderId, accessToken);
228
165
  return {
229
166
  create(params, options) {
230
- var _a, _b, _c, _d, _e;
231
- // Read once, before anything is awaited: the request goes out as this
232
- // actor, and its token must be kept for this actor even if the page has
233
- // signed in as someone else by the time the answer arrives.
234
- const actor = actorOf(axios);
235
- const wire = wireOf(params);
236
- const fingerprint = JSON.stringify([(_b = (_a = axios.defaults) === null || _a === void 0 ? void 0 : _a.baseURL) !== null && _b !== void 0 ? _b : "", appId, actor, wire]);
237
- const flight = `${(_c = options === null || options === void 0 ? void 0 : options.idempotencyKey) !== null && _c !== void 0 ? _c : ""}\u0000${fingerprint}`;
238
- // A second identical call while the first is still out is the same
239
- // attempt (a double click): it gets the first one's answer, not a
240
- // request of its own.
241
- const running = inFlight.get(flight);
242
- if (running)
243
- return running;
244
- const key = (_e = (_d = options === null || options === void 0 ? void 0 : options.idempotencyKey) !== null && _d !== void 0 ? _d : undecided.get(fingerprint)) !== null && _e !== void 0 ? _e : newKey();
245
- // Written on every attempt, not only the first: the window runs from the
246
- // LAST time this key went out, or a retry late in it would leave the next
247
- // one without the key of an order that may just have been placed.
248
- if (!(options === null || options === void 0 ? void 0 : options.idempotencyKey))
249
- undecided.set(fingerprint, key);
250
- // Only the attempt that put a key there may take it away: a page-keyed
251
- // attempt for the same things must not release the automatic one's key.
252
- const settle = () => {
253
- if (!(options === null || options === void 0 ? void 0 : options.idempotencyKey) && undecided.get(fingerprint) === key) {
254
- undecided.delete(fingerprint);
255
- }
256
- };
257
- const attempt = (async () => {
258
- var _a;
259
- try {
167
+ if (!rememberAttempts) {
168
+ // One call, one key: the caller's, else a new one. A function that
169
+ // retries passes its own key.
170
+ return (async () => {
171
+ var _a, _b;
260
172
  const data = (await axios.request({
261
173
  method: "POST",
262
- url: baseURL,
263
- data: wire,
264
- headers: { "Idempotency-Key": key },
174
+ url: await orders(),
175
+ data: wireOf(params, appId),
176
+ headers: { "Idempotency-Key": (_a = options === null || options === void 0 ? void 0 : options.idempotencyKey) !== null && _a !== void 0 ? _a : newKey() },
265
177
  }));
266
- // A 2xx that does not carry an order (a proxy's page, a mismatched
267
- // deployment) decides nothing: the key stays for the retry.
268
- if (typeof ((_a = data === null || data === void 0 ? void 0 : data.order) === null || _a === void 0 ? void 0 : _a.id) !== "string") {
178
+ if (typeof ((_b = data === null || data === void 0 ? void 0 : data.order) === null || _b === void 0 ? void 0 : _b.id) !== "string") {
269
179
  throw new Error("The order service answered without an order");
270
180
  }
271
- settle();
272
- remember(actor, data.order.id, data.access_token);
273
181
  return data;
274
- }
275
- catch (error) {
276
- // A 4xx is a decision (refused, invalid) — except 408 and 429, and
277
- // 401, which the rest of the client also treats as "try again"
278
- // (client.ts): a proxy can time out AFTER passing the request on.
279
- // Everything else may have placed the order, so the same key goes
280
- // out again next time; keeping a key too long costs nothing.
281
- const status = error === null || error === void 0 ? void 0 : error.status;
282
- if (typeof status === "number" &&
283
- status >= 400 &&
284
- status < 500 &&
285
- ![401, 408, 429].includes(status)) {
182
+ })();
183
+ }
184
+ return (async () => {
185
+ var _a, _b, _c, _d, _e;
186
+ // The actor is read after the path, with nothing awaited between it and
187
+ // the request: the request goes out as this actor, and its token must be
188
+ // kept for this actor even if the page signs in as someone else before
189
+ // the answer arrives. (Read before the path, a sign-in during the first
190
+ // public-settings read would file the order under the previous account.)
191
+ const url = await orders();
192
+ const actor = actorOf(axios);
193
+ const wire = wireOf(params, appId);
194
+ const fingerprint = JSON.stringify([(_b = (_a = axios.defaults) === null || _a === void 0 ? void 0 : _a.baseURL) !== null && _b !== void 0 ? _b : "", appId, actor, wire]);
195
+ const flight = `${(_c = options === null || options === void 0 ? void 0 : options.idempotencyKey) !== null && _c !== void 0 ? _c : ""}\u0000${fingerprint}`;
196
+ // A second identical call while the first is still out is the same
197
+ // attempt (a double click): it gets the first one's answer, not a
198
+ // request of its own.
199
+ const running = inFlight.get(flight);
200
+ if (running)
201
+ return running;
202
+ const key = (_e = (_d = options === null || options === void 0 ? void 0 : options.idempotencyKey) !== null && _d !== void 0 ? _d : undecided.get(fingerprint)) !== null && _e !== void 0 ? _e : newKey();
203
+ // Written on every attempt, not only the first: the window runs from the
204
+ // LAST time this key went out, or a retry late in it would leave the next
205
+ // one without the key of an order that may just have been placed.
206
+ if (!(options === null || options === void 0 ? void 0 : options.idempotencyKey))
207
+ undecided.set(fingerprint, key);
208
+ // Only the attempt that put a key there may take it away: a page-keyed
209
+ // attempt for the same things must not release the automatic one's key.
210
+ const settle = () => {
211
+ if (!(options === null || options === void 0 ? void 0 : options.idempotencyKey) && undecided.get(fingerprint) === key) {
212
+ undecided.delete(fingerprint);
213
+ }
214
+ };
215
+ const attempt = (async () => {
216
+ var _a;
217
+ try {
218
+ const data = (await axios.request({
219
+ method: "POST",
220
+ url,
221
+ data: wire,
222
+ headers: { "Idempotency-Key": key },
223
+ }));
224
+ // A 2xx that does not carry an order (a proxy's page, a mismatched
225
+ // deployment) decides nothing: the key stays for the retry.
226
+ if (typeof ((_a = data === null || data === void 0 ? void 0 : data.order) === null || _a === void 0 ? void 0 : _a.id) !== "string") {
227
+ throw new Error("The order service answered without an order");
228
+ }
286
229
  settle();
230
+ rememberOrderToken(axios, appId, actor, data.order.id, data.access_token);
231
+ return data;
232
+ }
233
+ catch (error) {
234
+ // A 4xx is a decision (refused, invalid) — except 408 and 429, and
235
+ // 401, which the rest of the client also treats as "try again"
236
+ // (client.ts): a proxy can time out AFTER passing the request on.
237
+ // Everything else may have placed the order, so the same key goes
238
+ // out again next time; keeping a key too long costs nothing.
239
+ const status = error === null || error === void 0 ? void 0 : error.status;
240
+ if (typeof status === "number" &&
241
+ status >= 400 &&
242
+ status < 500 &&
243
+ ![401, 408, 429].includes(status)) {
244
+ settle();
245
+ }
246
+ else if (!(options === null || options === void 0 ? void 0 : options.idempotencyKey)) {
247
+ // Still undecided: the window runs from now, however long the
248
+ // request itself was out.
249
+ undecided.set(fingerprint, key);
250
+ }
251
+ throw error;
287
252
  }
288
- else if (!(options === null || options === void 0 ? void 0 : options.idempotencyKey)) {
289
- // Still undecided: the window runs from now, however long the
290
- // request itself was out.
291
- undecided.set(fingerprint, key);
253
+ finally {
254
+ inFlight.delete(flight);
292
255
  }
293
- throw error;
294
- }
295
- finally {
296
- inFlight.delete(flight);
297
- }
256
+ })();
257
+ inFlight.set(flight, attempt);
258
+ return attempt;
298
259
  })();
299
- inFlight.set(flight, attempt);
300
- return attempt;
301
260
  },
302
261
  async list(params = {}) {
303
- const query = {};
304
- if (params.status)
305
- query.status = params.status;
306
- if (params.limit)
307
- query.limit = params.limit;
308
- if (params.skip)
309
- query.skip = params.skip;
310
- if (params.sort)
311
- query.sort = params.sort;
312
- const data = await axios.get(baseURL, { params: query });
313
- return data;
262
+ return (await axios.get(await orders(), { params: queryOf(params) }));
314
263
  },
315
264
  async get(orderId, options) {
316
- const data = await axios.get(`${baseURL}/${encodeURIComponent(orderId)}`, {
317
- headers: accessHeaders(orderId, options),
318
- });
319
- return data;
265
+ return (await axios.get(await orders(`/${seg(orderId)}`), {
266
+ headers: accessHeaders(orderId, options === null || options === void 0 ? void 0 : options.accessToken),
267
+ }));
268
+ },
269
+ async update(orderId, params) {
270
+ return (await axios.patch(await orders(`/${seg(orderId)}`), params));
320
271
  },
321
272
  async cancel(orderId, options) {
322
- const data = await axios.request({
273
+ const version = options === null || options === void 0 ? void 0 : options.version;
274
+ return (await axios.request({
275
+ method: "POST",
276
+ url: await orders(`/${seg(orderId)}/cancel`),
277
+ // The version is optional here: a customer's cancel sends none.
278
+ data: version === undefined ? undefined : { version },
279
+ headers: accessHeaders(orderId, options === null || options === void 0 ? void 0 : options.accessToken),
280
+ }));
281
+ },
282
+ async setFulfillment(orderId, { status, version }) {
283
+ return (await axios.put(await orders(`/${seg(orderId)}/fulfillment`), {
284
+ status,
285
+ version,
286
+ }));
287
+ },
288
+ async complete(orderId, { version }) {
289
+ return (await axios.request({
323
290
  method: "POST",
324
- url: `${baseURL}/${encodeURIComponent(orderId)}/cancel`,
325
- headers: accessHeaders(orderId, options),
326
- });
327
- return data;
291
+ url: await orders(`/${seg(orderId)}/complete`),
292
+ data: { version },
293
+ }));
328
294
  },
329
295
  };
330
296
  }
@@ -1,57 +1,104 @@
1
- export type OrderStatus = "open" | "completed" | "canceled";
2
- export type FulfillmentStatus = "pending" | "in_progress" | "completed" | "canceled";
1
+ import type { Address, Page, PageParams, VersionInput } from "./project.types";
2
+ /**
3
+ * - `pending`: placed on the site, waiting for its payment; expires after an hour unpaid.
4
+ * - `placed`: paid, free, or entered by the business. Has an order number.
5
+ * - `completed`: the business marked it done. Cannot be canceled any more.
6
+ * - `canceled`, `expired`.
7
+ */
8
+ export type OrderStatus = "pending" | "placed" | "completed" | "canceled" | "expired";
9
+ export type OrderChannel = "online" | "staff";
10
+ /** Delivery is marked for the whole order. */
11
+ export type FulfillmentStatus = "not_required" | "unfulfilled" | "fulfilled";
3
12
  /** A line of an order. Names, price and duration are as they were when it was placed. */
4
13
  export interface OrderItem {
5
14
  id: string;
6
- variant_id: string;
15
+ variant_id: string | null;
7
16
  item_name: string;
8
- variant_name: string;
17
+ variant_name: string | null;
9
18
  duration_minutes: number | null;
10
19
  unit_amount: number;
11
20
  quantity: number;
12
21
  line_amount: number;
13
- fulfillment_mode: "manual" | "calendar";
14
- fulfillment_status: FulfillmentStatus | null;
15
- fulfilled_at: string | null;
16
22
  }
17
23
  export interface OrderBuyer {
18
24
  name: string;
19
25
  email: string | null;
20
26
  phone: string | null;
21
27
  }
28
+ /** An address, plus who receives it (empty: the buyer). */
29
+ export interface ShippingAddress extends Address {
30
+ name: string | null;
31
+ phone: string | null;
32
+ }
22
33
  export interface Order {
23
34
  id: string;
24
- /** The number to show the customer. */
25
- order_number: string;
35
+ /** Consecutive per business and environment, given when the order is placed. Show it as `#12`. */
36
+ order_number: number | null;
26
37
  status: OrderStatus;
38
+ channel: OrderChannel;
39
+ /** When a `pending` order stops waiting for its payment. */
40
+ expires_at: string | null;
41
+ fulfillment_status: FulfillmentStatus;
27
42
  currency: string;
28
43
  /** In the currency's smallest unit (cents). */
29
44
  total_amount: number;
45
+ /** A test (preview) order or a real one. */
46
+ livemode: boolean;
47
+ /** The site it was placed on. */
48
+ app_id: string | null;
49
+ contact_id: string | null;
50
+ /** The account that placed it. */
51
+ user_id: string | null;
30
52
  buyer: OrderBuyer;
53
+ shipping_address: ShippingAddress | null;
54
+ customer_note: string | null;
55
+ /** Only the site's admin and service callers see it. */
56
+ internal_note?: string | null;
57
+ /** In the order they were bought in. */
31
58
  items: OrderItem[];
32
59
  payment: {
33
- /** `false` for a free order, which is `completed` as soon as it is placed. */
60
+ /** `false` for a free order, which is `placed` as soon as it is created. */
34
61
  required: boolean;
35
62
  amount_received: number;
36
63
  amount_refunded: number;
37
64
  };
65
+ /** Only the site's admin and service callers see it. */
66
+ created_by?: "customer" | "owner" | "system";
67
+ created_by_user_id?: string | null;
68
+ access?: {
69
+ issued: boolean;
70
+ revoked: boolean;
71
+ };
72
+ version: number;
38
73
  created_at: string;
74
+ updated_at: string;
75
+ placed_at: string | null;
39
76
  completed_at: string | null;
40
77
  canceled_at: string | null;
41
78
  }
42
79
  export interface OrderSummary {
43
80
  id: string;
44
- order_number: string;
81
+ order_number: number | null;
45
82
  status: OrderStatus;
83
+ channel: OrderChannel;
84
+ fulfillment_status: FulfillmentStatus;
46
85
  currency: string;
47
86
  total_amount: number;
87
+ livemode: boolean;
88
+ app_id: string | null;
89
+ contact_id: string | null;
90
+ user_id: string | null;
48
91
  buyer: {
49
92
  name: string;
50
93
  };
51
94
  created_at: string;
52
95
  }
53
96
  export interface CreateOrderParams {
54
- /** What is being bought. Prices are not sent: the order takes them from the catalog. */
97
+ /** `online` (default): a customer on the site. `staff`: the business entering one. */
98
+ channel?: OrderChannel;
99
+ /** The site it is placed on. Defaults to this client's app. */
100
+ app_id?: string | null;
101
+ /** 1–100 variants, each once. Prices are not sent: the order takes them from the catalog. */
55
102
  items: Array<{
56
103
  variant_id: string;
57
104
  quantity?: number;
@@ -62,6 +109,16 @@ export interface CreateOrderParams {
62
109
  email?: string;
63
110
  phone?: string;
64
111
  };
112
+ /** Required when something in it is shipped (`kind: "physical"`): at least `line` and `country`. */
113
+ shipping_address?: Partial<ShippingAddress>;
114
+ /** Up to 1000 characters. */
115
+ customer_note?: string;
116
+ /** `staff` only, and required there: a real order or a test. */
117
+ livemode?: boolean;
118
+ /** `staff` only: the contact it is for, instead of matching the buyer. */
119
+ contact_id?: string;
120
+ /** `staff` only. */
121
+ internal_note?: string;
65
122
  }
66
123
  export interface CreateOrderOptions {
67
124
  /**
@@ -75,34 +132,54 @@ export interface CreateOrderResult {
75
132
  order: Order;
76
133
  /**
77
134
  * Lets someone who is not signed in open this order again. The SDK keeps it
78
- * for the rest of the browser session, so `orders.get` and `orders.cancel`
79
- * work without passing it; put it in a link (after `#`) to reach the order
80
- * from elsewhere.
135
+ * for the rest of the browser session, so `orders.get`, `orders.cancel` and
136
+ * the payments calls work without passing it; put it in a link (after `#`)
137
+ * to reach the order from elsewhere.
81
138
  */
82
139
  access_token?: string;
83
- /** `false` when the order is free and already completed. */
140
+ /** `false` when the order is free and already placed. */
84
141
  payment_required: boolean;
85
142
  }
86
- export interface OrderListParams {
143
+ export interface OrderListParams extends PageParams {
87
144
  status?: OrderStatus;
88
- limit?: number;
89
- skip?: number;
90
- /** `created_at` (default, newest first), `total_amount`, `order_number`, `status`; `-` in front for descending. */
91
- sort?: string;
145
+ channel?: OrderChannel;
146
+ /** Orders placed on this site. */
147
+ app_id?: string;
148
+ /** The contact's orders, and those of contacts merged into it. */
149
+ contact_id?: string;
150
+ user_id?: string;
151
+ /** Order number, buyer name or email. */
152
+ q?: string;
153
+ created_from?: string;
154
+ created_to?: string;
92
155
  }
93
156
  export interface OrderAccessOptions {
94
157
  /** The order's `access_token`, when it came from a link rather than from this browser session. */
95
158
  accessToken?: string;
96
159
  }
160
+ export interface UpdateOrderParams extends VersionInput {
161
+ /** Not once the order is delivered, completed or closed. */
162
+ shipping_address?: Partial<ShippingAddress> | null;
163
+ internal_note?: string | null;
164
+ }
97
165
  /**
98
- * Orders placed on the site.
166
+ * Orders.
167
+ *
168
+ * Who sees an order: the account that placed it, accounts with the records
169
+ * of its contact, anyone holding its access token, the site's admin and
170
+ * service callers. Anyone else gets 404.
99
171
  */
100
172
  export interface OrdersModule {
101
173
  /**
102
174
  * Places an order for catalog variants. Works signed in or not.
103
175
  *
104
- * Rejects with status 409 and `code` `NOT_PURCHASABLE` (with `data.variant_id`)
105
- * when something in it cannot be bought, or `MIXED_CURRENCY`.
176
+ * Rejects with 409 `NOT_PURCHASABLE` (`details.variant_id`, `details.reason`)
177
+ * when something in it cannot be bought, 409 `MIXED_CURRENCY`, or 429
178
+ * `RATE_LIMITED`.
179
+ *
180
+ * An `online` order takes its environment (preview or published) from the
181
+ * page's `Origin`, which a backend function does not send: with
182
+ * `asServiceRole`, enter a `staff` order and say `livemode` instead.
106
183
  *
107
184
  * @example
108
185
  * ```typescript
@@ -113,13 +190,26 @@ export interface OrdersModule {
113
190
  * ```
114
191
  */
115
192
  create(params: CreateOrderParams, options?: CreateOrderOptions): Promise<CreateOrderResult>;
116
- /** The signed-in customer's orders, newest first. Rejects with status 401 when nobody is signed in. */
117
- list(params?: OrderListParams): Promise<OrderSummary[]>;
193
+ /** The orders the caller may see, newest first. Rejects with 401 when nobody is signed in. */
194
+ list(params?: OrderListParams): Promise<Page<OrderSummary>>;
195
+ /** One order. */
196
+ get(orderId: string, options?: OrderAccessOptions): Promise<Order>;
197
+ /** Changes the shipping address or the internal note. The site's admin and service callers only. */
198
+ update(orderId: string, params: UpdateOrderParams): Promise<Order>;
118
199
  /**
119
- * One order. Available to the signed-in customer who placed it, or to anyone
120
- * holding its access token; otherwise rejects with status 404.
200
+ * Cancels a `pending` or `placed` order (a customer: only one not paid yet).
201
+ * Canceling is not refunding. Rejects with 409 `INVALID_STATE` once the
202
+ * order is completed or expired, 409 `PAYMENT_UNRESOLVED` while a payment
203
+ * is still being confirmed.
121
204
  */
122
- get(orderId: string, options?: OrderAccessOptions): Promise<Order>;
123
- /** Cancels an order that is still `open`. Rejects with status 409 (`INVALID_STATE`) otherwise. */
124
- cancel(orderId: string, options?: OrderAccessOptions): Promise<Order>;
205
+ cancel(orderId: string, options?: OrderAccessOptions & Partial<VersionInput>): Promise<Order>;
206
+ /** Marks the whole order delivered, or takes that back. `placed` orders only. */
207
+ setFulfillment(orderId: string, params: VersionInput & {
208
+ status: "fulfilled" | "unfulfilled";
209
+ }): Promise<Order>;
210
+ /**
211
+ * Says the order is done. Nothing may still be owed (409 `INVALID_STATE`,
212
+ * `details.amount_due`); delivery is not required.
213
+ */
214
+ complete(orderId: string, params: VersionInput): Promise<Order>;
125
215
  }
@@ -1,5 +1,6 @@
1
1
  import { AxiosInstance } from "axios";
2
2
  import { PaymentsModule, ProductsModule } from "./payments.types";
3
+ import { ProjectScope } from "./project.js";
3
4
  /**
4
5
  * The product reads.
5
6
  *
@@ -21,4 +22,4 @@ export declare function createProductsModule(axios: AxiosInstance, appId: string
21
22
  *
22
23
  * @internal
23
24
  */
24
- export declare function createPaymentsModule(axios: AxiosInstance, appId: string): PaymentsModule;
25
+ export declare function createPaymentsModule(axios: AxiosInstance, appId: string, project: ProjectScope): PaymentsModule;
@@ -1,3 +1,5 @@
1
+ import { orderAccessHeaders } from "./order-access.js";
2
+ import { queryOf, seg } from "./project.js";
1
3
  /**
2
4
  * The product reads.
3
5
  *
@@ -47,7 +49,7 @@ export function createProductsModule(axios, appId) {
47
49
  *
48
50
  * @internal
49
51
  */
50
- export function createPaymentsModule(axios, appId) {
52
+ export function createPaymentsModule(axios, appId, project) {
51
53
  async function createCheckoutSession(params) {
52
54
  const data = await axios.request({
53
55
  method: "POST",
@@ -84,5 +86,30 @@ export function createPaymentsModule(axios, appId) {
84
86
  });
85
87
  return data;
86
88
  },
89
+ // Paying for an order: /projects/{project_id}/orders/{order_id}/...
90
+ // The order's access token rides along, as for orders.get.
91
+ async checkout(orderId, params = {}, options) {
92
+ const data = await axios.request({
93
+ method: "POST",
94
+ url: await project.path(`/orders/${seg(orderId)}/checkout`),
95
+ data: params,
96
+ headers: orderAccessHeaders(axios, appId, orderId, options === null || options === void 0 ? void 0 : options.accessToken),
97
+ });
98
+ return data;
99
+ },
100
+ async getForOrder(orderId, options) {
101
+ const data = await axios.get(await project.path(`/orders/${seg(orderId)}/payments`), {
102
+ headers: orderAccessHeaders(axios, appId, orderId, options === null || options === void 0 ? void 0 : options.accessToken),
103
+ });
104
+ return data;
105
+ },
106
+ async list(params = {}) {
107
+ const data = await axios.get(await project.path("/payments"), { params: queryOf(params) });
108
+ return data;
109
+ },
110
+ async get(paymentId) {
111
+ const data = await axios.get(await project.path(`/payments/${seg(paymentId)}`));
112
+ return data;
113
+ },
87
114
  };
88
115
  }