@wocart/storefront 1.9.0 → 1.11.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 +37 -0
- package/USAGE.md +59 -3
- package/dist/actions.d.ts +4 -1
- package/dist/actions.js +5 -0
- package/dist/client/cart.d.ts +16 -7
- package/dist/client/cart.js +8 -3
- package/dist/coupon.d.ts +18 -0
- package/dist/coupon.js +19 -0
- package/dist/domain.d.ts +3 -0
- package/dist/domain.js +1 -0
- package/dist/endpoints/storefront-routes.d.ts +4 -3
- package/dist/endpoints/storefront-routes.js +15 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/offer-view.d.ts +18 -1
- package/dist/offer-view.js +21 -0
- package/dist/schemas.d.ts +70 -0
- package/dist/schemas.js +38 -0
- package/dist/types.d.ts +38 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,43 @@ 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.11.0 — 2026-10-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Coupons.** `cart.quote` / `cart.validate` take `couponCode` and `phone`
|
|
13
|
+
(`CartQuoteOptions`) and answer `coupon` (`CouponOutcome`,
|
|
14
|
+
`CouponOutcomeSchema`): applied — its `discount` already out of `subtotal`
|
|
15
|
+
and of each line's `lineTotal`, `CartValidationLine.discount` being the
|
|
16
|
+
line's share, `freeShipping` making the delivery fee 0 — or refused with a
|
|
17
|
+
`reason` (`CouponRefusalReason`, open), `shortBy` or `startsAt` when it has
|
|
18
|
+
one. Null without a code; absent from an older server.
|
|
19
|
+
- `CreateOrderInput.couponCode` and `placeOrderInput.couponCode` (an empty
|
|
20
|
+
box sends none); `CreatedOrder.couponDiscount`. An order whose code does not
|
|
21
|
+
apply is refused whole with `COUPON_NOT_APPLICABLE`: `couponRefusalOf(result)`
|
|
22
|
+
reads the refusal, and `executePlaceOrder`'s upstream failure carries it as
|
|
23
|
+
`couponRefusal`.
|
|
24
|
+
- Shareable coupon links: `COUPON_URL_PARAM` (`?coupon=CODE`) and
|
|
25
|
+
`couponFromUrl(url)`.
|
|
26
|
+
- `CheckoutDraft.couponCode`.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `CartValidationLine.offerId` names the offer applied to the line, whatever
|
|
31
|
+
its kind — a coupon never covers that line.
|
|
32
|
+
|
|
33
|
+
## 1.10.0 — 2026-10-17
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **Quantity pricing.** `ProductDetail.quantityPricing` (`QuantityPricing`,
|
|
38
|
+
`QuantityPricingSchema`): a product's price tiers by quantity, each with an
|
|
39
|
+
optional free delivery — null or absent when it has none.
|
|
40
|
+
`getQuantityTierViews(pricing, unitPrice)` projects them for tiles (price,
|
|
41
|
+
per-unit price, saving); the total is the server's cart quote.
|
|
42
|
+
- `CartValidationLine.offerId`: the quantity-pricing offer that priced the
|
|
43
|
+
line, whose `lineTotal` is then the tier's price. Optional.
|
|
44
|
+
|
|
8
45
|
## 1.9.0 — 2026-10-17
|
|
9
46
|
|
|
10
47
|
### 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
|
|
|
@@ -1040,7 +1059,44 @@ variant details. The displayed price can be included for the request shape,
|
|
|
1040
1059
|
but the server is authoritative and recalculates the actual amount.
|
|
1041
1060
|
|
|
1042
1061
|
CreatedOrder contains the order ID, customer-facing order number, product
|
|
1043
|
-
price, delivery fee, and total, all in whole DZD
|
|
1062
|
+
price, delivery fee, and total, all in whole DZD — and `couponDiscount`, what
|
|
1063
|
+
the order's coupon took off.
|
|
1064
|
+
|
|
1065
|
+
### Coupons
|
|
1066
|
+
|
|
1067
|
+
A coupon is a code the shopper types. It applies after offers, only to the
|
|
1068
|
+
products no offer applied to, and the server alone decides whether it applies
|
|
1069
|
+
and what it takes off. A theme asks, then shows the answer.
|
|
1070
|
+
|
|
1071
|
+
Ask the cart quote with the code — and the phone once the shopper has typed
|
|
1072
|
+
one, so a coupon for selected customers or one use per customer is judged
|
|
1073
|
+
before the order:
|
|
1074
|
+
|
|
1075
|
+
~~~ts
|
|
1076
|
+
const quoted = await store.cart.quote(form, { destination, couponCode, phone });
|
|
1077
|
+
if (quoted.success && quoted.data.coupon) {
|
|
1078
|
+
const coupon = quoted.data.coupon;
|
|
1079
|
+
// applied: coupon.discount is already out of quoted.data.subtotal and of each
|
|
1080
|
+
// line's lineTotal (line.discount is its share); coupon.freeShipping
|
|
1081
|
+
// means delivery.fee is 0.
|
|
1082
|
+
// refused: coupon.reason — not_found, not_started (startsAt), expired,
|
|
1083
|
+
// used_up, identify_first, not_eligible, already_used,
|
|
1084
|
+
// no_eligible_products, below_minimum_subtotal / _quantity (shortBy),
|
|
1085
|
+
// delivery_already_free. An unknown reason gets the theme's general sentence.
|
|
1086
|
+
}
|
|
1087
|
+
~~~
|
|
1088
|
+
|
|
1089
|
+
`CartQuoteOptions` names what a quote may be asked besides its lines.
|
|
1090
|
+
|
|
1091
|
+
Send the code with the order as `couponCode` (`placeOrderInput` keeps it; an
|
|
1092
|
+
empty box sends none). An order whose code does not apply is refused whole —
|
|
1093
|
+
never placed without the discount the shopper expected — with the code
|
|
1094
|
+
`COUPON_NOT_APPLICABLE`; `couponRefusalOf(result)` reads its refusal, and
|
|
1095
|
+
`executePlaceOrder` hands it back as `couponRefusal` so the form can show it on
|
|
1096
|
+
the code's field.
|
|
1097
|
+
|
|
1098
|
+
A shareable link carries a code in the query (`COUPON_URL_PARAM`, `?coupon=RAMADAN10`);
|
|
1099
|
+
`couponFromUrl(url)` reads it so the theme can fill the code box.
|
|
1044
1100
|
|
|
1045
1101
|
### OTP
|
|
1046
1102
|
|
package/dist/actions.d.ts
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
import { z } from "zod";
|
|
37
37
|
import type { StoreClient } from "./client/index.js";
|
|
38
38
|
import type { DomainError } from "./domain.js";
|
|
39
|
-
import type { CreatedOrder } from "./types.js";
|
|
39
|
+
import type { CouponRefusal, CreatedOrder } from "./types.js";
|
|
40
40
|
/**
|
|
41
41
|
* The order form's input whitelist.
|
|
42
42
|
*
|
|
@@ -85,6 +85,7 @@ export declare const placeOrderInput: z.ZodObject<{
|
|
|
85
85
|
offerId: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
|
|
86
86
|
}, z.core.$strip>>>, unknown>;
|
|
87
87
|
landingPageSlug: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
|
|
88
|
+
couponCode: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
|
|
88
89
|
}, z.core.$strip>;
|
|
89
90
|
export type PlaceOrderInput = z.infer<typeof placeOrderInput>;
|
|
90
91
|
/**
|
|
@@ -106,6 +107,8 @@ export type PlaceOrderResult = {
|
|
|
106
107
|
error: string;
|
|
107
108
|
errorKind: "upstream";
|
|
108
109
|
domainError: DomainError;
|
|
110
|
+
/** Set when the order was refused for its code: the form shows it on the code's field. */
|
|
111
|
+
couponRefusal: CouponRefusal | null;
|
|
109
112
|
};
|
|
110
113
|
/**
|
|
111
114
|
* Execute the placeOrder action's handler body: enforce the
|
package/dist/actions.js
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
* without Astro.
|
|
35
35
|
*/
|
|
36
36
|
import { z } from "zod";
|
|
37
|
+
import { couponRefusalOf } from "./coupon.js";
|
|
37
38
|
import { toLocalAlgerianMobile } from "./phone.js";
|
|
38
39
|
function parseOptionalArrayInput(value) {
|
|
39
40
|
if (!value || value === "[]")
|
|
@@ -149,6 +150,9 @@ export const placeOrderInput = z.object({
|
|
|
149
150
|
// Landing page attribution — best-effort: unknown/draft slug leaves
|
|
150
151
|
// the order unattributed, never blocked (platform extension, LP feature).
|
|
151
152
|
landingPageSlug: z.preprocess((v) => (v === "" || v == null ? undefined : v), z.string().min(1).max(60).optional()),
|
|
153
|
+
// The code the shopper typed; an empty box sends none. Whether it applies
|
|
154
|
+
// is the server's to say — a refusal comes back as `couponRefusal`.
|
|
155
|
+
couponCode: z.preprocess((v) => (typeof v === "string" && v.trim() === "") || v == null ? undefined : v, z.string().max(64).optional()),
|
|
152
156
|
});
|
|
153
157
|
/**
|
|
154
158
|
* Collect the shopper's attribution headers so the server records the
|
|
@@ -195,6 +199,7 @@ export async function executePlaceOrder(input, request, client) {
|
|
|
195
199
|
error: result.error,
|
|
196
200
|
errorKind: "upstream",
|
|
197
201
|
domainError: result.domainError,
|
|
202
|
+
couponRefusal: couponRefusalOf(result),
|
|
198
203
|
};
|
|
199
204
|
}
|
|
200
205
|
return { success: true, data: result.data };
|
package/dist/client/cart.d.ts
CHANGED
|
@@ -24,19 +24,28 @@ export interface CartNamespace {
|
|
|
24
24
|
*
|
|
25
25
|
* With a `destination`, the answer also carries `delivery`: the fee and
|
|
26
26
|
* total the order would charge there. A theme shows that, never a fee or
|
|
27
|
-
* total of its own making.
|
|
27
|
+
* total of its own making. With a `couponCode`, it carries `coupon`: applied,
|
|
28
|
+
* or refused with the reason the order would be refused for.
|
|
28
29
|
*/
|
|
29
|
-
validate(items: readonly BasketLineInput[], options?:
|
|
30
|
-
destination?: DeliveryDestination;
|
|
31
|
-
}): Promise<ClientResult<CartValidation>>;
|
|
30
|
+
validate(items: readonly BasketLineInput[], options?: CartQuoteOptions): Promise<ClientResult<CartValidation>>;
|
|
32
31
|
/**
|
|
33
32
|
* Quote what an order would cost, for a basket or for one product exactly as
|
|
34
33
|
* its order form posts it (quantity, variants per unit, offer tier). The lines
|
|
35
34
|
* are built the way the order builds them, so the quote and the order agree.
|
|
36
35
|
*/
|
|
37
|
-
quote(lines: CartQuoteLines, options?:
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
quote(lines: CartQuoteLines, options?: CartQuoteOptions): Promise<ClientResult<CartValidation>>;
|
|
37
|
+
}
|
|
38
|
+
/** What a quote is asked besides its lines. */
|
|
39
|
+
export interface CartQuoteOptions {
|
|
40
|
+
/** Where it would be delivered: the answer then carries `delivery`. */
|
|
41
|
+
destination?: DeliveryDestination;
|
|
42
|
+
/** The code the shopper typed: the answer then carries `coupon`. */
|
|
43
|
+
couponCode?: string;
|
|
44
|
+
/**
|
|
45
|
+
* The phone the shopper typed, once there is one: a coupon for selected
|
|
46
|
+
* customers, or one use per customer, is then judged before the order.
|
|
47
|
+
*/
|
|
48
|
+
phone?: string;
|
|
40
49
|
}
|
|
41
50
|
/**
|
|
42
51
|
* Create cart namespace
|
package/dist/client/cart.js
CHANGED
|
@@ -19,9 +19,14 @@ import { localizedPath } from "./paths.js";
|
|
|
19
19
|
*/
|
|
20
20
|
export function createCartNamespace(core) {
|
|
21
21
|
// A closure rather than `this`: a theme may destructure `store.cart`.
|
|
22
|
-
const quote = async (lines, options) => {
|
|
23
|
-
const destination = options
|
|
24
|
-
return core.post(localizedPath(core.config, "/store/cart/validate"),
|
|
22
|
+
const quote = async (lines, options = {}) => {
|
|
23
|
+
const { destination, couponCode, phone } = options;
|
|
24
|
+
return core.post(localizedPath(core.config, "/store/cart/validate"), {
|
|
25
|
+
...lines,
|
|
26
|
+
...(destination ? { destination } : {}),
|
|
27
|
+
...(couponCode?.trim() ? { couponCode } : {}),
|
|
28
|
+
...(phone?.trim() ? { phone } : {}),
|
|
29
|
+
}, { schema: CartValidationSchema, operation: STOREFRONT_OPERATIONS.cartValidate });
|
|
25
30
|
};
|
|
26
31
|
return {
|
|
27
32
|
validate: (items, options) => quote({ items: [...items] }, options),
|
package/dist/coupon.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coupons on the storefront: the code a shareable link brings, and the
|
|
3
|
+
* refusal an order comes back with.
|
|
4
|
+
*
|
|
5
|
+
* What a code does is the server's to say — the cart quote answers `coupon`
|
|
6
|
+
* (applied, or refused with a reason) and the order is refused whole when its
|
|
7
|
+
* code does not apply. A theme shows those answers; it never decides one.
|
|
8
|
+
*/
|
|
9
|
+
import type { ClientResult } from "./domain.js";
|
|
10
|
+
import type { CouponRefusal } from "./types.js";
|
|
11
|
+
/** The error code of an order refused for its code. */
|
|
12
|
+
export declare const COUPON_NOT_APPLICABLE = "COUPON_NOT_APPLICABLE";
|
|
13
|
+
/** The query parameter a shareable coupon link carries: `/products/robe?coupon=RAMADAN10`. */
|
|
14
|
+
export declare const COUPON_URL_PARAM = "coupon";
|
|
15
|
+
/** The code a URL brings, trimmed; null when it brings none. */
|
|
16
|
+
export declare function couponFromUrl(url: URL): string | null;
|
|
17
|
+
/** Why an order was refused for its code, or null when it was refused — or placed — for anything else. */
|
|
18
|
+
export declare function couponRefusalOf(result: ClientResult<unknown>): CouponRefusal | null;
|
package/dist/coupon.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The error code of an order refused for its code. */
|
|
2
|
+
export const COUPON_NOT_APPLICABLE = "COUPON_NOT_APPLICABLE";
|
|
3
|
+
/** The query parameter a shareable coupon link carries: `/products/robe?coupon=RAMADAN10`. */
|
|
4
|
+
export const COUPON_URL_PARAM = "coupon";
|
|
5
|
+
/** The code a URL brings, trimmed; null when it brings none. */
|
|
6
|
+
export function couponFromUrl(url) {
|
|
7
|
+
const code = url.searchParams.get(COUPON_URL_PARAM)?.trim();
|
|
8
|
+
return code ? code.slice(0, 64) : null;
|
|
9
|
+
}
|
|
10
|
+
/** Why an order was refused for its code, or null when it was refused — or placed — for anything else. */
|
|
11
|
+
export function couponRefusalOf(result) {
|
|
12
|
+
if (result.success || result.domainError.code !== COUPON_NOT_APPLICABLE || typeof result.reason !== "string")
|
|
13
|
+
return null;
|
|
14
|
+
return {
|
|
15
|
+
reason: result.reason,
|
|
16
|
+
...(typeof result.shortBy === "number" ? { shortBy: result.shortBy } : {}),
|
|
17
|
+
...(typeof result.startsAt === "string" ? { startsAt: result.startsAt } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
package/dist/domain.d.ts
CHANGED
|
@@ -272,6 +272,8 @@ export interface CheckoutDraft {
|
|
|
272
272
|
readonly customFieldResponses: readonly CheckoutFieldAnswer[];
|
|
273
273
|
readonly otpProof: OtpProof | null;
|
|
274
274
|
readonly attribution: CheckoutAttribution;
|
|
275
|
+
/** The code the shopper typed, if any; the server decides whether it applies. */
|
|
276
|
+
readonly couponCode?: string | null;
|
|
275
277
|
}
|
|
276
278
|
/** Proof that the checkout phone was verified or that the provider failed open. */
|
|
277
279
|
export declare const OtpProofSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -292,6 +294,7 @@ export declare const OrderSchema: z.ZodObject<{
|
|
|
292
294
|
price: z.ZodNumber;
|
|
293
295
|
deliveryFee: z.ZodNumber;
|
|
294
296
|
total: z.ZodNumber;
|
|
297
|
+
couponDiscount: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
295
298
|
}, z.core.$strip>;
|
|
296
299
|
export type Order = z.infer<typeof OrderSchema>;
|
|
297
300
|
/** Server-owned decision for the thank-you page's conversion event. */
|
package/dist/domain.js
CHANGED
|
@@ -189,6 +189,7 @@ export const OrderSchema = z.object({
|
|
|
189
189
|
price: z.number().finite().nonnegative(),
|
|
190
190
|
deliveryFee: z.number().finite().nonnegative(),
|
|
191
191
|
total: z.number().finite().nonnegative(),
|
|
192
|
+
couponDiscount: z.number().finite().nonnegative().nullish(),
|
|
192
193
|
});
|
|
193
194
|
/** Server-owned decision for the thank-you page's conversion event. */
|
|
194
195
|
export const TrackingDecisionSchema = z.object({
|
|
@@ -35,9 +35,10 @@ export interface StorefrontRoutes<C extends AstroAPIContext> {
|
|
|
35
35
|
readonly otpVerify: StorefrontApiRoute<C>;
|
|
36
36
|
/**
|
|
37
37
|
* POST a basket `{ items }` or one product `{ productId, productName, quantity,
|
|
38
|
-
* variantSelections, offerId, … }`, each with an optional `destination
|
|
39
|
-
* `{ success: true, data: CartValidation }`.
|
|
40
|
-
* With a destination, `data.delivery` is the fee and total the order will
|
|
38
|
+
* variantSelections, offerId, … }`, each with an optional `destination`,
|
|
39
|
+
* `couponCode` and `phone` → `{ success: true, data: CartValidation }`.
|
|
40
|
+
* With a destination, `data.delivery` is the fee and total the order will
|
|
41
|
+
* charge; with a code, `data.coupon` is what it does.
|
|
41
42
|
*/
|
|
42
43
|
readonly cartValidate: StorefrontApiRoute<C>;
|
|
43
44
|
/** POST an `AbandonedCheckoutInput` → 204. */
|
|
@@ -32,16 +32,23 @@ const otpVerifyInput = z.object({
|
|
|
32
32
|
requestId: z.string().min(1).max(200),
|
|
33
33
|
code: z.string().min(1).max(16),
|
|
34
34
|
});
|
|
35
|
+
/** What a quote is asked besides its lines (`CartQuoteOptions`). */
|
|
36
|
+
const quoteAsks = {
|
|
37
|
+
destination: DeliveryDestinationSchema.optional(),
|
|
38
|
+
couponCode: z.string().max(64).optional(),
|
|
39
|
+
phone: z.string().max(32).optional(),
|
|
40
|
+
};
|
|
35
41
|
/**
|
|
36
42
|
* A basket, or one product as its order form posts it — the same two shapes the
|
|
37
|
-
* server's quote takes — each with an optional destination to quote to
|
|
43
|
+
* server's quote takes — each with an optional destination to quote to, the
|
|
44
|
+
* code typed and the phone typed.
|
|
38
45
|
*/
|
|
39
46
|
const cartValidateInput = z.union([
|
|
40
47
|
z.object({
|
|
41
48
|
items: z.array(BasketLineInputSchema).min(1).max(MAX_BASKET_LINES),
|
|
42
|
-
|
|
49
|
+
...quoteAsks,
|
|
43
50
|
}),
|
|
44
|
-
ProductQuoteInputSchema.extend(
|
|
51
|
+
ProductQuoteInputSchema.extend(quoteAsks),
|
|
45
52
|
]);
|
|
46
53
|
const abandonedConvertInput = z.object({
|
|
47
54
|
sessionId: z.string().uuid(),
|
|
@@ -125,7 +132,11 @@ export function createStorefrontRoutes(options) {
|
|
|
125
132
|
method: "POST",
|
|
126
133
|
inputSchema: cartValidateInput,
|
|
127
134
|
successBody: (data) => ({ success: true, data }),
|
|
128
|
-
call: (client, { destination, ...lines }) => client.cart.quote(lines,
|
|
135
|
+
call: (client, { destination, couponCode, phone, ...lines }) => client.cart.quote(lines, {
|
|
136
|
+
...(destination ? { destination } : {}),
|
|
137
|
+
...(couponCode !== undefined ? { couponCode } : {}),
|
|
138
|
+
...(phone !== undefined ? { phone } : {}),
|
|
139
|
+
}),
|
|
129
140
|
}),
|
|
130
141
|
abandonedUpsert: route({
|
|
131
142
|
method: "POST",
|
package/dist/index.d.ts
CHANGED
|
@@ -34,8 +34,10 @@ 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
|
|
37
|
+
export { getOfferTierView, getQuantityTierViews } from "./offer-view.js";
|
|
38
|
+
export { COUPON_NOT_APPLICABLE, COUPON_URL_PARAM, couponFromUrl, couponRefusalOf } from "./coupon.js";
|
|
39
|
+
export type { CartQuoteOptions } from "./client/cart.js";
|
|
40
|
+
export type { OfferTierInput, OfferTierView, QuantityTierView } from "./offer-view.js";
|
|
39
41
|
export { createAbandonedCheckoutTracker } from "./abandoned-checkout.js";
|
|
40
42
|
export type { AbandonedCheckoutPorts, AbandonedCheckoutSnapshot, AbandonedCheckoutStorage, AbandonedCheckoutTracker, } from "./abandoned-checkout.js";
|
|
41
43
|
export { META_CURRENCY, buildMetaAddToCartPayload, buildMetaInitiateCheckoutPayload, buildMetaProductPayload, buildMetaViewContentPayload, createMetaPixelTracker, } from "./meta-tracking.js";
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ 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
|
+
export { COUPON_NOT_APPLICABLE, COUPON_URL_PARAM, couponFromUrl, couponRefusalOf } from "./coupon.js";
|
|
29
30
|
export { createAbandonedCheckoutTracker } from "./abandoned-checkout.js";
|
|
30
31
|
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,8 @@ 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>>;
|
|
716
|
+
discount: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
680
717
|
maxQuantity: z.ZodNullable<z.ZodNumber>;
|
|
681
718
|
blocker: z.ZodNullable<z.ZodEnum<{
|
|
682
719
|
missing: "missing";
|
|
@@ -690,6 +727,24 @@ export declare const CartRewardSchema: z.ZodObject<{
|
|
|
690
727
|
productName: z.ZodNullable<z.ZodString>;
|
|
691
728
|
quantity: z.ZodNumber;
|
|
692
729
|
}, z.core.$strip>;
|
|
730
|
+
/**
|
|
731
|
+
* What the code the quote was asked with did: applied, or refused with the
|
|
732
|
+
* reason the order would be refused for. `reason` is an open string — a reason
|
|
733
|
+
* this SDK has not seen yet still validates — and `shortBy` / `startsAt` come
|
|
734
|
+
* with the reasons that have them.
|
|
735
|
+
*/
|
|
736
|
+
export declare const CouponOutcomeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
737
|
+
code: z.ZodString;
|
|
738
|
+
applied: z.ZodLiteral<true>;
|
|
739
|
+
discount: z.ZodNumber;
|
|
740
|
+
freeShipping: z.ZodBoolean;
|
|
741
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
742
|
+
code: z.ZodString;
|
|
743
|
+
applied: z.ZodLiteral<false>;
|
|
744
|
+
reason: z.ZodString;
|
|
745
|
+
shortBy: z.ZodOptional<z.ZodNumber>;
|
|
746
|
+
startsAt: z.ZodOptional<z.ZodString>;
|
|
747
|
+
}, z.core.$strip>], "applied">;
|
|
693
748
|
export declare const CartValidationSchema: z.ZodObject<{
|
|
694
749
|
lines: z.ZodArray<z.ZodObject<{
|
|
695
750
|
productId: z.ZodString;
|
|
@@ -699,6 +754,8 @@ export declare const CartValidationSchema: z.ZodObject<{
|
|
|
699
754
|
quantity: z.ZodNumber;
|
|
700
755
|
unitPrice: z.ZodNumber;
|
|
701
756
|
lineTotal: z.ZodNumber;
|
|
757
|
+
offerId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
758
|
+
discount: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
702
759
|
maxQuantity: z.ZodNullable<z.ZodNumber>;
|
|
703
760
|
blocker: z.ZodNullable<z.ZodEnum<{
|
|
704
761
|
missing: "missing";
|
|
@@ -728,6 +785,18 @@ export declare const CartValidationSchema: z.ZodObject<{
|
|
|
728
785
|
fee: z.ZodNull;
|
|
729
786
|
total: z.ZodNull;
|
|
730
787
|
}, z.core.$strip>], "available">>;
|
|
788
|
+
coupon: z.ZodOptional<z.ZodNullable<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
789
|
+
code: z.ZodString;
|
|
790
|
+
applied: z.ZodLiteral<true>;
|
|
791
|
+
discount: z.ZodNumber;
|
|
792
|
+
freeShipping: z.ZodBoolean;
|
|
793
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
794
|
+
code: z.ZodString;
|
|
795
|
+
applied: z.ZodLiteral<false>;
|
|
796
|
+
reason: z.ZodString;
|
|
797
|
+
shortBy: z.ZodOptional<z.ZodNumber>;
|
|
798
|
+
startsAt: z.ZodOptional<z.ZodString>;
|
|
799
|
+
}, z.core.$strip>], "applied">>>;
|
|
731
800
|
}, z.core.$strip>;
|
|
732
801
|
export declare const CreatedOrderSchema: z.ZodObject<{
|
|
733
802
|
orderId: z.ZodString;
|
|
@@ -735,6 +804,7 @@ export declare const CreatedOrderSchema: z.ZodObject<{
|
|
|
735
804
|
price: z.ZodNumber;
|
|
736
805
|
deliveryFee: z.ZodNumber;
|
|
737
806
|
total: z.ZodNumber;
|
|
807
|
+
couponDiscount: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
738
808
|
}, z.core.$strip>;
|
|
739
809
|
export declare const OrderTrackingSchema: z.ZodObject<{
|
|
740
810
|
pixelId: z.ZodNullable<z.ZodString>;
|
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,10 @@ export const CartValidationLineSchema = z.object({
|
|
|
331
351
|
quantity: z.number().int(),
|
|
332
352
|
unitPrice: z.number(),
|
|
333
353
|
lineTotal: z.number(),
|
|
354
|
+
/** The offer applied to the line; absent from a server that predates offers on lines. */
|
|
355
|
+
offerId: z.string().nullish(),
|
|
356
|
+
/** The line's share of the coupon's discount, already out of `lineTotal`; absent from a server that predates coupons. */
|
|
357
|
+
discount: z.number().nullish(),
|
|
334
358
|
maxQuantity: z.number().nullable(),
|
|
335
359
|
blocker: z.enum(["missing", "unlisted", "out_of_stock"]).nullable(),
|
|
336
360
|
});
|
|
@@ -340,6 +364,16 @@ export const CartRewardSchema = z.object({
|
|
|
340
364
|
productName: z.string().nullable(),
|
|
341
365
|
quantity: z.number().int(),
|
|
342
366
|
});
|
|
367
|
+
/**
|
|
368
|
+
* What the code the quote was asked with did: applied, or refused with the
|
|
369
|
+
* reason the order would be refused for. `reason` is an open string — a reason
|
|
370
|
+
* this SDK has not seen yet still validates — and `shortBy` / `startsAt` come
|
|
371
|
+
* with the reasons that have them.
|
|
372
|
+
*/
|
|
373
|
+
export const CouponOutcomeSchema = z.discriminatedUnion("applied", [
|
|
374
|
+
z.object({ code: z.string(), applied: z.literal(true), discount: z.number(), freeShipping: z.boolean() }),
|
|
375
|
+
z.object({ code: z.string(), applied: z.literal(false), reason: z.string(), shortBy: z.number().optional(), startsAt: z.string().optional() }),
|
|
376
|
+
]);
|
|
343
377
|
export const CartValidationSchema = z.object({
|
|
344
378
|
lines: z.array(CartValidationLineSchema),
|
|
345
379
|
subtotal: z.number(),
|
|
@@ -356,6 +390,8 @@ export const CartValidationSchema = z.object({
|
|
|
356
390
|
z.object({ available: z.literal(false), fee: z.null(), total: z.null() }),
|
|
357
391
|
])
|
|
358
392
|
.optional(),
|
|
393
|
+
/** The code's outcome: null without one; absent from a server that predates coupons. */
|
|
394
|
+
coupon: CouponOutcomeSchema.nullish(),
|
|
359
395
|
});
|
|
360
396
|
export const CreatedOrderSchema = z.object({
|
|
361
397
|
orderId: z.string(),
|
|
@@ -363,6 +399,8 @@ export const CreatedOrderSchema = z.object({
|
|
|
363
399
|
price: z.number(),
|
|
364
400
|
deliveryFee: z.number(),
|
|
365
401
|
total: z.number(),
|
|
402
|
+
/** What the coupon took off; absent from a server that predates coupons. */
|
|
403
|
+
couponDiscount: z.number().nullish(),
|
|
366
404
|
});
|
|
367
405
|
export const OrderTrackingSchema = z.object({
|
|
368
406
|
pixelId: z.string().nullable(),
|
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,12 @@ 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, offers and the coupon applied. */
|
|
310
314
|
lineTotal: number;
|
|
315
|
+
/** The offer applied to the line; null or absent when none. */
|
|
316
|
+
offerId?: string | null | undefined;
|
|
317
|
+
/** The line's share of the coupon's discount, already out of `lineTotal`; null or absent without one. */
|
|
318
|
+
discount?: number | null | undefined;
|
|
311
319
|
/** Units still available; null when the product is not stock-tracked. */
|
|
312
320
|
maxQuantity: number | null;
|
|
313
321
|
/**
|
|
@@ -343,7 +351,31 @@ export interface CartValidation {
|
|
|
343
351
|
* deliver there, and an order to it is refused.
|
|
344
352
|
*/
|
|
345
353
|
delivery?: CartDeliveryQuote | undefined;
|
|
354
|
+
/** What the code the quote was asked with did; null without one, absent from a server that predates coupons. */
|
|
355
|
+
coupon?: CouponOutcome | null | undefined;
|
|
346
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Why a code does not apply. Open: a server may name a reason this SDK has not
|
|
359
|
+
* seen yet, and a theme then shows its general sentence.
|
|
360
|
+
*/
|
|
361
|
+
export type CouponRefusalReason = "not_found" | "not_started" | "expired" | "used_up" | "identify_first" | "not_eligible" | "already_used" | "no_eligible_products" | "below_minimum_subtotal" | "below_minimum_quantity" | "delivery_already_free" | (string & {});
|
|
362
|
+
export interface CouponRefusal {
|
|
363
|
+
reason: CouponRefusalReason;
|
|
364
|
+
/** The dinars (`below_minimum_subtotal`) or items (`below_minimum_quantity`) still missing. */
|
|
365
|
+
shortBy?: number | undefined;
|
|
366
|
+
/** When a `not_started` code begins. */
|
|
367
|
+
startsAt?: string | undefined;
|
|
368
|
+
}
|
|
369
|
+
/** A code applied — `discount` already out of the lines, delivery free with `freeShipping` — or refused. */
|
|
370
|
+
export type CouponOutcome = {
|
|
371
|
+
code: string;
|
|
372
|
+
applied: true;
|
|
373
|
+
discount: number;
|
|
374
|
+
freeShipping: boolean;
|
|
375
|
+
} | ({
|
|
376
|
+
code: string;
|
|
377
|
+
applied: false;
|
|
378
|
+
} & CouponRefusal);
|
|
347
379
|
export type CartDeliveryQuote = {
|
|
348
380
|
available: true;
|
|
349
381
|
fee: number;
|
|
@@ -359,8 +391,10 @@ export interface CreatedOrder {
|
|
|
359
391
|
orderNumber: string;
|
|
360
392
|
price: number;
|
|
361
393
|
deliveryFee: number;
|
|
362
|
-
/** price + deliveryFee,
|
|
394
|
+
/** price + deliveryFee, offers and the coupon applied. */
|
|
363
395
|
total: number;
|
|
396
|
+
/** What the coupon took off; null or absent without one. */
|
|
397
|
+
couponDiscount?: number | null | undefined;
|
|
364
398
|
}
|
|
365
399
|
/** One basket line in a create-order body. */
|
|
366
400
|
export interface CreateOrderItemInput {
|
|
@@ -425,6 +459,8 @@ export interface CreateOrderInput {
|
|
|
425
459
|
items?: CreateOrderItemInput[] | undefined;
|
|
426
460
|
/** Landing-page attribution — best-effort; an unknown slug never blocks. */
|
|
427
461
|
landingPageSlug?: string | undefined;
|
|
462
|
+
/** The code the shopper typed. An order whose code does not apply is refused whole (`couponRefusalOf`). */
|
|
463
|
+
couponCode?: string | undefined;
|
|
428
464
|
}
|
|
429
465
|
/**
|
|
430
466
|
* The order's tracking destination — which Meta pixel it belongs to and
|
package/package.json
CHANGED