@rebuy/rebuy 3.17.0-rc.1 → 3.18.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/dist/gwp/index.cjs +4 -0
- package/dist/gwp/index.mjs +4 -0
- package/dist/gwp/types.d.ts +27 -5
- package/dist/server/analytics.d.ts +13 -3
- package/dist/server/analyticsSegments.d.ts +6 -0
- package/dist/server/giftValidation.d.ts +17 -19
- package/dist/server/giftWidgets.d.ts +37 -0
- package/dist/server/index.cjs +246 -102
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.mjs +246 -102
- package/dist/server/requestSchemas.d.ts +56 -1
- package/dist/server/sgwpCapacity.d.ts +25 -0
- package/dist/server/sgwpValidation.d.ts +19 -0
- package/dist/server/shared.d.ts +5 -4
- package/dist/server/shopSubtotal.d.ts +25 -0
- package/package.json +1 -1
package/dist/gwp/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/gwp/index.ts
|
|
21
21
|
var gwp_exports = {};
|
|
22
22
|
__export(gwp_exports, {
|
|
23
|
+
GIFT_KINDS: () => GIFT_KINDS,
|
|
23
24
|
getValidGwpProductIds: () => getValidGwpProductIds,
|
|
24
25
|
hasLostDiscount: () => hasLostDiscount,
|
|
25
26
|
hasUnsupportedRuleTypes: () => hasUnsupportedRuleTypes,
|
|
@@ -50,3 +51,6 @@ var isDefinitiveRuleAnswer = (metadata) => metadata != null && (metadata.matched
|
|
|
50
51
|
// src/gwp/shouldRemoveGift.ts
|
|
51
52
|
var hasLostDiscount = (gift) => !gift.discounted && gift.cost > 0;
|
|
52
53
|
var shouldRemoveGift = (gift, validIds) => !validIds.has(gift.productId) || hasLostDiscount(gift);
|
|
54
|
+
|
|
55
|
+
// src/gwp/types.ts
|
|
56
|
+
var GIFT_KINDS = ["gwp", "sgwp"];
|
package/dist/gwp/index.mjs
CHANGED
|
@@ -19,7 +19,11 @@ var isDefinitiveRuleAnswer = (metadata) => metadata != null && (metadata.matched
|
|
|
19
19
|
// src/gwp/shouldRemoveGift.ts
|
|
20
20
|
var hasLostDiscount = (gift) => !gift.discounted && gift.cost > 0;
|
|
21
21
|
var shouldRemoveGift = (gift, validIds) => !validIds.has(gift.productId) || hasLostDiscount(gift);
|
|
22
|
+
|
|
23
|
+
// src/gwp/types.ts
|
|
24
|
+
var GIFT_KINDS = ["gwp", "sgwp"];
|
|
22
25
|
export {
|
|
26
|
+
GIFT_KINDS,
|
|
23
27
|
getValidGwpProductIds,
|
|
24
28
|
hasLostDiscount,
|
|
25
29
|
hasUnsupportedRuleTypes,
|
package/dist/gwp/types.d.ts
CHANGED
|
@@ -1,17 +1,39 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The attribution a gift line carries, as the wire tags it: `gwp` = `Rebuy Gift with Purchase`, `sgwp` =
|
|
3
|
+
* `Rebuy Selectable Gift with Purchase`. `gwp` lines run the GWP rules; `sgwp` lines are judged against their
|
|
4
|
+
* widget's milestones (REB-24363).
|
|
5
|
+
*/
|
|
6
|
+
export declare const GIFT_KINDS: readonly ["gwp", "sgwp"];
|
|
7
|
+
export type GiftKind = (typeof GIFT_KINDS)[number];
|
|
8
|
+
/**
|
|
9
|
+
* A gift cart line the client asks the server to validate (GWP or selectable GWP — see {@link GiftKind}).
|
|
10
|
+
* This is the WIRE shape — what a client sends, `z.input` of the server's `GiftInputSchema` — so the two
|
|
11
|
+
* REB-24363 fields are optional here: a client that predates them sends GWP lines only, and the schema
|
|
12
|
+
* defaults fill them (`kind: 'gwp'`, `quantity: 1`) before the server reads either. The parsed twin
|
|
13
|
+
* (`GiftInput`, `./server`) carries both as required.
|
|
14
|
+
*/
|
|
2
15
|
export type GwpGift = {
|
|
3
16
|
/**
|
|
4
|
-
* The gift line's total in the buyer's MARKET currency, MAJOR units (e.g. `19.99`) — a
|
|
5
|
-
* Function-discounted (free) gift is `0`.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
17
|
+
* The gift line's total in the buyer's MARKET (presentment) currency, MAJOR units (e.g. `19.99`) — a
|
|
18
|
+
* Function-discounted (free) gift is `0`. Send the Shopify cart-line total as-is; both the unit and the
|
|
19
|
+
* magnitude are contractual. A `gwp` line only has its zero/non-zero read (`cost > 0` ⇒ the gift lost
|
|
20
|
+
* its discount), but an `sgwp` line's cost is subtracted from the subtotal its milestones are judged
|
|
21
|
+
* against (×100, ÷ the FX rate), so a minor-unit or unit-price value there would over-subtract and
|
|
22
|
+
* silently strip earned gifts (REB-24363).
|
|
8
23
|
*/
|
|
9
24
|
cost: number;
|
|
10
25
|
/** Whether the Shopify Function discount is currently allocated to the line. */
|
|
11
26
|
discounted: boolean;
|
|
27
|
+
/** Which attribution the line carries; omitted by clients that predate the field (reads as `gwp`). */
|
|
28
|
+
kind?: GiftKind;
|
|
12
29
|
/** Shopify cart-line id to remove. */
|
|
13
30
|
lineId: string;
|
|
14
31
|
productId: number;
|
|
32
|
+
/**
|
|
33
|
+
* The line's unit count; the selectable-GWP verdict trims surplus units against milestone capacity.
|
|
34
|
+
* Omitted by clients that predate the field (reads as one unit — a GWP verdict is whole-line anyway).
|
|
35
|
+
*/
|
|
36
|
+
quantity?: number;
|
|
15
37
|
/** The Rebuy widget that added the gift (`_widget_id`). */
|
|
16
38
|
widgetId: number;
|
|
17
39
|
};
|
|
@@ -25,7 +25,7 @@ declare const TAXONOMY: {
|
|
|
25
25
|
element_id: string;
|
|
26
26
|
experiment_type: string;
|
|
27
27
|
};
|
|
28
|
-
buildTags: ({ experimentId }: Record<string, unknown
|
|
28
|
+
buildTags: ({ experimentId }: Record<string, unknown>, input: AnalyticsInput) => string[] | undefined;
|
|
29
29
|
noun: "cart";
|
|
30
30
|
subject: "abtest";
|
|
31
31
|
usesWidgetId: false;
|
|
@@ -37,7 +37,7 @@ declare const TAXONOMY: {
|
|
|
37
37
|
buildMeta: ({ productId, variantId }: Record<string, unknown>) => {
|
|
38
38
|
[x: string]: true;
|
|
39
39
|
};
|
|
40
|
-
buildTags: ({ experimentId }: Record<string, unknown
|
|
40
|
+
buildTags: ({ experimentId }: Record<string, unknown>, input: AnalyticsInput) => string[] | undefined;
|
|
41
41
|
/** Under an experiment the add belongs to the VARIANT element, not the placeholder (React parity). */
|
|
42
42
|
buildWidgetId: ({ elementId }: Record<string, unknown>, input: {
|
|
43
43
|
events: {
|
|
@@ -49,6 +49,11 @@ declare const TAXONOMY: {
|
|
|
49
49
|
visitorId: string;
|
|
50
50
|
cartToken?: string | undefined;
|
|
51
51
|
checkoutToken?: string | undefined;
|
|
52
|
+
segments?: {
|
|
53
|
+
device?: string | undefined;
|
|
54
|
+
source?: string | undefined;
|
|
55
|
+
visitor?: string | undefined;
|
|
56
|
+
} | undefined;
|
|
52
57
|
widgetId?: string | undefined;
|
|
53
58
|
}) => string | undefined;
|
|
54
59
|
noun: "widget";
|
|
@@ -77,7 +82,7 @@ declare const TAXONOMY: {
|
|
|
77
82
|
verb: "tracking";
|
|
78
83
|
};
|
|
79
84
|
'widget-viewed': {
|
|
80
|
-
buildTags: ({ experimentId }: Record<string, unknown
|
|
85
|
+
buildTags: ({ experimentId }: Record<string, unknown>, input: AnalyticsInput) => string[] | undefined;
|
|
81
86
|
/** Under an experiment the view belongs to the VARIANT element, not the placeholder (React parity, same as added-to-cart) — else views credit the placeholder while adds credit the variant and per-widget conversion is meaningless. */
|
|
82
87
|
buildWidgetId: ({ elementId }: Record<string, unknown>, input: {
|
|
83
88
|
events: {
|
|
@@ -89,6 +94,11 @@ declare const TAXONOMY: {
|
|
|
89
94
|
visitorId: string;
|
|
90
95
|
cartToken?: string | undefined;
|
|
91
96
|
checkoutToken?: string | undefined;
|
|
97
|
+
segments?: {
|
|
98
|
+
device?: string | undefined;
|
|
99
|
+
source?: string | undefined;
|
|
100
|
+
visitor?: string | undefined;
|
|
101
|
+
} | undefined;
|
|
92
102
|
widgetId?: string | undefined;
|
|
93
103
|
}) => string | undefined;
|
|
94
104
|
noun: "widget";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type AnalyticsSegmentsInput } from '../server/requestSchemas';
|
|
2
|
+
/**
|
|
3
|
+
* One `<baseTag>.<dimension>.<bucket>` tag per segment whose bucket passes {@link SEGMENT_BUCKETS}; invalid
|
|
4
|
+
* or absent dimensions contribute nothing (so valid siblings still ride along).
|
|
5
|
+
*/
|
|
6
|
+
export declare const segmentTags: (baseTag: string, segments: AnalyticsSegmentsInput | undefined) => string[];
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import { type GiftValidationInput, type ServerContext } from '../server/shared';
|
|
1
|
+
import { type GiftValidationInput, type GiftValidationResponse, type ServerContext } from '../server/shared';
|
|
2
2
|
/**
|
|
3
|
-
* Validates gift lines
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Validates the gift lines and returns the receipt. The two gift kinds get independent verdicts, each
|
|
4
|
+
* filling its own list — so a line id can never land in both: GWP (`gwp`) lines are judged whole-line by
|
|
5
|
+
* their widget's rules and fill `remove` ({@link validateGwpGifts}); selectable GWP (`sgwp`) lines are
|
|
6
|
+
* trimmed to their widget's milestone capacity and fill `adjust` (target quantity, `0` = remove —
|
|
7
|
+
* {@link validateSelectableGifts}). A selectable gift may be legitimately priced, so the GWP charged-gift
|
|
8
|
+
* rule never touches it. Both halves fail safe per widget, so a degraded answer for one widget never
|
|
9
|
+
* disturbs another's verdict.
|
|
6
10
|
*
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* matching the React extension). **Discount loss** ({@link hasLostDiscount}: `!discounted && cost > 0`) is cart-observable
|
|
16
|
-
* and needs NO engine answer, so it removes the gift on EVERY path, degraded or not — a buyer must not keep
|
|
17
|
-
* paying for a "gift" because the rule engine was degraded. (React removed on any successful fetch; this
|
|
18
|
-
* splits the observable discount-loss out of the qualification fail-safe rather than gating both behind it.)
|
|
11
|
+
* The receipt's one-verdict-per-line invariant (`GiftValidationResponseSchema`) is upheld HERE, not by the
|
|
12
|
+
* client: nothing stops a buggy watcher sending one `lineId` twice — under both kinds (a line carrying
|
|
13
|
+
* both attributions, or a double push), or as `sgwp` under two widgets — which would otherwise land it in
|
|
14
|
+
* both lists, or twice in `adjust` with conflicting targets, and fail the response schema at the route — a
|
|
15
|
+
* 500 where the feature promises fail-safe. The strictest verdict wins: removal over any target (a removed
|
|
16
|
+
* line has no quantity left), and the smallest target among duplicates (each is a trim, so the smallest can
|
|
17
|
+
* never exceed what was sent). `remove` is likewise named once per line (a `gwp` line sent under two
|
|
18
|
+
* widgets is removed by both legs), so a client issuing one cart mutation per entry never removes twice.
|
|
19
19
|
*/
|
|
20
|
-
export declare const validateGifts: (input: GiftValidationInput, ctx: ServerContext) => Promise<
|
|
21
|
-
remove: string[];
|
|
22
|
-
}>;
|
|
20
|
+
export declare const validateGifts: (input: GiftValidationInput, ctx: ServerContext) => Promise<GiftValidationResponse>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type GiftKind } from '../gwp';
|
|
3
|
+
import { type GiftInput, type Reporter, type ServerContext } from '../server/shared';
|
|
4
|
+
/**
|
|
5
|
+
* One selectable-GWP milestone, RAW (snake_case) as the retired React validator read it: `unlock_price` in
|
|
6
|
+
* shop MAJOR units, `selectable_product_quantity` in units (a non-negative whole count — a fractional or
|
|
7
|
+
* negative capacity is nonsense, so it is malformed rather than summed).
|
|
8
|
+
*
|
|
9
|
+
* `unlock_price` is deliberately NOT bounded below. A negative or zero price is a legitimate "always
|
|
10
|
+
* unlocked" tier (the progress-bar converter tolerates the same via `Number(...) || 0`), and it only
|
|
11
|
+
* grants ITS OWN capacity. Rejecting it would mark the whole list malformed, which keeps every gift the
|
|
12
|
+
* widget added — unlimited capacity, a strictly worse degradation than the one avoided.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SgwpMilestoneSchema: z.ZodObject<{
|
|
15
|
+
selectable_product_quantity: z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodCoercedNumber<string | number>>, z.ZodNumber>;
|
|
16
|
+
unlock_price: z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodCoercedNumber<string | number>>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
/** One parsed selectable-GWP milestone: the shop-major-unit unlock price and the gift units it grants. */
|
|
19
|
+
export type SgwpMilestone = z.infer<typeof SgwpMilestoneSchema>;
|
|
20
|
+
/**
|
|
21
|
+
* One gift widget's raw settings from the engine (the `data` envelope unwrapped, so a deleted widget's
|
|
22
|
+
* `{ data: null }` reads as `null`). Throws on an upstream failure — every caller fails safe (keeps the
|
|
23
|
+
* widget's gifts), so the throw is the signal, not a bug.
|
|
24
|
+
*/
|
|
25
|
+
export declare const fetchGiftWidgetSettings: (widgetId: number, shop: string, ctx: ServerContext) => Promise<unknown>;
|
|
26
|
+
/** A GWP widget's data-source path from its raw settings `endpoint` (uniform across widget types). */
|
|
27
|
+
export declare const readDataSourcePath: (settings: unknown) => string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* A selectable-GWP widget's milestones from its raw settings. Absent or empty milestones are an empty list
|
|
30
|
+
* (the widget has nothing to judge against — silent). A MALFORMED list also reads as empty, because the
|
|
31
|
+
* fail-safe direction is to keep the gifts, but that is a settings-contract drift, so it is reported — with
|
|
32
|
+
* the first issue's path AND message, since the likeliest real drift (`milestones` is an object, not an
|
|
33
|
+
* array) fails at the top level, where the path is empty and only the message says what arrived.
|
|
34
|
+
*/
|
|
35
|
+
export declare const readMilestones: (settings: unknown, report?: Reporter) => SgwpMilestone[];
|
|
36
|
+
/** The gifts of one `kind`, grouped by widget in request order (insertion-ordered map; lines keep their order within a widget). */
|
|
37
|
+
export declare const groupGiftsByWidget: (gifts: GiftInput[], kind: GiftKind) => Map<number, GiftInput[]>;
|