@caldik/client 0.1.2 → 0.1.4
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 +261 -21
- package/dist/index.d.mts +93 -21
- package/dist/index.mjs +331 -125
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# Caldik SDK
|
|
2
|
+
TThe official Caldik SDK for interacting with Caldik storefronts, products, carts, checkouts, and customer accounts.
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
## Installation
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
```bash
|
|
7
|
+
npm install @caldik/client
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Getting Started
|
|
11
|
+
|
|
12
|
+
### Caldik-hosted Storefront
|
|
6
13
|
|
|
7
14
|
No credentials or store configuration are required when running inside a Caldik-hosted storefront.
|
|
8
15
|
|
|
@@ -14,7 +21,7 @@ const caldik = new CaldikClient()
|
|
|
14
21
|
const { products } = await caldik.products.list()
|
|
15
22
|
```
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
### External Server
|
|
18
25
|
|
|
19
26
|
External integrations must explicitly provide a store ID and a private `sk_` API key.
|
|
20
27
|
|
|
@@ -27,40 +34,196 @@ const caldik = new CaldikClient({
|
|
|
27
34
|
})
|
|
28
35
|
```
|
|
29
36
|
|
|
37
|
+
> **Security:** Private API keys must only be used in trusted server-side environments. Never expose `sk_` keys in client-side code.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Orders and Items
|
|
41
|
+
|
|
42
|
+
### List Products
|
|
43
|
+
|
|
44
|
+
Retrieve products associated with the current store.
|
|
45
|
+
|
|
46
|
+
**Basic usage**
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
const { products } = await caldik.products.list()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Limit results**
|
|
53
|
+
|
|
54
|
+
Pass a number to limit the number of products returned.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const { products } = await caldik.products.list(20)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Get Product
|
|
61
|
+
|
|
62
|
+
Retrieve a specific product by its ID.
|
|
63
|
+
|
|
64
|
+
**Basic usage**
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const { product } = await caldik.products.get("product_123")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Get Category
|
|
71
|
+
|
|
72
|
+
Retrieve a specific category by its ID.
|
|
73
|
+
|
|
74
|
+
**Basic usage**
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const { category } = await caldik.categories.get("CAT_ID")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### List Categories
|
|
81
|
+
|
|
82
|
+
Retrieve categories associated with the current store.
|
|
83
|
+
|
|
84
|
+
**Basic usage**
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const { categories } = await caldik.categories.list()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Limit results**
|
|
91
|
+
|
|
92
|
+
Pass a number to limit the number of categories returned.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const { categories } = await caldik.categories.list(20)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Advanced usage**
|
|
99
|
+
|
|
100
|
+
Use an options object to configure the limit and request additional fields.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const { categories } = await caldik.categories.list({
|
|
104
|
+
limit: 20,
|
|
105
|
+
include: ["productCount", "startingPrice"],
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Optional fields**
|
|
110
|
+
|
|
111
|
+
The following fields are omitted by default unless explicitly requested using `include`:
|
|
112
|
+
|
|
113
|
+
| Field | Description |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `productCount` | Number of products associated with the category. |
|
|
116
|
+
| `startingPrice` | Lowest available product price in the category, in cents. |
|
|
117
|
+
|
|
118
|
+
|
|
30
119
|
## Cart and Discounts
|
|
31
120
|
|
|
32
121
|
### Cart Management
|
|
33
122
|
|
|
34
|
-
|
|
123
|
+
**Get the cart**
|
|
124
|
+
|
|
125
|
+
Refresh the current items and applied discount from the server.
|
|
35
126
|
|
|
36
127
|
```ts
|
|
37
|
-
const
|
|
128
|
+
const { items, discount } = await caldik.cart.get()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Add an item**
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const cart = await caldik.cart.add({
|
|
38
135
|
productId: "PRODUCT_...",
|
|
39
136
|
productVariantId: "PRODUCT_VARIANT_...",
|
|
40
137
|
quantity: 1,
|
|
41
138
|
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Update item quantity**
|
|
142
|
+
|
|
143
|
+
Set an item's quantity. A quantity of zero removes it.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
await caldik.cart.updateQuantity(cart.items[0].id, 3)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Remove an item**
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
await caldik.cart.remove(cart.items[0].id)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Clear the cart**
|
|
156
|
+
|
|
157
|
+
Remove all items and the applied discount from the persisted cart.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
await caldik.cart.clear()
|
|
161
|
+
```
|
|
42
162
|
|
|
43
|
-
|
|
44
|
-
caldik.cart.updateQuantity(cart.items[0].id, 3)
|
|
163
|
+
**Check a discount**
|
|
45
164
|
|
|
46
|
-
|
|
47
|
-
|
|
165
|
+
Get a price and discount quote for the current items without applying the code.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const quote = await caldik.cart.checkDiscount("SAVE10")
|
|
48
169
|
```
|
|
49
170
|
|
|
50
|
-
|
|
171
|
+
**Apply a discount**
|
|
51
172
|
|
|
52
|
-
|
|
173
|
+
Validate the code and save it to the current cart.
|
|
53
174
|
|
|
54
175
|
```ts
|
|
55
|
-
await caldik.cart.applyDiscount("SAVE10")
|
|
176
|
+
const discountedCart = await caldik.cart.applyDiscount("SAVE10")
|
|
56
177
|
```
|
|
57
178
|
|
|
58
|
-
|
|
179
|
+
**Remove the discount**
|
|
59
180
|
|
|
60
|
-
|
|
181
|
+
Remove the applied code while keeping the cart's items.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
await caldik.cart.removeDiscount()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Checkout the cart**
|
|
188
|
+
|
|
189
|
+
Create an order using the persisted cart. The API revalidates prices and discounts.
|
|
61
190
|
|
|
62
191
|
```ts
|
|
63
192
|
const result = await caldik.cart.checkout({
|
|
193
|
+
paymentMethod: "stripe",
|
|
194
|
+
termsAccepted: acceptedTermsCheckbox,
|
|
195
|
+
successUrl: "https://shop.example/success",
|
|
196
|
+
cancelUrl: "https://shop.example/cart",
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Server-rendered Storefronts
|
|
201
|
+
|
|
202
|
+
Create a client for each incoming request so shopper sessions stay isolated.
|
|
203
|
+
Pass the request and forward returned session cookies to the outgoing response:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const responseHeaders = new Headers()
|
|
207
|
+
const caldik = new CaldikClient({
|
|
208
|
+
request,
|
|
209
|
+
onSetCookie: (cookie) => responseHeaders.append("Set-Cookie", cookie),
|
|
210
|
+
// External servers also supply storeId and their private apiKey.
|
|
211
|
+
})
|
|
212
|
+
const cart = await caldik.cart.get()
|
|
213
|
+
// Include responseHeaders in your framework's HTTP response.
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Browser and server calls must share the same cookie context (for example through
|
|
217
|
+
a storefront API proxy). Cookies set only on a separate API hostname are not
|
|
218
|
+
included in requests to the storefront server. A private store key does not
|
|
219
|
+
replace a shopper's customer session.
|
|
220
|
+
|
|
221
|
+
### Checkout with Explicit Items
|
|
222
|
+
|
|
223
|
+
Use `caldik.checkout.create()` to create an order from explicitly provided items.
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const result = await caldik.checkout.create({
|
|
64
227
|
paymentMethod: "stripe",
|
|
65
228
|
items: [
|
|
66
229
|
{
|
|
@@ -96,11 +259,24 @@ Username authentication is available through the **Customer Usernames** plugin.
|
|
|
96
259
|
|
|
97
260
|
Customers can log in using a six-digit verification code sent to their email address.
|
|
98
261
|
|
|
262
|
+
**Send a verification code**
|
|
263
|
+
|
|
99
264
|
```ts
|
|
100
|
-
// Send a six-digit verification code
|
|
101
265
|
await caldik.customer.login("customer@example.com")
|
|
266
|
+
```
|
|
102
267
|
|
|
103
|
-
|
|
268
|
+
Alternatively, use an options object to configure the session.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
await caldik.customer.login({
|
|
272
|
+
email: "customer@example.com",
|
|
273
|
+
rememberMe: true, // Optional, defaults to false
|
|
274
|
+
})
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Verify the code**
|
|
278
|
+
|
|
279
|
+
```ts
|
|
104
280
|
await caldik.customer.login.verify(code)
|
|
105
281
|
```
|
|
106
282
|
|
|
@@ -112,6 +288,7 @@ Email and password authentication requires password login to be enabled in the s
|
|
|
112
288
|
await caldik.customer.login({
|
|
113
289
|
email: "customer@example.com",
|
|
114
290
|
password,
|
|
291
|
+
rememberMe: true, // Optional, defaults to false
|
|
115
292
|
})
|
|
116
293
|
```
|
|
117
294
|
|
|
@@ -125,11 +302,58 @@ Once installed, customers can authenticate using their username instead of their
|
|
|
125
302
|
await caldik.customer.login({
|
|
126
303
|
username: "customer",
|
|
127
304
|
password,
|
|
305
|
+
rememberMe: true, // Optional, defaults to false
|
|
128
306
|
})
|
|
129
307
|
```
|
|
130
308
|
|
|
131
309
|
> **Note:** Username authentication requires both the Customer Usernames plugin and password authentication to be enabled.
|
|
132
310
|
|
|
311
|
+
### Session Duration
|
|
312
|
+
|
|
313
|
+
All customer authentication methods support the optional `rememberMe` parameter.
|
|
314
|
+
|
|
315
|
+
| `rememberMe` | Session Duration |
|
|
316
|
+
|---|---|
|
|
317
|
+
| `false` (default) | 24 hours |
|
|
318
|
+
| `true` | 30 days |
|
|
319
|
+
|
|
320
|
+
Omit `rememberMe` to use the default session duration.
|
|
321
|
+
|
|
322
|
+
### Session Check
|
|
323
|
+
|
|
324
|
+
Retrieve the current customer's authentication status, account details, and session expiration.
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
const {
|
|
328
|
+
authenticated,
|
|
329
|
+
customer,
|
|
330
|
+
expiresAt,
|
|
331
|
+
} = await caldik.customer.session()
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Password Reset
|
|
335
|
+
|
|
336
|
+
Customers can reset their passwords using a verification code sent to their email address.
|
|
337
|
+
|
|
338
|
+
**Request a password reset**
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
const { challengeId } =
|
|
342
|
+
await caldik.customer.password.requestReset(email)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Password reset codes expire after **10 minutes**.
|
|
346
|
+
|
|
347
|
+
**Reset the password**
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
await caldik.customer.password.reset({
|
|
351
|
+
challengeId,
|
|
352
|
+
code,
|
|
353
|
+
password: newPassword,
|
|
354
|
+
})
|
|
355
|
+
```
|
|
356
|
+
|
|
133
357
|
## Customer Registration
|
|
134
358
|
|
|
135
359
|
Customers can register using their email address, with optional password and username support.
|
|
@@ -142,14 +366,18 @@ Password registration must be enabled in the store dashboard.
|
|
|
142
366
|
|
|
143
367
|
Register a customer using their email address and password.
|
|
144
368
|
|
|
369
|
+
**Register a customer**
|
|
370
|
+
|
|
145
371
|
```ts
|
|
146
|
-
// Register a new customer
|
|
147
372
|
await caldik.customer.register({
|
|
148
373
|
email: "customer@example.com",
|
|
149
374
|
password,
|
|
150
375
|
})
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Verify the email address**
|
|
151
379
|
|
|
152
|
-
|
|
380
|
+
```ts
|
|
153
381
|
await caldik.customer.register.verify(code)
|
|
154
382
|
```
|
|
155
383
|
|
|
@@ -159,15 +387,19 @@ To register customers with a username, install and enable the **Customer Usernam
|
|
|
159
387
|
|
|
160
388
|
Once installed, the registration endpoint accepts an additional `username` field.
|
|
161
389
|
|
|
390
|
+
**Register a customer with a username**
|
|
391
|
+
|
|
162
392
|
```ts
|
|
163
|
-
// Register a new customer with a username
|
|
164
393
|
await caldik.customer.register({
|
|
165
394
|
email: "customer@example.com",
|
|
166
395
|
password,
|
|
167
396
|
username: "customer",
|
|
168
397
|
})
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Verify the email address**
|
|
169
401
|
|
|
170
|
-
|
|
402
|
+
```ts
|
|
171
403
|
await caldik.customer.register.verify(code)
|
|
172
404
|
```
|
|
173
405
|
|
|
@@ -179,28 +411,36 @@ Retrieve customer authentication settings, checkout configuration, and available
|
|
|
179
411
|
|
|
180
412
|
### Customer Settings
|
|
181
413
|
|
|
414
|
+
Retrieve the store's customer authentication settings.
|
|
415
|
+
|
|
182
416
|
```ts
|
|
183
417
|
const { settings: loginSettings } = await caldik.customer.settings.get()
|
|
184
418
|
```
|
|
185
419
|
|
|
186
420
|
### Checkout Settings
|
|
187
421
|
|
|
422
|
+
Retrieve the store's checkout configuration.
|
|
423
|
+
|
|
188
424
|
```ts
|
|
189
425
|
const { settings: checkoutSettings } = await caldik.checkout.settings.get()
|
|
190
426
|
```
|
|
191
427
|
|
|
192
428
|
### Available Payment Methods
|
|
193
429
|
|
|
430
|
+
Retrieve the payment methods available for checkout.
|
|
431
|
+
|
|
194
432
|
```ts
|
|
195
433
|
const { paymentMethods } = await caldik.checkout.paymentmethods.list()
|
|
196
434
|
```
|
|
197
435
|
|
|
198
436
|
### Combined Example
|
|
199
437
|
|
|
438
|
+
Retrieve customer settings, checkout settings, and available payment methods together.
|
|
439
|
+
|
|
200
440
|
```ts
|
|
201
441
|
const { settings: loginSettings } = await caldik.customer.settings.get()
|
|
202
442
|
|
|
203
443
|
const { settings: checkoutSettings } = await caldik.checkout.settings.get()
|
|
204
444
|
|
|
205
445
|
const { paymentMethods } = await caldik.checkout.paymentmethods.list()
|
|
206
|
-
```
|
|
446
|
+
```
|
package/dist/index.d.mts
CHANGED
|
@@ -24,7 +24,10 @@ interface Category {
|
|
|
24
24
|
name: string;
|
|
25
25
|
slug: string;
|
|
26
26
|
description: string;
|
|
27
|
-
|
|
27
|
+
/** Present when startingPrice is included. Prefer startingPrice. */
|
|
28
|
+
price?: string;
|
|
29
|
+
productCount?: number;
|
|
30
|
+
startingPrice?: string | null;
|
|
28
31
|
logo: string | null;
|
|
29
32
|
banner: string | null;
|
|
30
33
|
metadata: CategoryMetadata[];
|
|
@@ -33,6 +36,11 @@ interface CategoriesResponse {
|
|
|
33
36
|
success: boolean;
|
|
34
37
|
categories: Category[];
|
|
35
38
|
}
|
|
39
|
+
type CategoryInclude = "productCount" | "startingPrice" | "product count" | "starting price";
|
|
40
|
+
interface CategoryListOptions {
|
|
41
|
+
limit?: number;
|
|
42
|
+
include?: readonly CategoryInclude[];
|
|
43
|
+
}
|
|
36
44
|
interface CategoryProduct {
|
|
37
45
|
id: string;
|
|
38
46
|
name: string;
|
|
@@ -245,10 +253,41 @@ interface CustomerPasswordResponse extends CustomerLoginVerifyResponse {
|
|
|
245
253
|
extensions?: Record<string, Record<string, string | number | boolean>>;
|
|
246
254
|
};
|
|
247
255
|
}
|
|
248
|
-
|
|
249
|
-
/** A username from an installed identity plugin, or the customer's email. */
|
|
256
|
+
type CustomerPasswordInput = ({
|
|
250
257
|
username: string;
|
|
258
|
+
email?: never;
|
|
259
|
+
} | {
|
|
260
|
+
email: string;
|
|
261
|
+
username?: never;
|
|
262
|
+
}) & {
|
|
251
263
|
password: string;
|
|
264
|
+
rememberMe?: boolean;
|
|
265
|
+
};
|
|
266
|
+
interface CustomerEmailLoginInput {
|
|
267
|
+
email: string;
|
|
268
|
+
rememberMe?: boolean;
|
|
269
|
+
}
|
|
270
|
+
interface CustomerSessionResponse {
|
|
271
|
+
success: boolean;
|
|
272
|
+
authenticated: boolean;
|
|
273
|
+
customer: {
|
|
274
|
+
id: string;
|
|
275
|
+
email: string | null;
|
|
276
|
+
} | null;
|
|
277
|
+
expiresAt: number | null;
|
|
278
|
+
}
|
|
279
|
+
interface CustomerPasswordResetResponse extends CustomerLoginResponse {
|
|
280
|
+
expiresAt: number;
|
|
281
|
+
message: string;
|
|
282
|
+
}
|
|
283
|
+
interface CustomerPasswordResetInput {
|
|
284
|
+
challengeId?: string;
|
|
285
|
+
code: string;
|
|
286
|
+
password: string;
|
|
287
|
+
}
|
|
288
|
+
interface CustomerPasswordResetVerifyResponse {
|
|
289
|
+
success: boolean;
|
|
290
|
+
message: string;
|
|
252
291
|
}
|
|
253
292
|
interface CustomerRegistrationInput {
|
|
254
293
|
email: string;
|
|
@@ -310,6 +349,8 @@ interface RequestOptions {
|
|
|
310
349
|
edgeCache?: boolean;
|
|
311
350
|
/** Internal authorization policy. Protected hosted calls are the default. */
|
|
312
351
|
access?: "public" | "runtime";
|
|
352
|
+
/** Scoped credentials used by stateful storefront resources. */
|
|
353
|
+
headers?: Record<string, string>;
|
|
313
354
|
}
|
|
314
355
|
//#endregion
|
|
315
356
|
//#region src/internal/client.d.ts
|
|
@@ -317,13 +358,22 @@ type HttpClientOptions = {
|
|
|
317
358
|
apiKey?: string;
|
|
318
359
|
apiUrl?: string;
|
|
319
360
|
storeId?: string;
|
|
361
|
+
request?: Request;
|
|
362
|
+
onSetCookie?: (cookie: string) => void | Promise<void>;
|
|
320
363
|
};
|
|
321
364
|
declare class CaldikHttpClient {
|
|
322
365
|
private readonly apiKey?;
|
|
323
366
|
private readonly apiUrl;
|
|
324
367
|
private readonly storeId?;
|
|
368
|
+
private readonly cookies;
|
|
369
|
+
private readonly onSetCookie?;
|
|
370
|
+
private readonly authenticationListeners;
|
|
325
371
|
constructor(options?: HttpClientOptions);
|
|
326
372
|
storeContextId(): string | null;
|
|
373
|
+
hasCartSession(): boolean;
|
|
374
|
+
onCustomerAuthentication(listener: () => Promise<unknown>): void;
|
|
375
|
+
customerAuthenticated(): Promise<void>;
|
|
376
|
+
private captureCookies;
|
|
327
377
|
private baseUrl;
|
|
328
378
|
private request;
|
|
329
379
|
get<T>(path: string, params?: Record<string, string | number>, options?: RequestOptions): Promise<T>;
|
|
@@ -334,7 +384,7 @@ declare class CaldikHttpClient {
|
|
|
334
384
|
declare class CategoriesApi {
|
|
335
385
|
private client;
|
|
336
386
|
constructor(client: CaldikHttpClient);
|
|
337
|
-
list(
|
|
387
|
+
list(options?: number | CategoryListOptions): Promise<CategoriesResponse>;
|
|
338
388
|
get(categoryId: string): Promise<CategoryResponse>;
|
|
339
389
|
}
|
|
340
390
|
//#endregion
|
|
@@ -381,7 +431,10 @@ declare class CustomerApi {
|
|
|
381
431
|
get: () => Promise<CustomerLoginSettingsResponse>;
|
|
382
432
|
};
|
|
383
433
|
readonly login: {
|
|
384
|
-
(email: string
|
|
434
|
+
(email: string, options?: {
|
|
435
|
+
rememberMe?: boolean;
|
|
436
|
+
}): Promise<CustomerLoginResponse>;
|
|
437
|
+
(input: CustomerEmailLoginInput): Promise<CustomerLoginResponse>;
|
|
385
438
|
(input: CustomerPasswordInput): Promise<CustomerPasswordResponse>;
|
|
386
439
|
} & {
|
|
387
440
|
/** Pass challengeId to resume a code request after reloading the page. */verify: (code: string, challengeId?: string | undefined) => Promise<CustomerLoginVerifyResponse>;
|
|
@@ -392,7 +445,13 @@ declare class CustomerApi {
|
|
|
392
445
|
} & {
|
|
393
446
|
/** Verify the email and atomically activate the account. Supports resuming after a reload. */verify: (code: string, challengeId?: string | undefined) => Promise<CustomerPasswordResponse>;
|
|
394
447
|
};
|
|
448
|
+
readonly password: {
|
|
449
|
+
requestReset: (email: string) => Promise<CustomerPasswordResetResponse>;
|
|
450
|
+
reset: (input: CustomerPasswordResetInput) => Promise<CustomerPasswordResetVerifyResponse>;
|
|
451
|
+
};
|
|
395
452
|
constructor(client: CaldikHttpClient);
|
|
453
|
+
/** Check a store-scoped session without raising an error for signed-out customers. */
|
|
454
|
+
session(): Promise<CustomerSessionResponse>;
|
|
396
455
|
/** Read the signed-in customer and their balances. */
|
|
397
456
|
me(): Promise<CustomerProfileResponse>;
|
|
398
457
|
}
|
|
@@ -428,10 +487,8 @@ declare class DiscountsApi {
|
|
|
428
487
|
}
|
|
429
488
|
//#endregion
|
|
430
489
|
//#region src/cart.d.ts
|
|
431
|
-
interface CartItem {
|
|
490
|
+
interface CartItem extends ProductCommerceItem {
|
|
432
491
|
id: string;
|
|
433
|
-
productId: string;
|
|
434
|
-
productVariantId?: string;
|
|
435
492
|
quantity: number;
|
|
436
493
|
}
|
|
437
494
|
interface CartDiscount {
|
|
@@ -446,24 +503,35 @@ interface CartSnapshot {
|
|
|
446
503
|
items: CartItem[];
|
|
447
504
|
discount: CartDiscount | null;
|
|
448
505
|
}
|
|
449
|
-
type CartCheckoutInput = Omit<CreateOrderInput, "items" | "discountCode">;
|
|
506
|
+
type CartCheckoutInput = Omit<CreateOrderInput, "items" | "discountCode" | "customerId">;
|
|
507
|
+
/** One persisted cart, automatically scoped to the current shopper session. */
|
|
450
508
|
declare class CartApi {
|
|
451
509
|
private client;
|
|
452
|
-
private
|
|
510
|
+
private cache;
|
|
511
|
+
private queue;
|
|
512
|
+
private initialized;
|
|
453
513
|
constructor(client: CaldikHttpClient);
|
|
454
|
-
private
|
|
455
|
-
private
|
|
514
|
+
private storage;
|
|
515
|
+
private key;
|
|
516
|
+
private readCache;
|
|
517
|
+
private persistCache;
|
|
456
518
|
private write;
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
private
|
|
519
|
+
private enqueue;
|
|
520
|
+
private options;
|
|
521
|
+
private resolve;
|
|
522
|
+
private reset;
|
|
523
|
+
private mutate;
|
|
524
|
+
private save;
|
|
525
|
+
private quote;
|
|
526
|
+
get(): Promise<CartSnapshot>;
|
|
527
|
+
add(item: ProductCommerceItem): Promise<CartSnapshot>;
|
|
528
|
+
updateQuantity(itemId: string, nextQuantity: number): Promise<CartSnapshot>;
|
|
529
|
+
remove(itemId: string): Promise<CartSnapshot>;
|
|
530
|
+
clear(): Promise<CartSnapshot>;
|
|
463
531
|
checkDiscount(code: string): Promise<DiscountQuoteResponse>;
|
|
464
532
|
applyDiscount(code: string): Promise<CartSnapshot>;
|
|
465
|
-
removeDiscount(): CartSnapshot
|
|
466
|
-
checkout(input
|
|
533
|
+
removeDiscount(): Promise<CartSnapshot>;
|
|
534
|
+
checkout(input?: CartCheckoutInput): Promise<OrderResponse>;
|
|
467
535
|
}
|
|
468
536
|
//#endregion
|
|
469
537
|
//#region src/index.d.ts
|
|
@@ -473,6 +541,10 @@ interface CaldikClientOptions {
|
|
|
473
541
|
/** Private API key for external server integrations. Never use in browsers. */
|
|
474
542
|
apiKey?: string;
|
|
475
543
|
apiUrl?: string;
|
|
544
|
+
/** Incoming storefront request. Create one client per server request to isolate shoppers. */
|
|
545
|
+
request?: Request;
|
|
546
|
+
/** Forward session cookies to the outgoing storefront response in server code. */
|
|
547
|
+
onSetCookie?: (cookie: string) => void | Promise<void>;
|
|
476
548
|
}
|
|
477
549
|
declare class CaldikClient {
|
|
478
550
|
readonly categories: CategoriesApi;
|
|
@@ -487,4 +559,4 @@ declare class CaldikClient {
|
|
|
487
559
|
constructor(options?: CaldikClientOptions);
|
|
488
560
|
}
|
|
489
561
|
//#endregion
|
|
490
|
-
export { AppliedDiscount, CaldikClient, CaldikClientOptions, CartApi, CartCheckoutInput, CartDiscount, CartItem, CartSnapshot, CategoriesResponse, Category, CategoryDetails, CategoryMetadata, CategoryProduct, CategoryResponse, CheckDiscountInput, CheckoutPayment, CheckoutPaymentMethod, CheckoutPaymentMethodsResponse, CheckoutPaymentProviderId, CheckoutSettings, CheckoutSettingsResponse, CommerceItem, CreateOrderInput, CreateSubscriptionInput, CustomerLoginResponse, CustomerLoginSettings, CustomerLoginSettingsResponse, CustomerLoginVerifyResponse, CustomerPasswordInput, CustomerPasswordResponse, CustomerProfileResponse, CustomerRegistrationInput, CustomerRegistrationResponse, DiscountQuoteResponse, DiscountsApi, ManualCommerceItem, OrderResponse, PaymentProviderId, Product, ProductCommerceItem, ProductCustomField, ProductCustomFieldInputType, ProductCustomFieldOption, ProductMetadata, ProductResponse, ProductTag, ProductVariant, ProductsResponse, Review, ReviewsResponse, Store, StoreResponse, StoreWebhookEvent, StoreWebhookEventName, StorefrontMetadata, StorefrontOrder, SubscriptionPaymentProviderId, SubscriptionResponse, checkoutPaymentProviderIds, paymentProviderIds, storeWebhookEventNames, subscriptionPaymentProviderIds };
|
|
562
|
+
export { AppliedDiscount, CaldikClient, CaldikClientOptions, CartApi, CartCheckoutInput, CartDiscount, CartItem, CartSnapshot, CategoriesResponse, Category, CategoryDetails, CategoryInclude, CategoryListOptions, CategoryMetadata, CategoryProduct, CategoryResponse, CheckDiscountInput, CheckoutPayment, CheckoutPaymentMethod, CheckoutPaymentMethodsResponse, CheckoutPaymentProviderId, CheckoutSettings, CheckoutSettingsResponse, CommerceItem, CreateOrderInput, CreateSubscriptionInput, CustomerEmailLoginInput, CustomerLoginResponse, CustomerLoginSettings, CustomerLoginSettingsResponse, CustomerLoginVerifyResponse, CustomerPasswordInput, CustomerPasswordResetInput, CustomerPasswordResetResponse, CustomerPasswordResetVerifyResponse, CustomerPasswordResponse, CustomerProfileResponse, CustomerRegistrationInput, CustomerRegistrationResponse, CustomerSessionResponse, DiscountQuoteResponse, DiscountsApi, ManualCommerceItem, OrderResponse, PaymentProviderId, Product, ProductCommerceItem, ProductCustomField, ProductCustomFieldInputType, ProductCustomFieldOption, ProductMetadata, ProductResponse, ProductTag, ProductVariant, ProductsResponse, Review, ReviewsResponse, Store, StoreResponse, StoreWebhookEvent, StoreWebhookEventName, StorefrontMetadata, StorefrontOrder, SubscriptionPaymentProviderId, SubscriptionResponse, checkoutPaymentProviderIds, paymentProviderIds, storeWebhookEventNames, subscriptionPaymentProviderIds };
|
package/dist/index.mjs
CHANGED
|
@@ -26,6 +26,14 @@ function defaultApiUrl(browser) {
|
|
|
26
26
|
//#region src/internal/client.ts
|
|
27
27
|
const STORE_ID_PATTERN = /^STORE_[a-f0-9]{32}$/i;
|
|
28
28
|
const STOREFRONT_STORE_ID_ATTRIBUTE = "data-caldik-store-id";
|
|
29
|
+
var CaldikHttpError = class extends Error {
|
|
30
|
+
status;
|
|
31
|
+
constructor(message, status) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.status = status;
|
|
34
|
+
this.name = "CaldikHttpError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
29
37
|
function isBrowserRuntime() {
|
|
30
38
|
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
31
39
|
}
|
|
@@ -48,9 +56,17 @@ var CaldikHttpClient = class {
|
|
|
48
56
|
apiKey;
|
|
49
57
|
apiUrl;
|
|
50
58
|
storeId;
|
|
59
|
+
cookies = /* @__PURE__ */ new Map();
|
|
60
|
+
onSetCookie;
|
|
61
|
+
authenticationListeners = /* @__PURE__ */ new Set();
|
|
51
62
|
constructor(options = {}) {
|
|
52
63
|
this.apiKey = options.apiKey?.trim() || void 0;
|
|
53
64
|
this.apiUrl = options.apiUrl || defaultApiUrl(isBrowserRuntime());
|
|
65
|
+
this.onSetCookie = options.onSetCookie;
|
|
66
|
+
if (!isBrowserRuntime()) for (const part of (options.request?.headers.get("Cookie") || "").split(";")) {
|
|
67
|
+
const separator = part.indexOf("=");
|
|
68
|
+
if (separator > 0) this.cookies.set(part.slice(0, separator).trim(), part.slice(separator + 1).trim());
|
|
69
|
+
}
|
|
54
70
|
const configuredStoreId = options.storeId?.trim() || void 0;
|
|
55
71
|
if (this.apiKey && !this.apiKey.startsWith("sk_")) throw new Error("Caldik external integrations require a private sk_ API key");
|
|
56
72
|
if (configuredStoreId && !STORE_ID_PATTERN.test(configuredStoreId)) throw new Error("Caldik storeId is invalid");
|
|
@@ -60,6 +76,31 @@ var CaldikHttpClient = class {
|
|
|
60
76
|
storeContextId() {
|
|
61
77
|
return this.storeId || boundRuntimeStoreId();
|
|
62
78
|
}
|
|
79
|
+
hasCartSession() {
|
|
80
|
+
return this.cookies.has(`caldik_cart_session_${this.storeContextId()}`);
|
|
81
|
+
}
|
|
82
|
+
onCustomerAuthentication(listener) {
|
|
83
|
+
this.authenticationListeners.add(listener);
|
|
84
|
+
}
|
|
85
|
+
async customerAuthenticated() {
|
|
86
|
+
await Promise.allSettled([...this.authenticationListeners].map((listener) => listener()));
|
|
87
|
+
}
|
|
88
|
+
async captureCookies(response) {
|
|
89
|
+
for (const cookie of response.headers.getSetCookie()) {
|
|
90
|
+
const [pair = "", ...attributes] = cookie.split(";");
|
|
91
|
+
const separator = pair.indexOf("=");
|
|
92
|
+
if (separator < 1) continue;
|
|
93
|
+
const name = pair.slice(0, separator).trim();
|
|
94
|
+
if (attributes.some((attribute) => {
|
|
95
|
+
const [key, ...value] = attribute.trim().split("=");
|
|
96
|
+
if (key?.toLowerCase() === "max-age") return Number(value.join("=")) <= 0;
|
|
97
|
+
if (key?.toLowerCase() === "expires") return Date.parse(value.join("=")) <= Date.now();
|
|
98
|
+
return false;
|
|
99
|
+
})) this.cookies.delete(name);
|
|
100
|
+
else this.cookies.set(name, pair.slice(separator + 1).trim());
|
|
101
|
+
await this.onSetCookie?.(cookie);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
63
104
|
baseUrl() {
|
|
64
105
|
if (isBrowserRuntime()) {
|
|
65
106
|
const storeId = this.storeId;
|
|
@@ -76,8 +117,9 @@ var CaldikHttpClient = class {
|
|
|
76
117
|
async request(method, path, input, params, options) {
|
|
77
118
|
const url = new URL(`${this.baseUrl()}${path}`);
|
|
78
119
|
if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, String(value));
|
|
79
|
-
const headers = {};
|
|
120
|
+
const headers = { ...options?.headers };
|
|
80
121
|
if (!isBrowserRuntime()) headers["User-Agent"] = "CaldikClient/0.0.2";
|
|
122
|
+
if (!isBrowserRuntime() && options?.access === "public" && this.cookies.size) headers.Cookie = [...this.cookies].map(([name, value]) => `${name}=${value}`).join("; ");
|
|
81
123
|
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
82
124
|
else if (options?.access === "public" && (!isBrowserRuntime() || isLocalBrowserRuntime())) headers["X-Caldik-Public-Request"] = "1";
|
|
83
125
|
if (input !== void 0) headers["Content-Type"] = "application/json";
|
|
@@ -89,10 +131,10 @@ var CaldikHttpClient = class {
|
|
|
89
131
|
cache: "default",
|
|
90
132
|
credentials: "include"
|
|
91
133
|
});
|
|
134
|
+
if (!isBrowserRuntime() && options?.access === "public") await this.captureCookies(response);
|
|
92
135
|
if (!response.ok) {
|
|
93
136
|
const error = await response.clone().json().catch(() => null);
|
|
94
|
-
|
|
95
|
-
throw new Error(message);
|
|
137
|
+
throw new CaldikHttpError(typeof error?.message === "string" && error.message ? error.message : `${response.status} ${response.statusText}`.trim(), response.status);
|
|
96
138
|
}
|
|
97
139
|
return response.json();
|
|
98
140
|
}
|
|
@@ -110,8 +152,12 @@ var CategoriesApi = class {
|
|
|
110
152
|
constructor(client) {
|
|
111
153
|
this.client = client;
|
|
112
154
|
}
|
|
113
|
-
list(
|
|
114
|
-
|
|
155
|
+
list(options) {
|
|
156
|
+
const { limit, include } = typeof options === "number" ? { limit: options } : options ?? {};
|
|
157
|
+
return this.client.get("/categories", {
|
|
158
|
+
...limit !== void 0 ? { limit } : {},
|
|
159
|
+
...include?.length ? { include: include.join(",") } : {}
|
|
160
|
+
}, { access: "public" });
|
|
115
161
|
}
|
|
116
162
|
get(categoryId) {
|
|
117
163
|
return this.client.get(`/categories/${categoryId}`, void 0, { access: "public" });
|
|
@@ -178,13 +224,40 @@ var CustomerApi = class {
|
|
|
178
224
|
settings;
|
|
179
225
|
login;
|
|
180
226
|
register;
|
|
227
|
+
password;
|
|
181
228
|
constructor(client) {
|
|
182
229
|
this.client = client;
|
|
183
230
|
this.settings = { get: () => this.client.get("/customers/settings", void 0, { access: "public" }) };
|
|
231
|
+
let pendingResetId;
|
|
232
|
+
this.password = {
|
|
233
|
+
requestReset: async (email) => {
|
|
234
|
+
const response = await client.post("/customers/password/reset", { email }, { access: "public" });
|
|
235
|
+
pendingResetId = response.challengeId;
|
|
236
|
+
return response;
|
|
237
|
+
},
|
|
238
|
+
reset: async (input) => {
|
|
239
|
+
const challengeId = input.challengeId ?? pendingResetId;
|
|
240
|
+
if (!challengeId) throw new Error("Request a password reset first or provide its challengeId");
|
|
241
|
+
if (!/^\d{6}$/.test(input.code)) throw new Error("A six-digit reset code is required");
|
|
242
|
+
const response = await client.post("/customers/password/reset/verify", {
|
|
243
|
+
...input,
|
|
244
|
+
challengeId
|
|
245
|
+
}, { access: "public" });
|
|
246
|
+
if (pendingResetId === challengeId) pendingResetId = void 0;
|
|
247
|
+
return response;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
184
250
|
let pendingChallengeId;
|
|
185
|
-
async function login(input) {
|
|
186
|
-
if (typeof input !== "string"
|
|
187
|
-
|
|
251
|
+
async function login(input, options) {
|
|
252
|
+
if (typeof input !== "string" && "password" in input) {
|
|
253
|
+
const response = await client.post("/customers/login/password", input, { access: "public" });
|
|
254
|
+
await client.customerAuthenticated();
|
|
255
|
+
return response;
|
|
256
|
+
}
|
|
257
|
+
const response = await client.post("/customers/login", typeof input === "string" ? {
|
|
258
|
+
email: input,
|
|
259
|
+
...options
|
|
260
|
+
} : input, { access: "public" });
|
|
188
261
|
pendingChallengeId = response.challengeId;
|
|
189
262
|
return response;
|
|
190
263
|
}
|
|
@@ -198,6 +271,7 @@ verify: async (code, challengeId = pendingChallengeId) => {
|
|
|
198
271
|
challengeId
|
|
199
272
|
}, { access: "public" });
|
|
200
273
|
if (pendingChallengeId === challengeId) pendingChallengeId = void 0;
|
|
274
|
+
await client.customerAuthenticated();
|
|
201
275
|
return response;
|
|
202
276
|
} });
|
|
203
277
|
let pendingRegistrationId;
|
|
@@ -216,9 +290,14 @@ verify: async (code, challengeId = pendingRegistrationId) => {
|
|
|
216
290
|
challengeId
|
|
217
291
|
}, { access: "public" });
|
|
218
292
|
if (pendingRegistrationId === challengeId) pendingRegistrationId = void 0;
|
|
293
|
+
await client.customerAuthenticated();
|
|
219
294
|
return response;
|
|
220
295
|
} });
|
|
221
296
|
}
|
|
297
|
+
/** Check a store-scoped session without raising an error for signed-out customers. */
|
|
298
|
+
session() {
|
|
299
|
+
return this.client.get("/customers/session", void 0, { access: "public" });
|
|
300
|
+
}
|
|
222
301
|
/** Read the signed-in customer and their balances. */
|
|
223
302
|
me() {
|
|
224
303
|
return this.client.get("/customers/me", void 0, { access: "public" });
|
|
@@ -248,20 +327,18 @@ var DiscountsApi = class {
|
|
|
248
327
|
};
|
|
249
328
|
//#endregion
|
|
250
329
|
//#region src/cart.ts
|
|
251
|
-
const CART_VERSION = 1;
|
|
252
330
|
const MAX_QUANTITY = 999999;
|
|
253
|
-
function
|
|
254
|
-
return item.productVariantId ? `${item.productId}:${item.productVariantId}` : item.productId;
|
|
255
|
-
}
|
|
256
|
-
function quantity(value, allowZero = false) {
|
|
257
|
-
const normalized = value ?? 1;
|
|
331
|
+
function quantity(value = 1, allowZero = false) {
|
|
258
332
|
const minimum = allowZero ? 0 : 1;
|
|
259
|
-
if (!Number.isInteger(
|
|
260
|
-
return
|
|
333
|
+
if (!Number.isInteger(value) || value < minimum || value > MAX_QUANTITY) throw new Error(`Cart quantity must be an integer between ${minimum} and ${MAX_QUANTITY}`);
|
|
334
|
+
return value;
|
|
261
335
|
}
|
|
262
336
|
function cloneSnapshot(state) {
|
|
263
337
|
return {
|
|
264
|
-
items: state.items.map((item) => ({
|
|
338
|
+
items: state.items.map((item) => ({
|
|
339
|
+
...item,
|
|
340
|
+
...item.customFields ? { customFields: { ...item.customFields } } : {}
|
|
341
|
+
})),
|
|
265
342
|
discount: state.discount ? { ...state.discount } : null
|
|
266
343
|
};
|
|
267
344
|
}
|
|
@@ -272,140 +349,267 @@ function isCartItem(value) {
|
|
|
272
349
|
}
|
|
273
350
|
function isCartDiscount(value) {
|
|
274
351
|
if (!value || typeof value !== "object") return false;
|
|
275
|
-
|
|
276
|
-
|
|
352
|
+
return [
|
|
353
|
+
"code",
|
|
354
|
+
"percentage",
|
|
355
|
+
"subtotal",
|
|
356
|
+
"discountAmount",
|
|
357
|
+
"total",
|
|
358
|
+
"currency"
|
|
359
|
+
].every((key) => typeof value[key] === "string");
|
|
277
360
|
}
|
|
361
|
+
function isReference(value) {
|
|
362
|
+
if (!value || typeof value !== "object") return false;
|
|
363
|
+
const reference = value;
|
|
364
|
+
return typeof reference.id === "string" && /^CART_[a-f0-9]{32}$/.test(reference.id) && (reference.token == null || typeof reference.token === "string");
|
|
365
|
+
}
|
|
366
|
+
function commerceItems(items) {
|
|
367
|
+
return items.map(({ id: _id, ...item }) => item);
|
|
368
|
+
}
|
|
369
|
+
function discountFromQuote(quote) {
|
|
370
|
+
return {
|
|
371
|
+
...quote.discount,
|
|
372
|
+
subtotal: quote.subtotal,
|
|
373
|
+
discountAmount: quote.discountAmount,
|
|
374
|
+
total: quote.total,
|
|
375
|
+
currency: quote.currency
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
/** One persisted cart, automatically scoped to the current shopper session. */
|
|
278
379
|
var CartApi = class {
|
|
279
380
|
client;
|
|
280
|
-
|
|
281
|
-
version:
|
|
282
|
-
|
|
283
|
-
|
|
381
|
+
cache = {
|
|
382
|
+
version: 2,
|
|
383
|
+
reference: null,
|
|
384
|
+
snapshot: {
|
|
385
|
+
items: [],
|
|
386
|
+
discount: null
|
|
387
|
+
}
|
|
284
388
|
};
|
|
389
|
+
queue = Promise.resolve();
|
|
390
|
+
initialized = false;
|
|
285
391
|
constructor(client) {
|
|
286
392
|
this.client = client;
|
|
393
|
+
client.onCustomerAuthentication(() => {
|
|
394
|
+
this.readCache();
|
|
395
|
+
return this.initialized || this.cache.reference || this.cache.snapshot.items.length || client.hasCartSession() ? this.enqueue(async () => {
|
|
396
|
+
await this.resolve();
|
|
397
|
+
}) : Promise.resolve();
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
storage() {
|
|
401
|
+
try {
|
|
402
|
+
return typeof window !== "undefined" && typeof document !== "undefined" && typeof localStorage !== "undefined" ? localStorage : null;
|
|
403
|
+
} catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
287
406
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
if (
|
|
297
|
-
const
|
|
298
|
-
if (
|
|
299
|
-
version:
|
|
407
|
+
key() {
|
|
408
|
+
return `caldik:cart:${this.client.storeContextId()}`;
|
|
409
|
+
}
|
|
410
|
+
readCache() {
|
|
411
|
+
const storage = this.storage();
|
|
412
|
+
if (!storage) return;
|
|
413
|
+
try {
|
|
414
|
+
const parsed = JSON.parse(storage.getItem(this.key()) || "null");
|
|
415
|
+
if (parsed?.version === 2) {
|
|
416
|
+
const snapshot = parsed.snapshot;
|
|
417
|
+
if (snapshot && Array.isArray(snapshot.items) && snapshot.items.every(isCartItem) && (parsed.reference === null || isReference(parsed.reference))) this.cache = {
|
|
418
|
+
version: 2,
|
|
419
|
+
reference: parsed.reference,
|
|
420
|
+
snapshot: cloneSnapshot({
|
|
421
|
+
items: snapshot.items,
|
|
422
|
+
discount: isCartDiscount(snapshot.discount) ? snapshot.discount : null
|
|
423
|
+
})
|
|
424
|
+
};
|
|
425
|
+
} else if (parsed?.version === 1 && Array.isArray(parsed.items) && parsed.items.every(isCartItem)) this.cache = {
|
|
426
|
+
version: 2,
|
|
427
|
+
reference: null,
|
|
428
|
+
snapshot: cloneSnapshot({
|
|
300
429
|
items: parsed.items,
|
|
301
430
|
discount: isCartDiscount(parsed.discount) ? parsed.discount : null
|
|
302
|
-
}
|
|
431
|
+
})
|
|
432
|
+
};
|
|
433
|
+
if (!this.cache.reference) {
|
|
434
|
+
const legacy = JSON.parse(storage.getItem(`caldik:server-cart:${this.client.storeContextId()}`) || "null");
|
|
435
|
+
if (isReference(legacy)) this.cache.reference = legacy;
|
|
303
436
|
}
|
|
304
437
|
} catch {}
|
|
305
|
-
return this.memory;
|
|
306
438
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const key = this.storageKey();
|
|
313
|
-
if (key && typeof localStorage !== "undefined") try {
|
|
314
|
-
localStorage.setItem(key, JSON.stringify(this.memory));
|
|
439
|
+
persistCache() {
|
|
440
|
+
try {
|
|
441
|
+
const storage = this.storage();
|
|
442
|
+
storage?.setItem(this.key(), JSON.stringify(this.cache));
|
|
443
|
+
storage?.removeItem(`caldik:server-cart:${this.client.storeContextId()}`);
|
|
315
444
|
} catch {}
|
|
316
|
-
|
|
445
|
+
}
|
|
446
|
+
write(cart, token = this.cache.reference?.token, discount = null) {
|
|
447
|
+
this.cache = {
|
|
448
|
+
version: 2,
|
|
449
|
+
reference: {
|
|
450
|
+
id: cart.id,
|
|
451
|
+
token: cart.customerId ? null : token
|
|
452
|
+
},
|
|
453
|
+
snapshot: cloneSnapshot({
|
|
454
|
+
items: cart.items,
|
|
455
|
+
discount
|
|
456
|
+
})
|
|
457
|
+
};
|
|
458
|
+
this.persistCache();
|
|
459
|
+
return cloneSnapshot(this.cache.snapshot);
|
|
460
|
+
}
|
|
461
|
+
enqueue(operation) {
|
|
462
|
+
const run = () => {
|
|
463
|
+
const locks = this.storage() && typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
464
|
+
return locks ? locks.request(this.key(), operation) : operation();
|
|
465
|
+
};
|
|
466
|
+
const result = this.queue.then(run, run);
|
|
467
|
+
this.queue = result.catch(() => void 0);
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
options() {
|
|
471
|
+
return {
|
|
472
|
+
access: "public",
|
|
473
|
+
...this.cache.reference?.token ? { headers: { "X-Caldik-Cart-Token": this.cache.reference.token } } : {}
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
async resolve(checkout = false, seed = true) {
|
|
477
|
+
this.readCache();
|
|
478
|
+
const input = this.cache.reference ? { cartId: this.cache.reference.id } : seed && this.cache.snapshot.items.length ? {
|
|
479
|
+
items: commerceItems(this.cache.snapshot.items),
|
|
480
|
+
discountCode: this.cache.snapshot.discount?.code
|
|
481
|
+
} : {};
|
|
482
|
+
const response = await this.client.post("/carts/session", {
|
|
483
|
+
...input,
|
|
484
|
+
...checkout ? { checkout: true } : {}
|
|
485
|
+
}, this.options());
|
|
486
|
+
this.initialized = true;
|
|
487
|
+
this.write(response.cart, response.token ?? null);
|
|
488
|
+
if (!checkout && response.cart.status === "checked_out") {
|
|
489
|
+
this.reset();
|
|
490
|
+
return this.resolve(false, false);
|
|
491
|
+
}
|
|
492
|
+
return response.cart;
|
|
493
|
+
}
|
|
494
|
+
reset() {
|
|
495
|
+
this.cache = {
|
|
496
|
+
version: 2,
|
|
497
|
+
reference: null,
|
|
498
|
+
snapshot: {
|
|
499
|
+
items: [],
|
|
500
|
+
discount: null
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
this.persistCache();
|
|
504
|
+
}
|
|
505
|
+
async mutate(operation, seed = true) {
|
|
506
|
+
let cart = await this.resolve(false, seed);
|
|
507
|
+
let response;
|
|
508
|
+
try {
|
|
509
|
+
response = await operation(cart);
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (!(error instanceof CaldikHttpError) || error.status !== 409) throw error;
|
|
512
|
+
const refreshed = await this.resolve();
|
|
513
|
+
if (refreshed.id !== cart.id || refreshed.status !== "active") throw error;
|
|
514
|
+
cart = refreshed;
|
|
515
|
+
response = await operation(cart);
|
|
516
|
+
}
|
|
517
|
+
return this.write(response.cart);
|
|
518
|
+
}
|
|
519
|
+
save(cart, items, discountCode) {
|
|
520
|
+
return this.client.post(`/carts/${cart.id}`, {
|
|
521
|
+
items: commerceItems(items),
|
|
522
|
+
version: cart.version,
|
|
523
|
+
discountCode
|
|
524
|
+
}, this.options());
|
|
525
|
+
}
|
|
526
|
+
quote(cart, code) {
|
|
527
|
+
if (!cart.items.length) throw new Error("Cannot check a discount on an empty cart");
|
|
528
|
+
return this.client.post("/discounts/check", {
|
|
529
|
+
code,
|
|
530
|
+
items: commerceItems(cart.items)
|
|
531
|
+
}, { access: "public" });
|
|
317
532
|
}
|
|
318
533
|
get() {
|
|
319
|
-
return
|
|
534
|
+
return this.enqueue(async () => {
|
|
535
|
+
let cart = await this.resolve();
|
|
536
|
+
let discount = null;
|
|
537
|
+
if (cart.discountCode) try {
|
|
538
|
+
discount = discountFromQuote(await this.quote(cart, cart.discountCode));
|
|
539
|
+
} catch (error) {
|
|
540
|
+
if (!(error instanceof CaldikHttpError) || ![
|
|
541
|
+
400,
|
|
542
|
+
404,
|
|
543
|
+
422
|
|
544
|
+
].includes(error.status)) throw error;
|
|
545
|
+
cart = (await this.save(cart, cart.items, null)).cart;
|
|
546
|
+
}
|
|
547
|
+
return this.write(cart, this.cache.reference?.token, discount);
|
|
548
|
+
});
|
|
320
549
|
}
|
|
321
550
|
add(item) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
551
|
+
return this.enqueue(async () => {
|
|
552
|
+
if (!item.productId.trim()) throw new Error("Cart productId is required");
|
|
553
|
+
const normalized = {
|
|
554
|
+
...item,
|
|
555
|
+
productId: item.productId.trim(),
|
|
556
|
+
quantity: quantity(item.quantity)
|
|
557
|
+
};
|
|
558
|
+
return this.mutate((cart) => this.client.post(`/carts/${cart.id}/items`, {
|
|
559
|
+
item: normalized,
|
|
560
|
+
version: cart.version
|
|
561
|
+
}, this.options()));
|
|
329
562
|
});
|
|
330
|
-
const existing = state.items.find((entry) => entry.id === id);
|
|
331
|
-
const addedQuantity = quantity(item.quantity);
|
|
332
|
-
if (existing) existing.quantity = quantity(existing.quantity + addedQuantity);
|
|
333
|
-
else state.items.push({
|
|
334
|
-
id,
|
|
335
|
-
productId,
|
|
336
|
-
...productVariantId ? { productVariantId } : {},
|
|
337
|
-
quantity: addedQuantity
|
|
338
|
-
});
|
|
339
|
-
state.discount = null;
|
|
340
|
-
return this.write(state);
|
|
341
563
|
}
|
|
342
564
|
updateQuantity(itemId, nextQuantity) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
565
|
+
return this.enqueue(async () => {
|
|
566
|
+
const normalized = quantity(nextQuantity, true);
|
|
567
|
+
return this.mutate((cart) => {
|
|
568
|
+
if (!cart.items.some((item) => item.id === itemId)) throw new Error("Cart item not found");
|
|
569
|
+
const items = cart.items.map((item) => item.id === itemId ? {
|
|
570
|
+
...item,
|
|
571
|
+
quantity: normalized
|
|
572
|
+
} : item).filter((item) => item.quantity > 0);
|
|
573
|
+
return this.save(cart, items, null);
|
|
574
|
+
});
|
|
575
|
+
});
|
|
351
576
|
}
|
|
352
577
|
remove(itemId) {
|
|
353
|
-
|
|
354
|
-
const items = state.items.filter((item) => item.id !== itemId);
|
|
355
|
-
if (items.length === state.items.length) throw new Error("Cart item not found");
|
|
356
|
-
return this.write({
|
|
357
|
-
items,
|
|
358
|
-
discount: null
|
|
359
|
-
});
|
|
578
|
+
return this.updateQuantity(itemId, 0);
|
|
360
579
|
}
|
|
361
580
|
clear() {
|
|
362
|
-
return this.
|
|
363
|
-
|
|
364
|
-
|
|
581
|
+
return this.enqueue(() => this.mutate((cart) => this.save(cart, [], null), false));
|
|
582
|
+
}
|
|
583
|
+
checkDiscount(code) {
|
|
584
|
+
return this.enqueue(async () => this.quote(await this.resolve(), code));
|
|
585
|
+
}
|
|
586
|
+
applyDiscount(code) {
|
|
587
|
+
return this.enqueue(async () => {
|
|
588
|
+
let quote;
|
|
589
|
+
const snapshot = await this.mutate(async (cart) => {
|
|
590
|
+
quote = await this.quote(cart, code);
|
|
591
|
+
return this.save(cart, cart.items, quote.discount.code);
|
|
592
|
+
});
|
|
593
|
+
snapshot.discount = discountFromQuote(quote);
|
|
594
|
+
this.cache.snapshot = cloneSnapshot(snapshot);
|
|
595
|
+
this.persistCache();
|
|
596
|
+
return snapshot;
|
|
365
597
|
});
|
|
366
598
|
}
|
|
367
|
-
commerceItems() {
|
|
368
|
-
return this.get().items.map(({ productId, productVariantId, quantity }) => ({
|
|
369
|
-
productId,
|
|
370
|
-
...productVariantId ? { productVariantId } : {},
|
|
371
|
-
quantity
|
|
372
|
-
}));
|
|
373
|
-
}
|
|
374
|
-
async checkDiscount(code) {
|
|
375
|
-
const items = this.commerceItems();
|
|
376
|
-
if (items.length === 0) throw new Error("Cannot check a discount on an empty cart");
|
|
377
|
-
return this.client.post("/discounts/check", {
|
|
378
|
-
code,
|
|
379
|
-
items
|
|
380
|
-
}, { access: "public" });
|
|
381
|
-
}
|
|
382
|
-
async applyDiscount(code) {
|
|
383
|
-
const quote = await this.checkDiscount(code);
|
|
384
|
-
const state = this.get();
|
|
385
|
-
state.discount = {
|
|
386
|
-
...quote.discount,
|
|
387
|
-
subtotal: quote.subtotal,
|
|
388
|
-
discountAmount: quote.discountAmount,
|
|
389
|
-
total: quote.total,
|
|
390
|
-
currency: quote.currency
|
|
391
|
-
};
|
|
392
|
-
return this.write(state);
|
|
393
|
-
}
|
|
394
599
|
removeDiscount() {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
return response;
|
|
600
|
+
return this.enqueue(() => this.mutate((cart) => this.save(cart, cart.items, null)));
|
|
601
|
+
}
|
|
602
|
+
checkout(input = {}) {
|
|
603
|
+
return this.enqueue(async () => {
|
|
604
|
+
const cart = await this.resolve(true);
|
|
605
|
+
if (!cart.items.length) throw new Error("Cannot checkout an empty cart");
|
|
606
|
+
const response = await this.client.post(`/carts/${cart.id}/checkout`, {
|
|
607
|
+
...input,
|
|
608
|
+
version: cart.version
|
|
609
|
+
}, this.options());
|
|
610
|
+
this.reset();
|
|
611
|
+
return response;
|
|
612
|
+
});
|
|
409
613
|
}
|
|
410
614
|
};
|
|
411
615
|
//#endregion
|
|
@@ -466,7 +670,9 @@ var CaldikClient = class {
|
|
|
466
670
|
const client = new CaldikHttpClient({
|
|
467
671
|
apiKey: options.apiKey,
|
|
468
672
|
apiUrl: options.apiUrl,
|
|
469
|
-
storeId: options.storeId
|
|
673
|
+
storeId: options.storeId,
|
|
674
|
+
request: options.request,
|
|
675
|
+
onSetCookie: options.onSetCookie
|
|
470
676
|
});
|
|
471
677
|
this.categories = new CategoriesApi(client);
|
|
472
678
|
this.products = new ProductsApi(client);
|