@wocart/storefront 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ 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.3.0 — 2026-10-15
9
+
10
+ ### Added
11
+
12
+ - **Delivery quotes.** `cart.validate(items, { destination })` answers
13
+ `delivery`: the fee and total the order will charge at that wilaya, commune
14
+ and delivery type — product shipping profiles, commune overrides, the
15
+ free-delivery threshold and free-shipping offers included — or
16
+ `available: false` where the store does not deliver. The server computes the
17
+ quote with the same function it charges orders with. A platform server that
18
+ predates the quote answers without `delivery`.
19
+ - `DeliveryDestinationSchema`, `DeliveryDestination` and `CartDeliveryQuote`.
20
+ - The `cartValidate` route passes a `destination` through.
21
+
8
22
  ## 1.2.0 — 2026-10-15
9
23
 
10
24
  ### 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. Render cart.validate results and treat order creation as the final
1434
- server decision.
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
 
@@ -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, 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,14 @@ 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[]): Promise<ClientResult<CartValidation>>;
29
+ validate(items: readonly BasketLineInput[], options?: {
30
+ destination?: DeliveryDestination;
31
+ }): Promise<ClientResult<CartValidation>>;
26
32
  }
27
33
  /**
28
34
  * Create cart namespace
@@ -19,8 +19,9 @@ import { localizedPath } from "./paths.js";
19
19
  */
20
20
  export function createCartNamespace(core) {
21
21
  return {
22
- async validate(items) {
23
- return core.post(localizedPath(core.config, "/store/cart/validate"), { items }, { schema: CartValidationSchema, operation: STOREFRONT_OPERATIONS.cartValidate });
22
+ async validate(items, options) {
23
+ const destination = options?.destination;
24
+ return core.post(localizedPath(core.config, "/store/cart/validate"), destination ? { items, destination } : { items }, { schema: CartValidationSchema, operation: STOREFRONT_OPERATIONS.cartValidate });
24
25
  },
25
26
  };
26
27
  }
package/dist/domain.d.ts CHANGED
@@ -175,6 +175,20 @@ 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
+ * Where a basket would be delivered, for a delivery quote. The commune is
180
+ * optional — without it the wilaya's rate applies; with it, the commune's own
181
+ * override when the merchant set one.
182
+ */
183
+ export declare const DeliveryDestinationSchema: z.ZodObject<{
184
+ wilayaId: z.ZodNumber;
185
+ communeId: z.ZodOptional<z.ZodString>;
186
+ deliveryType: z.ZodEnum<{
187
+ home: "home";
188
+ stop_desk: "stop_desk";
189
+ }>;
190
+ }, z.core.$strip>;
191
+ export type DeliveryDestination = z.infer<typeof DeliveryDestinationSchema>;
178
192
  /** Shopper's selected delivery mode and destination. Policy decides whether address is required. */
179
193
  export declare const DeliverySelectionSchema: z.ZodObject<{
180
194
  wilayaId: z.ZodNumber;
package/dist/domain.js CHANGED
@@ -126,6 +126,16 @@ 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
+ * Where a basket would be delivered, for a delivery quote. The commune is
131
+ * optional — without it the wilaya's rate applies; with it, the commune's own
132
+ * override when the merchant set one.
133
+ */
134
+ export const DeliveryDestinationSchema = z.object({
135
+ wilayaId: WilayaIdSchema,
136
+ communeId: z.string().min(1).max(200).optional(),
137
+ deliveryType: z.enum(["home", "stop_desk"]),
138
+ });
129
139
  /** Shopper's selected delivery mode and destination. Policy decides whether address is required. */
130
140
  export const DeliverySelectionSchema = z.object({
131
141
  wilayaId: z.number().int().min(1).max(58),
@@ -32,7 +32,10 @@ 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
- /** POST `{ items }` → `{ success: true, data: CartValidation }`. */
35
+ /**
36
+ * POST `{ items, destination? }` → `{ success: true, data: CartValidation }`.
37
+ * With a destination, `data.delivery` is the fee and total the order will charge.
38
+ */
36
39
  readonly cartValidate: StorefrontApiRoute<C>;
37
40
  /** POST an `AbandonedCheckoutInput` → 204. */
38
41
  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, 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;
@@ -33,6 +33,7 @@ const otpVerifyInput = z.object({
33
33
  });
34
34
  const cartValidateInput = z.object({
35
35
  items: z.array(BasketLineInputSchema).min(1).max(MAX_BASKET_LINES),
36
+ destination: DeliveryDestinationSchema.optional(),
36
37
  });
37
38
  const abandonedConvertInput = z.object({
38
39
  sessionId: z.string().uuid(),
@@ -87,7 +88,7 @@ export function createStorefrontRoutes(options) {
87
88
  method: "POST",
88
89
  inputSchema: cartValidateInput,
89
90
  successBody: (data) => ({ success: true, data }),
90
- call: (client, { items }) => client.cart.validate(items),
91
+ call: (client, { items, destination }) => destination ? client.cart.validate(items, { destination }) : client.cart.validate(items),
91
92
  }),
92
93
  abandonedUpsert: route({
93
94
  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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wocart/storefront",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Typed, validated SDK for Wocart cash-on-delivery storefronts: catalog reads, basket, checkout, OTP and order tracking, with Astro adapters.",
5
5
  "keywords": [
6
6
  "wocart",