@wocart/storefront 1.9.0 → 1.10.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 +12 -0
- package/USAGE.md +21 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/offer-view.d.ts +18 -1
- package/dist/offer-view.js +21 -0
- package/dist/schemas.d.ts +37 -0
- package/dist/schemas.js +22 -0
- package/dist/types.d.ts +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ 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.10.0 — 2026-10-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Quantity pricing.** `ProductDetail.quantityPricing` (`QuantityPricing`,
|
|
13
|
+
`QuantityPricingSchema`): a product's price tiers by quantity, each with an
|
|
14
|
+
optional free delivery — null or absent when it has none.
|
|
15
|
+
`getQuantityTierViews(pricing, unitPrice)` projects them for tiles (price,
|
|
16
|
+
per-unit price, saving); the total is the server's cart quote.
|
|
17
|
+
- `CartValidationLine.offerId`: the quantity-pricing offer that priced the
|
|
18
|
+
line, whose `lineTotal` is then the tier's price. Optional.
|
|
19
|
+
|
|
8
20
|
## 1.9.0 — 2026-10-17
|
|
9
21
|
|
|
10
22
|
### Added
|
package/USAGE.md
CHANGED
|
@@ -223,6 +223,7 @@ server directly.
|
|
|
223
223
|
| `productImageSource` | Which photo represents a product (cover → gallery fallback) |
|
|
224
224
|
| `getProductView`, `getProductListView` | Pure price, discount, default-variant, and stock values for detail and list projections |
|
|
225
225
|
| `getOfferTierView` | Pure offer-tier price, compare-price, savings, and free-shipping projection; display-only, never order authority |
|
|
226
|
+
| `getQuantityTierViews` | A product's quantity-pricing tiers as tiles show them — price, per-unit price, saving against the unit price, free delivery; display-only, the total is the server's cart quote |
|
|
226
227
|
| `META_CURRENCY`, `buildMetaProductPayload`, `buildMetaViewContentPayload`, `buildMetaAddToCartPayload`, `buildMetaInitiateCheckoutPayload` | Browser-safe Meta event payloads with the platform currency and catalog shape |
|
|
227
228
|
| `createMetaPixelTracker` | Injected browser-pixel dispatch; the theme supplies the vendor global and keeps bootstrap/timing |
|
|
228
229
|
| `createBasketStore`, `createLocalStorageAdapter`, `createMemoryStorageAdapter`, `DEFAULT_BASKET_STORAGE_KEY` | The browser basket, its storage, and the key it persists under (`cod_cart_v1`) |
|
|
@@ -625,8 +626,26 @@ if (!product) {
|
|
|
625
626
|
~~~
|
|
626
627
|
|
|
627
628
|
Product detail includes the product fields, parsed variant options and tags,
|
|
628
|
-
the category, variants, images, review statistics,
|
|
629
|
-
decides whether a product is publicly available.
|
|
629
|
+
the category, variants, images, review statistics, quantity pricing, and
|
|
630
|
+
offers. The server decides whether a product is publicly available.
|
|
631
|
+
|
|
632
|
+
**Quantity pricing** (`product.quantityPricing`, null when the product has
|
|
633
|
+
none) prices a product by how many are bought — "1 for 2,000 DA, 2 for 3,500
|
|
634
|
+
DA, 3 for 4,500 DA". The highest tier the quantity reaches prices every unit
|
|
635
|
+
at its per-unit price, and a tier may include free delivery for a basket of
|
|
636
|
+
that product alone. A product has at most one offer per order, so it has
|
|
637
|
+
either quantity pricing or offers in `offers`, never both at once.
|
|
638
|
+
|
|
639
|
+
~~~ts
|
|
640
|
+
const tiles = product.quantityPricing ? getQuantityTierViews(product.quantityPricing, product.price) : [];
|
|
641
|
+
// tiles: [{ quantity, price, perUnit, savingsAmount, savePct, freeShipping }]
|
|
642
|
+
~~~
|
|
643
|
+
|
|
644
|
+
Show the tiers as choices; never compute the total yourself. The cart quote
|
|
645
|
+
(`POST /api/cart/validate`) answers what the shopper pays — each line's
|
|
646
|
+
`lineTotal` already priced by its tier (`offerId` names the offer), and the
|
|
647
|
+
delivery — and the order charges exactly that. A tier locked to one variant
|
|
648
|
+
(`triggerVariantId`) counts and prices only that variant.
|
|
630
649
|
|
|
631
650
|
### Product lists
|
|
632
651
|
|
package/dist/index.d.ts
CHANGED
|
@@ -34,8 +34,8 @@ export * from "./types.js";
|
|
|
34
34
|
export * from "./schemas.js";
|
|
35
35
|
export * from "./domain.js";
|
|
36
36
|
export * from "./product-view.js";
|
|
37
|
-
export { getOfferTierView } from "./offer-view.js";
|
|
38
|
-
export type { OfferTierInput, OfferTierView } from "./offer-view.js";
|
|
37
|
+
export { getOfferTierView, getQuantityTierViews } from "./offer-view.js";
|
|
38
|
+
export type { OfferTierInput, OfferTierView, QuantityTierView } from "./offer-view.js";
|
|
39
39
|
export { createAbandonedCheckoutTracker } from "./abandoned-checkout.js";
|
|
40
40
|
export type { AbandonedCheckoutPorts, AbandonedCheckoutSnapshot, AbandonedCheckoutStorage, AbandonedCheckoutTracker, } from "./abandoned-checkout.js";
|
|
41
41
|
export { META_CURRENCY, buildMetaAddToCartPayload, buildMetaInitiateCheckoutPayload, buildMetaProductPayload, buildMetaViewContentPayload, createMetaPixelTracker, } from "./meta-tracking.js";
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,6 @@ export * from "./types.js";
|
|
|
25
25
|
export * from "./schemas.js";
|
|
26
26
|
export * from "./domain.js";
|
|
27
27
|
export * from "./product-view.js";
|
|
28
|
-
export { getOfferTierView } from "./offer-view.js";
|
|
28
|
+
export { getOfferTierView, getQuantityTierViews } from "./offer-view.js";
|
|
29
29
|
export { createAbandonedCheckoutTracker } from "./abandoned-checkout.js";
|
|
30
30
|
export { META_CURRENCY, buildMetaAddToCartPayload, buildMetaInitiateCheckoutPayload, buildMetaProductPayload, buildMetaViewContentPayload, createMetaPixelTracker, } from "./meta-tracking.js";
|
package/dist/offer-view.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Offer } from "./types.js";
|
|
1
|
+
import type { Offer, QuantityPricing } from "./types.js";
|
|
2
2
|
export interface OfferTierView {
|
|
3
3
|
isFreeShipping: boolean;
|
|
4
4
|
savePct: number;
|
|
@@ -8,3 +8,20 @@ export interface OfferTierView {
|
|
|
8
8
|
}
|
|
9
9
|
export type OfferTierInput = Pick<Offer, "discountType" | "triggerQuantity" | "rewardQuantity" | "rewardUnitPrice">;
|
|
10
10
|
export declare function getOfferTierView(offer: OfferTierInput, basePrice: number, comparePrice: number | null): OfferTierView;
|
|
11
|
+
export interface QuantityTierView {
|
|
12
|
+
quantity: number;
|
|
13
|
+
/** What that many units cost. */
|
|
14
|
+
price: number;
|
|
15
|
+
/** What one unit costs within the tier, rounded to the dinar. */
|
|
16
|
+
perUnit: number;
|
|
17
|
+
/** What the tier saves against buying that many at `unitPrice`; 0 when it saves nothing. */
|
|
18
|
+
savingsAmount: number;
|
|
19
|
+
savePct: number;
|
|
20
|
+
freeShipping: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The tiers of a product's quantity pricing as tiles show them: price, per-unit
|
|
24
|
+
* price and saving against the product's unit price. Presentation only — the
|
|
25
|
+
* total for a quantity is the server's cart quote.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getQuantityTierViews(pricing: Pick<QuantityPricing, "tiers">, unitPrice: number): QuantityTierView[];
|
package/dist/offer-view.js
CHANGED
|
@@ -15,3 +15,24 @@ export function getOfferTierView(offer, basePrice, comparePrice) {
|
|
|
15
15
|
comparePrice: comparePrice === null ? null : comparePrice * offer.triggerQuantity,
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* The tiers of a product's quantity pricing as tiles show them: price, per-unit
|
|
20
|
+
* price and saving against the product's unit price. Presentation only — the
|
|
21
|
+
* total for a quantity is the server's cart quote.
|
|
22
|
+
*/
|
|
23
|
+
export function getQuantityTierViews(pricing, unitPrice) {
|
|
24
|
+
return [...pricing.tiers]
|
|
25
|
+
.sort((a, b) => a.quantity - b.quantity)
|
|
26
|
+
.map((tier) => {
|
|
27
|
+
const listValue = unitPrice * tier.quantity;
|
|
28
|
+
const savingsAmount = Math.max(0, listValue - tier.price);
|
|
29
|
+
return {
|
|
30
|
+
quantity: tier.quantity,
|
|
31
|
+
price: tier.price,
|
|
32
|
+
perUnit: Math.round(tier.price / tier.quantity),
|
|
33
|
+
savingsAmount,
|
|
34
|
+
savePct: listValue > 0 ? Math.round((savingsAmount / listValue) * 100) : 0,
|
|
35
|
+
freeShipping: tier.freeShipping,
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
package/dist/schemas.d.ts
CHANGED
|
@@ -43,6 +43,21 @@ export declare const OfferSchema: z.ZodObject<{
|
|
|
43
43
|
rewardVariantLabel: z.ZodNullable<z.ZodString>;
|
|
44
44
|
rewardUnitPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
45
45
|
}, z.core.$strip>;
|
|
46
|
+
/**
|
|
47
|
+
* Quantity pricing — price tiers by quantity ("2 for 3,500 DA"). The highest
|
|
48
|
+
* tier the quantity reaches prices every unit at its per-unit price; the
|
|
49
|
+
* server's cart quote is the total.
|
|
50
|
+
*/
|
|
51
|
+
export declare const QuantityPricingSchema: z.ZodObject<{
|
|
52
|
+
offerId: z.ZodString;
|
|
53
|
+
name: z.ZodString;
|
|
54
|
+
triggerVariantId: z.ZodNullable<z.ZodString>;
|
|
55
|
+
tiers: z.ZodArray<z.ZodObject<{
|
|
56
|
+
quantity: z.ZodNumber;
|
|
57
|
+
price: z.ZodNumber;
|
|
58
|
+
freeShipping: z.ZodBoolean;
|
|
59
|
+
}, z.core.$strip>>;
|
|
60
|
+
}, z.core.$strip>;
|
|
46
61
|
export declare const CategorySchema: z.ZodObject<{
|
|
47
62
|
id: z.ZodString;
|
|
48
63
|
name: z.ZodString;
|
|
@@ -201,6 +216,16 @@ export declare const ProductSchema: z.ZodObject<{
|
|
|
201
216
|
avgRating: z.ZodNumber;
|
|
202
217
|
reviewCount: z.ZodNumber;
|
|
203
218
|
}, z.core.$strip>>;
|
|
219
|
+
quantityPricing: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
220
|
+
offerId: z.ZodString;
|
|
221
|
+
name: z.ZodString;
|
|
222
|
+
triggerVariantId: z.ZodNullable<z.ZodString>;
|
|
223
|
+
tiers: z.ZodArray<z.ZodObject<{
|
|
224
|
+
quantity: z.ZodNumber;
|
|
225
|
+
price: z.ZodNumber;
|
|
226
|
+
freeShipping: z.ZodBoolean;
|
|
227
|
+
}, z.core.$strip>>;
|
|
228
|
+
}, z.core.$strip>>>;
|
|
204
229
|
offers: z.ZodArray<z.ZodObject<{
|
|
205
230
|
id: z.ZodString;
|
|
206
231
|
name: z.ZodString;
|
|
@@ -629,6 +654,16 @@ export declare const LandingPageSchema: z.ZodObject<{
|
|
|
629
654
|
avgRating: z.ZodNumber;
|
|
630
655
|
reviewCount: z.ZodNumber;
|
|
631
656
|
}, z.core.$strip>>;
|
|
657
|
+
quantityPricing: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
658
|
+
offerId: z.ZodString;
|
|
659
|
+
name: z.ZodString;
|
|
660
|
+
triggerVariantId: z.ZodNullable<z.ZodString>;
|
|
661
|
+
tiers: z.ZodArray<z.ZodObject<{
|
|
662
|
+
quantity: z.ZodNumber;
|
|
663
|
+
price: z.ZodNumber;
|
|
664
|
+
freeShipping: z.ZodBoolean;
|
|
665
|
+
}, z.core.$strip>>;
|
|
666
|
+
}, z.core.$strip>>>;
|
|
632
667
|
offers: z.ZodArray<z.ZodObject<{
|
|
633
668
|
id: z.ZodString;
|
|
634
669
|
name: z.ZodString;
|
|
@@ -677,6 +712,7 @@ export declare const CartValidationLineSchema: z.ZodObject<{
|
|
|
677
712
|
quantity: z.ZodNumber;
|
|
678
713
|
unitPrice: z.ZodNumber;
|
|
679
714
|
lineTotal: z.ZodNumber;
|
|
715
|
+
offerId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
680
716
|
maxQuantity: z.ZodNullable<z.ZodNumber>;
|
|
681
717
|
blocker: z.ZodNullable<z.ZodEnum<{
|
|
682
718
|
missing: "missing";
|
|
@@ -699,6 +735,7 @@ export declare const CartValidationSchema: z.ZodObject<{
|
|
|
699
735
|
quantity: z.ZodNumber;
|
|
700
736
|
unitPrice: z.ZodNumber;
|
|
701
737
|
lineTotal: z.ZodNumber;
|
|
738
|
+
offerId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
702
739
|
maxQuantity: z.ZodNullable<z.ZodNumber>;
|
|
703
740
|
blocker: z.ZodNullable<z.ZodEnum<{
|
|
704
741
|
missing: "missing";
|
package/dist/schemas.js
CHANGED
|
@@ -42,6 +42,21 @@ export const OfferSchema = z.object({
|
|
|
42
42
|
// it omits the field, and the product's own price stands in for the gift.
|
|
43
43
|
rewardUnitPrice: z.number().nullish(),
|
|
44
44
|
});
|
|
45
|
+
/**
|
|
46
|
+
* Quantity pricing — price tiers by quantity ("2 for 3,500 DA"). The highest
|
|
47
|
+
* tier the quantity reaches prices every unit at its per-unit price; the
|
|
48
|
+
* server's cart quote is the total.
|
|
49
|
+
*/
|
|
50
|
+
export const QuantityPricingSchema = z.object({
|
|
51
|
+
offerId: z.string(),
|
|
52
|
+
name: z.string(),
|
|
53
|
+
triggerVariantId: z.string().nullable(),
|
|
54
|
+
tiers: z.array(z.object({
|
|
55
|
+
quantity: z.number().int(),
|
|
56
|
+
price: z.number().int(),
|
|
57
|
+
freeShipping: z.boolean(),
|
|
58
|
+
})),
|
|
59
|
+
});
|
|
45
60
|
const productCategorySchema = z.object({
|
|
46
61
|
id: z.string(),
|
|
47
62
|
name: z.string(),
|
|
@@ -122,6 +137,11 @@ export const ProductSchema = z.object({
|
|
|
122
137
|
variants: z.array(ProductVariantSchema),
|
|
123
138
|
images: z.array(ProductImageSchema),
|
|
124
139
|
reviewStats: reviewStatsSchema.nullable(),
|
|
140
|
+
/**
|
|
141
|
+
* The product's live quantity pricing; null or absent when it has none
|
|
142
|
+
* (absent from a server that predates it).
|
|
143
|
+
*/
|
|
144
|
+
quantityPricing: QuantityPricingSchema.nullish(),
|
|
125
145
|
offers: z.array(OfferSchema),
|
|
126
146
|
});
|
|
127
147
|
export const StorePagePublicSchema = z.object({
|
|
@@ -331,6 +351,8 @@ export const CartValidationLineSchema = z.object({
|
|
|
331
351
|
quantity: z.number().int(),
|
|
332
352
|
unitPrice: z.number(),
|
|
333
353
|
lineTotal: z.number(),
|
|
354
|
+
/** The quantity-pricing offer that set `lineTotal`; absent from a server that predates offers on lines. */
|
|
355
|
+
offerId: z.string().nullish(),
|
|
334
356
|
maxQuantity: z.number().nullable(),
|
|
335
357
|
blocker: z.enum(["missing", "unlisted", "out_of_stock"]).nullable(),
|
|
336
358
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { ProductSchema, ProductListSchema, CategorySchema, ProductImageSchema, ProductVariantSchema, OfferSchema, PageDocumentPayloadSchema } from "./schemas.js";
|
|
2
|
+
import { ProductSchema, ProductListSchema, CategorySchema, ProductImageSchema, ProductVariantSchema, OfferSchema, QuantityPricingSchema, PageDocumentPayloadSchema } from "./schemas.js";
|
|
3
3
|
export type Product = z.infer<typeof ProductSchema>;
|
|
4
4
|
export type ProductList = z.infer<typeof ProductListSchema>;
|
|
5
5
|
/** Compact catalog projection used in grids, cards and search results. */
|
|
@@ -10,6 +10,9 @@ export type Category = z.infer<typeof CategorySchema>;
|
|
|
10
10
|
export type ProductImage = z.infer<typeof ProductImageSchema>;
|
|
11
11
|
export type ProductVariant = z.infer<typeof ProductVariantSchema>;
|
|
12
12
|
export type Offer = z.infer<typeof OfferSchema>;
|
|
13
|
+
/** A product's quantity pricing — `ProductDetail.quantityPricing`. */
|
|
14
|
+
export type QuantityPricing = z.infer<typeof QuantityPricingSchema>;
|
|
15
|
+
export type QuantityPricingTier = QuantityPricing["tiers"][number];
|
|
13
16
|
/**
|
|
14
17
|
* One store's saved composition document for one surface. Transport shape
|
|
15
18
|
* only — `sections` items are `unknown`; the theme
|
|
@@ -307,7 +310,10 @@ export interface CartValidationLine {
|
|
|
307
310
|
quantity: number;
|
|
308
311
|
/** CURRENT catalog price — the cart's optimistic price is display-only. */
|
|
309
312
|
unitPrice: number;
|
|
313
|
+
/** What the line costs: unit price × quantity, or its quantity-pricing tier's price. */
|
|
310
314
|
lineTotal: number;
|
|
315
|
+
/** The quantity-pricing offer that set `lineTotal`; null or absent at the catalog price. */
|
|
316
|
+
offerId?: string | null | undefined;
|
|
311
317
|
/** Units still available; null when the product is not stock-tracked. */
|
|
312
318
|
maxQuantity: number | null;
|
|
313
319
|
/**
|
package/package.json
CHANGED