@wocart/storefront 1.2.0 → 1.4.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/CHANGELOG.md +29 -0
- package/USAGE.md +25 -2
- package/dist/client/cart.d.ts +16 -2
- package/dist/client/cart.js +7 -3
- package/dist/domain.d.ts +56 -0
- package/dist/domain.js +35 -0
- package/dist/endpoints/storefront-routes.d.ts +6 -1
- package/dist/endpoints/storefront-routes.js +13 -5
- package/dist/schemas.d.ts +9 -0
- package/dist/schemas.js +6 -0
- package/dist/types.d.ts +17 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ follows [semantic versioning](https://semver.org/): breaking changes ship only
|
|
|
5
5
|
in a new major version, and an export is marked `@deprecated` for at least one
|
|
6
6
|
minor release before a major removes it.
|
|
7
7
|
|
|
8
|
+
## 1.4.0 — 2026-10-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`cart.quote(lines, { destination })`** quotes what an order would cost for
|
|
13
|
+
a basket, or for one product exactly as its order form posts it: a quantity,
|
|
14
|
+
a variant per unit (`variantSelections`), an offer tier (`offerId`). The
|
|
15
|
+
server builds the lines the way the order does, so a product page's delivery
|
|
16
|
+
and total are the order's own. `validate(items, options)` is now its basket
|
|
17
|
+
form.
|
|
18
|
+
- `ProductQuoteInputSchema`, `CartQuoteLinesSchema` and the `ProductQuoteInput`
|
|
19
|
+
and `CartQuoteLines` types.
|
|
20
|
+
- The `cartValidate` route accepts a product form as well as a basket, each
|
|
21
|
+
with an optional `destination`.
|
|
22
|
+
|
|
23
|
+
## 1.3.0 — 2026-10-15
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **Delivery quotes.** `cart.validate(items, { destination })` answers
|
|
28
|
+
`delivery`: the fee and total the order will charge at that wilaya, commune
|
|
29
|
+
and delivery type — product shipping profiles, commune overrides, the
|
|
30
|
+
free-delivery threshold and free-shipping offers included — or
|
|
31
|
+
`available: false` where the store does not deliver. The server computes the
|
|
32
|
+
quote with the same function it charges orders with. A platform server that
|
|
33
|
+
predates the quote answers without `delivery`.
|
|
34
|
+
- `DeliveryDestinationSchema`, `DeliveryDestination` and `CartDeliveryQuote`.
|
|
35
|
+
- The `cartValidate` route passes a `destination` through.
|
|
36
|
+
|
|
8
37
|
## 1.2.0 — 2026-10-15
|
|
9
38
|
|
|
10
39
|
### Added
|
package/USAGE.md
CHANGED
|
@@ -745,6 +745,28 @@ type ShippingRate = {
|
|
|
745
745
|
If the map is empty or one rate is null, show a useful unavailable state. Do
|
|
746
746
|
not silently display zero shipping.
|
|
747
747
|
|
|
748
|
+
These are the store default profile's wilaya rates — a guide for "delivery
|
|
749
|
+
from X DZD", never the fee an order is charged. The fee also depends on each
|
|
750
|
+
product's own shipping profile, the commune, the free-delivery threshold and
|
|
751
|
+
free-shipping offers. To show what the shopper will pay, quote it:
|
|
752
|
+
|
|
753
|
+
~~~ts
|
|
754
|
+
const result = await store.cart.validate(items, {
|
|
755
|
+
destination: { wilayaId: 16, communeId: "c-16-001", deliveryType: "home" },
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
if (result.success && result.data.delivery) {
|
|
759
|
+
const quote = result.data.delivery;
|
|
760
|
+
// quote.available → quote.fee and quote.total are what the order charges.
|
|
761
|
+
// !quote.available → the store does not deliver there; an order is refused.
|
|
762
|
+
}
|
|
763
|
+
~~~
|
|
764
|
+
|
|
765
|
+
The quote comes from the same function the order uses, so the fee and total
|
|
766
|
+
it names are the ones charged. A single-product order form quotes the same
|
|
767
|
+
way, with its one product as the only line. From the browser, post
|
|
768
|
+
`{ items, destination }` to the `cartValidate` route.
|
|
769
|
+
|
|
748
770
|
### Wilayas and communes
|
|
749
771
|
|
|
750
772
|
~~~ts
|
|
@@ -1430,8 +1452,9 @@ render null as not found.
|
|
|
1430
1452
|
### The cart total is different after checkout
|
|
1431
1453
|
|
|
1432
1454
|
This is expected when a browser price, offer, stock state, or delivery value
|
|
1433
|
-
was stale
|
|
1434
|
-
|
|
1455
|
+
was stale, or when the theme computed a fee or total itself. Show the totals
|
|
1456
|
+
cart.validate answers — with a destination for the delivery fee — and treat
|
|
1457
|
+
order creation as the final server decision.
|
|
1435
1458
|
|
|
1436
1459
|
### OTP says unavailable
|
|
1437
1460
|
|
package/dist/client/cart.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { ClientCore, ClientResult } from "./core.js";
|
|
15
15
|
import type { CartValidation } from "../types.js";
|
|
16
|
-
import type { BasketLineInput } from "../domain.js";
|
|
16
|
+
import type { BasketLineInput, CartQuoteLines, DeliveryDestination } from "../domain.js";
|
|
17
17
|
export interface CartNamespace {
|
|
18
18
|
/**
|
|
19
19
|
* Re-price and re-check a basket against the live catalog.
|
|
@@ -21,8 +21,22 @@ export interface CartNamespace {
|
|
|
21
21
|
* optional variant fields); pricing from their side is never trusted, and
|
|
22
22
|
* each line's product name and variant label come back from the catalog in
|
|
23
23
|
* the client's language.
|
|
24
|
+
*
|
|
25
|
+
* With a `destination`, the answer also carries `delivery`: the fee and
|
|
26
|
+
* total the order would charge there. A theme shows that, never a fee or
|
|
27
|
+
* total of its own making.
|
|
24
28
|
*/
|
|
25
|
-
validate(items: readonly BasketLineInput[]
|
|
29
|
+
validate(items: readonly BasketLineInput[], options?: {
|
|
30
|
+
destination?: DeliveryDestination;
|
|
31
|
+
}): Promise<ClientResult<CartValidation>>;
|
|
32
|
+
/**
|
|
33
|
+
* Quote what an order would cost, for a basket or for one product exactly as
|
|
34
|
+
* its order form posts it (quantity, variants per unit, offer tier). The lines
|
|
35
|
+
* are built the way the order builds them, so the quote and the order agree.
|
|
36
|
+
*/
|
|
37
|
+
quote(lines: CartQuoteLines, options?: {
|
|
38
|
+
destination?: DeliveryDestination;
|
|
39
|
+
}): Promise<ClientResult<CartValidation>>;
|
|
26
40
|
}
|
|
27
41
|
/**
|
|
28
42
|
* Create cart namespace
|
package/dist/client/cart.js
CHANGED
|
@@ -18,9 +18,13 @@ import { localizedPath } from "./paths.js";
|
|
|
18
18
|
* Create cart namespace
|
|
19
19
|
*/
|
|
20
20
|
export function createCartNamespace(core) {
|
|
21
|
+
// A closure rather than `this`: a theme may destructure `store.cart`.
|
|
22
|
+
const quote = async (lines, options) => {
|
|
23
|
+
const destination = options?.destination;
|
|
24
|
+
return core.post(localizedPath(core.config, "/store/cart/validate"), destination ? { ...lines, destination } : lines, { schema: CartValidationSchema, operation: STOREFRONT_OPERATIONS.cartValidate });
|
|
25
|
+
};
|
|
21
26
|
return {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
},
|
|
27
|
+
validate: (items, options) => quote({ items: [...items] }, options),
|
|
28
|
+
quote,
|
|
25
29
|
};
|
|
26
30
|
}
|
package/dist/domain.d.ts
CHANGED
|
@@ -175,6 +175,62 @@ export declare const AbandonedCheckoutInputSchema: z.ZodObject<{
|
|
|
175
175
|
* `items[]` line: absent — not null — is the common wire shape.
|
|
176
176
|
*/
|
|
177
177
|
export type AbandonedCheckoutInput = z.infer<typeof AbandonedCheckoutInputSchema>;
|
|
178
|
+
/**
|
|
179
|
+
* One product as a product page's order form posts it: a quantity, or a
|
|
180
|
+
* variant chosen per unit, and an offer tier. The quote reads these the way the
|
|
181
|
+
* order does, so the page asks with exactly what its form will submit.
|
|
182
|
+
*/
|
|
183
|
+
export declare const ProductQuoteInputSchema: z.ZodObject<{
|
|
184
|
+
productId: z.ZodString;
|
|
185
|
+
productName: z.ZodString;
|
|
186
|
+
quantity: z.ZodOptional<z.ZodNumber>;
|
|
187
|
+
variantId: z.ZodOptional<z.ZodString>;
|
|
188
|
+
variantLabel: z.ZodOptional<z.ZodString>;
|
|
189
|
+
offerId: z.ZodOptional<z.ZodString>;
|
|
190
|
+
variantSelections: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
191
|
+
variantId: z.ZodString;
|
|
192
|
+
variantLabel: z.ZodOptional<z.ZodString>;
|
|
193
|
+
}, z.core.$strip>>>;
|
|
194
|
+
}, z.core.$strip>;
|
|
195
|
+
export type ProductQuoteInput = z.infer<typeof ProductQuoteInputSchema>;
|
|
196
|
+
/** What a quote is asked about: a basket, or one product as its form posts it. */
|
|
197
|
+
export declare const CartQuoteLinesSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
198
|
+
items: z.ZodArray<z.ZodObject<{
|
|
199
|
+
productId: z.ZodString;
|
|
200
|
+
productName: z.ZodString;
|
|
201
|
+
variantId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
202
|
+
variantLabel: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
203
|
+
quantity: z.ZodNumber;
|
|
204
|
+
pricePerUnit: z.ZodOptional<z.ZodNumber>;
|
|
205
|
+
offerId: z.ZodOptional<z.ZodString>;
|
|
206
|
+
}, z.core.$strip>>;
|
|
207
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
208
|
+
productId: z.ZodString;
|
|
209
|
+
productName: z.ZodString;
|
|
210
|
+
quantity: z.ZodOptional<z.ZodNumber>;
|
|
211
|
+
variantId: z.ZodOptional<z.ZodString>;
|
|
212
|
+
variantLabel: z.ZodOptional<z.ZodString>;
|
|
213
|
+
offerId: z.ZodOptional<z.ZodString>;
|
|
214
|
+
variantSelections: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
215
|
+
variantId: z.ZodString;
|
|
216
|
+
variantLabel: z.ZodOptional<z.ZodString>;
|
|
217
|
+
}, z.core.$strip>>>;
|
|
218
|
+
}, z.core.$strip>]>;
|
|
219
|
+
export type CartQuoteLines = z.infer<typeof CartQuoteLinesSchema>;
|
|
220
|
+
/**
|
|
221
|
+
* Where a basket would be delivered, for a delivery quote. The commune is
|
|
222
|
+
* optional — without it the wilaya's rate applies; with it, the commune's own
|
|
223
|
+
* override when the merchant set one.
|
|
224
|
+
*/
|
|
225
|
+
export declare const DeliveryDestinationSchema: z.ZodObject<{
|
|
226
|
+
wilayaId: z.ZodNumber;
|
|
227
|
+
communeId: z.ZodOptional<z.ZodString>;
|
|
228
|
+
deliveryType: z.ZodEnum<{
|
|
229
|
+
home: "home";
|
|
230
|
+
stop_desk: "stop_desk";
|
|
231
|
+
}>;
|
|
232
|
+
}, z.core.$strip>;
|
|
233
|
+
export type DeliveryDestination = z.infer<typeof DeliveryDestinationSchema>;
|
|
178
234
|
/** Shopper's selected delivery mode and destination. Policy decides whether address is required. */
|
|
179
235
|
export declare const DeliverySelectionSchema: z.ZodObject<{
|
|
180
236
|
wilayaId: z.ZodNumber;
|
package/dist/domain.js
CHANGED
|
@@ -126,6 +126,41 @@ export const AbandonedCheckoutInputSchema = z.object({
|
|
|
126
126
|
fbc: z.string().max(500).optional(),
|
|
127
127
|
fbp: z.string().max(500).optional(),
|
|
128
128
|
});
|
|
129
|
+
/**
|
|
130
|
+
* One product as a product page's order form posts it: a quantity, or a
|
|
131
|
+
* variant chosen per unit, and an offer tier. The quote reads these the way the
|
|
132
|
+
* order does, so the page asks with exactly what its form will submit.
|
|
133
|
+
*/
|
|
134
|
+
export const ProductQuoteInputSchema = z.object({
|
|
135
|
+
productId: z.string().min(1).max(200),
|
|
136
|
+
productName: z.string().min(1).max(200),
|
|
137
|
+
quantity: z.number().int().min(1).max(MAX_BASKET_LINE_QUANTITY).optional(),
|
|
138
|
+
variantId: z.string().min(1).max(200).optional(),
|
|
139
|
+
variantLabel: z.string().max(200).optional(),
|
|
140
|
+
offerId: z.string().min(1).max(200).optional(),
|
|
141
|
+
variantSelections: z
|
|
142
|
+
.array(z.object({
|
|
143
|
+
variantId: z.string().min(1).max(200),
|
|
144
|
+
variantLabel: z.string().max(200).optional(),
|
|
145
|
+
}))
|
|
146
|
+
.max(MAX_BASKET_LINE_QUANTITY)
|
|
147
|
+
.optional(),
|
|
148
|
+
});
|
|
149
|
+
/** What a quote is asked about: a basket, or one product as its form posts it. */
|
|
150
|
+
export const CartQuoteLinesSchema = z.union([
|
|
151
|
+
z.object({ items: z.array(BasketLineInputSchema).min(1).max(MAX_BASKET_LINES) }),
|
|
152
|
+
ProductQuoteInputSchema,
|
|
153
|
+
]);
|
|
154
|
+
/**
|
|
155
|
+
* Where a basket would be delivered, for a delivery quote. The commune is
|
|
156
|
+
* optional — without it the wilaya's rate applies; with it, the commune's own
|
|
157
|
+
* override when the merchant set one.
|
|
158
|
+
*/
|
|
159
|
+
export const DeliveryDestinationSchema = z.object({
|
|
160
|
+
wilayaId: WilayaIdSchema,
|
|
161
|
+
communeId: z.string().min(1).max(200).optional(),
|
|
162
|
+
deliveryType: z.enum(["home", "stop_desk"]),
|
|
163
|
+
});
|
|
129
164
|
/** Shopper's selected delivery mode and destination. Policy decides whether address is required. */
|
|
130
165
|
export const DeliverySelectionSchema = z.object({
|
|
131
166
|
wilayaId: z.number().int().min(1).max(58),
|
|
@@ -32,7 +32,12 @@ export interface StorefrontRoutes<C extends AstroAPIContext> {
|
|
|
32
32
|
readonly otpSend: StorefrontApiRoute<C>;
|
|
33
33
|
/** POST `{ phone, requestId, code }` → `{ data: OtpVerifyResult }`. */
|
|
34
34
|
readonly otpVerify: StorefrontApiRoute<C>;
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* POST a basket `{ items }` or one product `{ productId, productName, quantity,
|
|
37
|
+
* variantSelections, offerId, … }`, each with an optional `destination` →
|
|
38
|
+
* `{ success: true, data: CartValidation }`.
|
|
39
|
+
* With a destination, `data.delivery` is the fee and total the order will charge.
|
|
40
|
+
*/
|
|
36
41
|
readonly cartValidate: StorefrontApiRoute<C>;
|
|
37
42
|
/** POST an `AbandonedCheckoutInput` → 204. */
|
|
38
43
|
readonly abandonedUpsert: StorefrontApiRoute<C>;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* (see `createProxyEndpoint`), and the server's diagnostic fields ride along.
|
|
22
22
|
*/
|
|
23
23
|
import { z } from "zod";
|
|
24
|
-
import { AbandonedCheckoutInputSchema, BasketLineInputSchema, MAX_BASKET_LINES, WilayaIdSchema, } from "../domain.js";
|
|
24
|
+
import { AbandonedCheckoutInputSchema, BasketLineInputSchema, DeliveryDestinationSchema, ProductQuoteInputSchema, MAX_BASKET_LINES, WilayaIdSchema, } from "../domain.js";
|
|
25
25
|
import { createProxyEndpoint, } from "./proxy.js";
|
|
26
26
|
export const REVIEWS_PAGE_DEFAULT = 20;
|
|
27
27
|
export const REVIEWS_PAGE_MAX = 50;
|
|
@@ -31,9 +31,17 @@ const otpVerifyInput = z.object({
|
|
|
31
31
|
requestId: z.string().min(1).max(200),
|
|
32
32
|
code: z.string().min(1).max(16),
|
|
33
33
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
/**
|
|
35
|
+
* A basket, or one product as its order form posts it — the same two shapes the
|
|
36
|
+
* server's quote takes — each with an optional destination to quote to.
|
|
37
|
+
*/
|
|
38
|
+
const cartValidateInput = z.union([
|
|
39
|
+
z.object({
|
|
40
|
+
items: z.array(BasketLineInputSchema).min(1).max(MAX_BASKET_LINES),
|
|
41
|
+
destination: DeliveryDestinationSchema.optional(),
|
|
42
|
+
}),
|
|
43
|
+
ProductQuoteInputSchema.extend({ destination: DeliveryDestinationSchema.optional() }),
|
|
44
|
+
]);
|
|
37
45
|
const abandonedConvertInput = z.object({
|
|
38
46
|
sessionId: z.string().uuid(),
|
|
39
47
|
orderId: z.string().min(1).max(200),
|
|
@@ -87,7 +95,7 @@ export function createStorefrontRoutes(options) {
|
|
|
87
95
|
method: "POST",
|
|
88
96
|
inputSchema: cartValidateInput,
|
|
89
97
|
successBody: (data) => ({ success: true, data }),
|
|
90
|
-
call: (client, {
|
|
98
|
+
call: (client, { destination, ...lines }) => client.cart.quote(lines, destination ? { destination } : undefined),
|
|
91
99
|
}),
|
|
92
100
|
abandonedUpsert: route({
|
|
93
101
|
method: "POST",
|
package/dist/schemas.d.ts
CHANGED
|
@@ -692,6 +692,15 @@ export declare const CartValidationSchema: z.ZodObject<{
|
|
|
692
692
|
qualified: z.ZodBoolean;
|
|
693
693
|
remaining: z.ZodNumber;
|
|
694
694
|
}, z.core.$strip>;
|
|
695
|
+
delivery: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
696
|
+
available: z.ZodLiteral<true>;
|
|
697
|
+
fee: z.ZodNumber;
|
|
698
|
+
total: z.ZodNumber;
|
|
699
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
700
|
+
available: z.ZodLiteral<false>;
|
|
701
|
+
fee: z.ZodNull;
|
|
702
|
+
total: z.ZodNull;
|
|
703
|
+
}, z.core.$strip>], "available">>;
|
|
695
704
|
}, z.core.$strip>;
|
|
696
705
|
export declare const CreatedOrderSchema: z.ZodObject<{
|
|
697
706
|
orderId: z.ZodString;
|
package/dist/schemas.js
CHANGED
|
@@ -314,6 +314,12 @@ export const CartValidationSchema = z.object({
|
|
|
314
314
|
qualified: z.boolean(),
|
|
315
315
|
remaining: z.number(),
|
|
316
316
|
}),
|
|
317
|
+
delivery: z
|
|
318
|
+
.discriminatedUnion("available", [
|
|
319
|
+
z.object({ available: z.literal(true), fee: z.number(), total: z.number() }),
|
|
320
|
+
z.object({ available: z.literal(false), fee: z.null(), total: z.null() }),
|
|
321
|
+
])
|
|
322
|
+
.optional(),
|
|
317
323
|
});
|
|
318
324
|
export const CreatedOrderSchema = z.object({
|
|
319
325
|
orderId: z.string(),
|
package/dist/types.d.ts
CHANGED
|
@@ -275,7 +275,24 @@ export interface CartValidation {
|
|
|
275
275
|
qualified: boolean;
|
|
276
276
|
remaining: number;
|
|
277
277
|
};
|
|
278
|
+
/**
|
|
279
|
+
* Present when `validate` was given a destination: what the order would
|
|
280
|
+
* charge there, from the same quote the order itself uses (shipping
|
|
281
|
+
* profiles, commune overrides, the free-delivery threshold and offers).
|
|
282
|
+
* `available: false` — fee and total null — means the store does not
|
|
283
|
+
* deliver there, and an order to it is refused.
|
|
284
|
+
*/
|
|
285
|
+
delivery?: CartDeliveryQuote | undefined;
|
|
278
286
|
}
|
|
287
|
+
export type CartDeliveryQuote = {
|
|
288
|
+
available: true;
|
|
289
|
+
fee: number;
|
|
290
|
+
total: number;
|
|
291
|
+
} | {
|
|
292
|
+
available: false;
|
|
293
|
+
fee: null;
|
|
294
|
+
total: null;
|
|
295
|
+
};
|
|
279
296
|
/** What POST /store/orders returns on 201. */
|
|
280
297
|
export interface CreatedOrder {
|
|
281
298
|
orderId: string;
|
package/package.json
CHANGED