@rebuy/rebuy 3.7.0 → 3.8.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/index.cjs +7 -1
- package/dist/index.mjs +7 -1
- package/dist/schema/checkout-and-beyond/index.cjs +7 -1
- package/dist/schema/checkout-and-beyond/index.mjs +7 -1
- package/dist/schema/widget-data.cjs +7 -1
- package/dist/schema/widget-data.mjs +7 -1
- package/dist/schema/widgets/checkout-and-beyond/monetize.d.ts +13 -2
- package/dist/server/index.cjs +236 -187
- package/dist/server/index.mjs +236 -187
- package/dist/server/requestSchemas.d.ts +59 -8
- package/dist/transforms/index.cjs +7 -1
- package/dist/transforms/index.mjs +7 -1
- package/dist/transforms/monetizeV1/convertMonetizeToV2.d.ts +3 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2519,7 +2519,13 @@ var import_zod23 = require("zod");
|
|
|
2519
2519
|
var CABMonetizeSection = import_zod23.z.object({
|
|
2520
2520
|
...CABVariableFields,
|
|
2521
2521
|
name: import_zod23.z.string().optional(),
|
|
2522
|
-
/**
|
|
2522
|
+
/**
|
|
2523
|
+
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
2524
|
+
* context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
|
|
2525
|
+
* nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
|
|
2526
|
+
*/
|
|
2527
|
+
offers: import_zod23.z.array(import_zod23.z.lazy(() => CABSection)).optional(),
|
|
2528
|
+
/** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
|
|
2523
2529
|
privacyPolicyUrl: import_zod23.z.string().optional(),
|
|
2524
2530
|
sectionId: import_zod23.z.uuid().default(() => (0, import_uuid9.v7)()),
|
|
2525
2531
|
sectionType: import_zod23.z.literal(SectionType.monetize).default(SectionType.monetize),
|
package/dist/index.mjs
CHANGED
|
@@ -2238,7 +2238,13 @@ import { z as z23 } from "zod";
|
|
|
2238
2238
|
var CABMonetizeSection = z23.object({
|
|
2239
2239
|
...CABVariableFields,
|
|
2240
2240
|
name: z23.string().optional(),
|
|
2241
|
-
/**
|
|
2241
|
+
/**
|
|
2242
|
+
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
2243
|
+
* context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
|
|
2244
|
+
* nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
|
|
2245
|
+
*/
|
|
2246
|
+
offers: z23.array(z23.lazy(() => CABSection)).optional(),
|
|
2247
|
+
/** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
|
|
2242
2248
|
privacyPolicyUrl: z23.string().optional(),
|
|
2243
2249
|
sectionId: z23.uuid().default(() => uuidv78()),
|
|
2244
2250
|
sectionType: z23.literal(SectionType.monetize).default(SectionType.monetize),
|
|
@@ -1088,7 +1088,13 @@ var import_zod13 = require("zod");
|
|
|
1088
1088
|
var CABMonetizeSection = import_zod13.z.object({
|
|
1089
1089
|
...CABVariableFields,
|
|
1090
1090
|
name: import_zod13.z.string().optional(),
|
|
1091
|
-
/**
|
|
1091
|
+
/**
|
|
1092
|
+
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
1093
|
+
* context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
|
|
1094
|
+
* nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
|
|
1095
|
+
*/
|
|
1096
|
+
offers: import_zod13.z.array(import_zod13.z.lazy(() => CABSection)).optional(),
|
|
1097
|
+
/** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
|
|
1092
1098
|
privacyPolicyUrl: import_zod13.z.string().optional(),
|
|
1093
1099
|
sectionId: import_zod13.z.uuid().default(() => (0, import_uuid8.v7)()),
|
|
1094
1100
|
sectionType: import_zod13.z.literal(SectionType.monetize).default(SectionType.monetize),
|
|
@@ -896,7 +896,13 @@ import { z as z13 } from "zod";
|
|
|
896
896
|
var CABMonetizeSection = z13.object({
|
|
897
897
|
...CABVariableFields,
|
|
898
898
|
name: z13.string().optional(),
|
|
899
|
-
/**
|
|
899
|
+
/**
|
|
900
|
+
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
901
|
+
* context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
|
|
902
|
+
* nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
|
|
903
|
+
*/
|
|
904
|
+
offers: z13.array(z13.lazy(() => CABSection)).optional(),
|
|
905
|
+
/** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
|
|
900
906
|
privacyPolicyUrl: z13.string().optional(),
|
|
901
907
|
sectionId: z13.uuid().default(() => uuidv78()),
|
|
902
908
|
sectionType: z13.literal(SectionType.monetize).default(SectionType.monetize),
|
|
@@ -934,7 +934,13 @@ var import_zod16 = require("zod");
|
|
|
934
934
|
var CABMonetizeSection = import_zod16.z.object({
|
|
935
935
|
...CABVariableFields,
|
|
936
936
|
name: import_zod16.z.string().optional(),
|
|
937
|
-
/**
|
|
937
|
+
/**
|
|
938
|
+
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
939
|
+
* context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
|
|
940
|
+
* nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
|
|
941
|
+
*/
|
|
942
|
+
offers: import_zod16.z.array(import_zod16.z.lazy(() => CABSection)).optional(),
|
|
943
|
+
/** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
|
|
938
944
|
privacyPolicyUrl: import_zod16.z.string().optional(),
|
|
939
945
|
sectionId: import_zod16.z.uuid().default(() => (0, import_uuid8.v7)()),
|
|
940
946
|
sectionType: import_zod16.z.literal(SectionType.monetize).default(SectionType.monetize),
|
|
@@ -909,7 +909,13 @@ import { z as z16 } from "zod";
|
|
|
909
909
|
var CABMonetizeSection = z16.object({
|
|
910
910
|
...CABVariableFields,
|
|
911
911
|
name: z16.string().optional(),
|
|
912
|
-
/**
|
|
912
|
+
/**
|
|
913
|
+
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
914
|
+
* context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
|
|
915
|
+
* nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
|
|
916
|
+
*/
|
|
917
|
+
offers: z16.array(z16.lazy(() => CABSection)).optional(),
|
|
918
|
+
/** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
|
|
913
919
|
privacyPolicyUrl: z16.string().optional(),
|
|
914
920
|
sectionId: z16.uuid().default(() => uuidv78()),
|
|
915
921
|
sectionType: z16.literal(SectionType.monetize).default(SectionType.monetize),
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { type SectionDetails } from './shared';
|
|
2
|
+
import { CABSection, type SectionDetails } from './shared';
|
|
3
|
+
import { CABVariableFields } from './variables';
|
|
3
4
|
export declare const CABMonetizeSection: z.ZodObject<{
|
|
4
5
|
name: z.ZodOptional<z.ZodString>;
|
|
6
|
+
offers: z.ZodOptional<z.ZodArray<z.ZodLazy<z.ZodType<CABSection, unknown, z.core.$ZodTypeInternals<CABSection, unknown>>>>>;
|
|
5
7
|
privacyPolicyUrl: z.ZodOptional<z.ZodString>;
|
|
6
8
|
sectionId: z.ZodDefault<z.ZodUUID>;
|
|
7
9
|
sectionType: z.ZodDefault<z.ZodLiteral<"monetize">>;
|
|
@@ -154,5 +156,14 @@ export declare const CABMonetizeSection: z.ZodObject<{
|
|
|
154
156
|
visibleWhen: z.ZodDefault<z.ZodBoolean>;
|
|
155
157
|
}, z.core.$strip>>;
|
|
156
158
|
}, z.core.$strip>;
|
|
157
|
-
|
|
159
|
+
/** Twin of the schema above — `offers` makes this a recursive node (see recursiveTwins.test.ts). */
|
|
160
|
+
export type CABMonetizeSection = CABVariableFields & {
|
|
161
|
+
name?: string;
|
|
162
|
+
offers?: CABSection[];
|
|
163
|
+
privacyPolicyUrl?: string;
|
|
164
|
+
sectionId: string;
|
|
165
|
+
sectionType: 'monetize';
|
|
166
|
+
subVertical?: string;
|
|
167
|
+
vertical?: string;
|
|
168
|
+
};
|
|
158
169
|
export type CABMonetizeSectionProps = CABMonetizeSection & SectionDetails;
|
package/dist/server/index.cjs
CHANGED
|
@@ -1478,7 +1478,13 @@ var import_zod20 = require("zod");
|
|
|
1478
1478
|
var CABMonetizeSection = import_zod20.z.object({
|
|
1479
1479
|
...CABVariableFields,
|
|
1480
1480
|
name: import_zod20.z.string().optional(),
|
|
1481
|
-
/**
|
|
1481
|
+
/**
|
|
1482
|
+
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
1483
|
+
* context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
|
|
1484
|
+
* nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
|
|
1485
|
+
*/
|
|
1486
|
+
offers: import_zod20.z.array(import_zod20.z.lazy(() => CABSection)).optional(),
|
|
1487
|
+
/** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
|
|
1482
1488
|
privacyPolicyUrl: import_zod20.z.string().optional(),
|
|
1483
1489
|
sectionId: import_zod20.z.uuid().default(() => (0, import_uuid8.v7)()),
|
|
1484
1490
|
sectionType: import_zod20.z.literal(SectionType.monetize).default(SectionType.monetize),
|
|
@@ -3843,184 +3849,6 @@ var fetchDataSourceResults = async (input, ctx) => {
|
|
|
3843
3849
|
};
|
|
3844
3850
|
};
|
|
3845
3851
|
|
|
3846
|
-
// src/server/salvageSections.ts
|
|
3847
|
-
var import_uuid14 = require("uuid");
|
|
3848
|
-
var STUB_SECTION_TYPE = "unknown";
|
|
3849
|
-
var knownSectionTypes = new Set(sectionTypes);
|
|
3850
|
-
var hasSectionsArray = (raw) => typeof raw === "object" && raw !== null && Array.isArray(raw.sections);
|
|
3851
|
-
var stubSection = (raw, report) => {
|
|
3852
|
-
const record = typeof raw === "object" && raw !== null ? raw : {};
|
|
3853
|
-
const original = record.sectionType;
|
|
3854
|
-
const rawId = record.sectionId;
|
|
3855
|
-
report?.({
|
|
3856
|
-
detail: `invalid section (${typeof original === "string" ? original : "no sectionType"})`,
|
|
3857
|
-
kind: "section"
|
|
3858
|
-
});
|
|
3859
|
-
return {
|
|
3860
|
-
sectionId: typeof rawId === "string" && rawId ? rawId : (0, import_uuid14.v7)(),
|
|
3861
|
-
sectionType: typeof original === "string" && !knownSectionTypes.has(original) ? original : STUB_SECTION_TYPE
|
|
3862
|
-
};
|
|
3863
|
-
};
|
|
3864
|
-
var salvageSection = (raw, report) => {
|
|
3865
|
-
const parsed = CABSection.safeParse(raw);
|
|
3866
|
-
if (parsed.success) return parsed.data;
|
|
3867
|
-
if (hasSectionsArray(raw)) {
|
|
3868
|
-
const shell = CABSection.safeParse({ ...raw, sections: [] });
|
|
3869
|
-
if (shell.success)
|
|
3870
|
-
return { ...shell.data, sections: raw.sections.map((child) => salvageSection(child, report)) };
|
|
3871
|
-
}
|
|
3872
|
-
return stubSection(raw, report);
|
|
3873
|
-
};
|
|
3874
|
-
var salvageRootSection = (raw, report) => {
|
|
3875
|
-
const shell = CABRootSection.safeParse(hasSectionsArray(raw) ? { ...raw, sections: [] } : raw);
|
|
3876
|
-
if (!shell.success) return CABRootSection.parse(raw);
|
|
3877
|
-
const sections = hasSectionsArray(raw) ? raw.sections.map((child) => salvageSection(child, report)) : [];
|
|
3878
|
-
return { ...shell.data, sections };
|
|
3879
|
-
};
|
|
3880
|
-
|
|
3881
|
-
// src/server/widgetSettings.ts
|
|
3882
|
-
var isMissingUpstreamPayload = isEmptyObject;
|
|
3883
|
-
var dispatchWidgetTransform = (id, normalized, report) => {
|
|
3884
|
-
if (isCABRootSection(normalized)) return salvageRootSection(normalizeTextContent(normalized), report);
|
|
3885
|
-
if (isContentBlockV1(normalized)) return convertContentBlockToV2({ id, settings: normalized });
|
|
3886
|
-
if (isLineItemEditorV1(normalized)) return convertLineItemEditorToV2({ id, settings: normalized });
|
|
3887
|
-
if (isMonetizeV1(normalized)) return convertMonetizeToV2({ id, settings: normalized });
|
|
3888
|
-
if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
|
|
3889
|
-
return normalized;
|
|
3890
|
-
};
|
|
3891
|
-
var fetchProgressBarContext = async (shop, ctx) => {
|
|
3892
|
-
const config = await fetchUserConfig({ shop }, ctx).catch(() => void 0);
|
|
3893
|
-
if (!config) return {};
|
|
3894
|
-
if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
|
|
3895
|
-
ctx.reportDrift?.({
|
|
3896
|
-
detail: "shop config active package entry inerted (drifted shape) \u2014 smart-cart entitlement may misgate",
|
|
3897
|
-
kind: "shopConfig"
|
|
3898
|
-
});
|
|
3899
|
-
}
|
|
3900
|
-
const entitled = config.shop.billingVersion !== "v6" || config.shop.activePackages.some((pkg) => pkg.shortName === "cart-merchandising" && pkg.isActive);
|
|
3901
|
-
return {
|
|
3902
|
-
bars: entitled ? extractSmartCartBars(config.smartCart) : [],
|
|
3903
|
-
progressColor: await fetchThemeColor(config.shop.shopId, ctx)
|
|
3904
|
-
};
|
|
3905
|
-
};
|
|
3906
|
-
var fetchThemeColor = async (shopId, ctx) => {
|
|
3907
|
-
if (!shopId) return void 0;
|
|
3908
|
-
try {
|
|
3909
|
-
const theme = await ctx.fetchUpstream(`/admin/api/v1/shop/theme/${shopId}`, {}, { host: ctx.host });
|
|
3910
|
-
const color = theme.theme?.settings?.buttonBackground;
|
|
3911
|
-
return typeof color === "string" && color ? color : void 0;
|
|
3912
|
-
} catch {
|
|
3913
|
-
return void 0;
|
|
3914
|
-
}
|
|
3915
|
-
};
|
|
3916
|
-
var colorlessProgressBars = (section) => {
|
|
3917
|
-
const own = section.sectionType === SectionType.progressBar && section.progressColor === void 0;
|
|
3918
|
-
const nested = (section.sections ?? []).flatMap((child) => colorlessProgressBars(child));
|
|
3919
|
-
return own ? [section, ...nested] : nested;
|
|
3920
|
-
};
|
|
3921
|
-
var fetchAndConvert = async (id, input, ctx) => {
|
|
3922
|
-
const params = { id: String(id), shop: input.shop };
|
|
3923
|
-
if (input.cacheKey != null) params.cache_key = String(input.cacheKey);
|
|
3924
|
-
if (input.key) params.key = input.key;
|
|
3925
|
-
let upstream;
|
|
3926
|
-
try {
|
|
3927
|
-
upstream = await ctx.fetchUpstream("/api/v1/widgets/settings", params, { host: ctx.host });
|
|
3928
|
-
} catch (err) {
|
|
3929
|
-
if (err instanceof UpstreamError && [400, 404, 410].includes(err.status)) {
|
|
3930
|
-
throw new NotFoundError(`Widget not found: ${id}`);
|
|
3931
|
-
}
|
|
3932
|
-
throw err;
|
|
3933
|
-
}
|
|
3934
|
-
const data = unwrapData(upstream);
|
|
3935
|
-
if (isMissingUpstreamPayload(data)) throw new NotFoundError(`Widget not found: ${id}`);
|
|
3936
|
-
const normalized = convertNumericObjects(deepCamelCase(data));
|
|
3937
|
-
if (isProgressBarV1(normalized) && normalized.barSettings.barType === "smart-cart") {
|
|
3938
|
-
const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
3939
|
-
return convertSmartCartProgressBarToV2({ bars: bars ?? [], id, progressColor, settings: normalized });
|
|
3940
|
-
}
|
|
3941
|
-
if (isConvertibleProgressBarV1(normalized)) {
|
|
3942
|
-
const { progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
3943
|
-
return convertProgressBarToV2({ id, progressColor, settings: normalized });
|
|
3944
|
-
}
|
|
3945
|
-
const converted = dispatchWidgetTransform(id, normalized, ctx.reportDrift);
|
|
3946
|
-
const colorless = isCABRootSection(normalized) ? colorlessProgressBars(converted) : [];
|
|
3947
|
-
if (colorless.length > 0) {
|
|
3948
|
-
const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx);
|
|
3949
|
-
if (color) for (const bar of colorless) bar.progressColor = color;
|
|
3950
|
-
}
|
|
3951
|
-
return converted;
|
|
3952
|
-
};
|
|
3953
|
-
var resolveWidgetExperiment = async (input, ctx) => {
|
|
3954
|
-
if (!input.visitorId) return void 0;
|
|
3955
|
-
try {
|
|
3956
|
-
const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
|
|
3957
|
-
return resolveExperiment(shop.activeExperiments, input.id, input.visitorId);
|
|
3958
|
-
} catch {
|
|
3959
|
-
return void 0;
|
|
3960
|
-
}
|
|
3961
|
-
};
|
|
3962
|
-
var isCABTree = (result) => result.version === 2;
|
|
3963
|
-
var convertWidgetSettings = async (input, ctx) => {
|
|
3964
|
-
const experiment = await resolveWidgetExperiment(input, ctx);
|
|
3965
|
-
const result = await fetchAndConvert(experiment?.elementId ?? input.id, input, ctx);
|
|
3966
|
-
return experiment && isCABTree(result) ? { ...result, experiment } : result;
|
|
3967
|
-
};
|
|
3968
|
-
|
|
3969
|
-
// src/server/giftValidation.ts
|
|
3970
|
-
var resolveDataSourcePath = async (widgetId, shop, ctx) => {
|
|
3971
|
-
const raw = await ctx.fetchUpstream("/api/v1/widgets/settings", { id: String(widgetId), shop }, { host: ctx.host });
|
|
3972
|
-
const data = raw && typeof raw === "object" && "data" in raw ? raw.data : raw;
|
|
3973
|
-
const endpoint = deepCamelCase(data)?.endpoint;
|
|
3974
|
-
return typeof endpoint === "string" && endpoint ? endpoint : void 0;
|
|
3975
|
-
};
|
|
3976
|
-
var groupByWidget = (gifts) => {
|
|
3977
|
-
const byWidget = /* @__PURE__ */ new Map();
|
|
3978
|
-
for (const gift of gifts) {
|
|
3979
|
-
byWidget.set(gift.widgetId, [...byWidget.get(gift.widgetId) ?? [], gift]);
|
|
3980
|
-
}
|
|
3981
|
-
return byWidget;
|
|
3982
|
-
};
|
|
3983
|
-
var validateGifts = async (input, ctx) => {
|
|
3984
|
-
const perWidget = await Promise.all(
|
|
3985
|
-
[...groupByWidget(input.gifts)].map(async ([widgetId, gifts]) => {
|
|
3986
|
-
const discountLost = gifts.filter(hasLostDiscount).map((gift) => gift.lineId);
|
|
3987
|
-
try {
|
|
3988
|
-
const dataSourcePath = await resolveDataSourcePath(widgetId, input.shop, ctx);
|
|
3989
|
-
if (!dataSourcePath) return discountLost;
|
|
3990
|
-
const { metadata } = await fetchDataSourceResults(
|
|
3991
|
-
{
|
|
3992
|
-
cart: input.cart,
|
|
3993
|
-
/** Market context threads through so a market-gated gift rule answers for the buyer's market — as engine params, inheriting the shop's markets-toggle gate (REB-21688). */
|
|
3994
|
-
country: input.country,
|
|
3995
|
-
customerId: input.customerId,
|
|
3996
|
-
dataSourcePath,
|
|
3997
|
-
/** The gift being validated is in the cart — input filtering would erase it from the answer. */
|
|
3998
|
-
filterInputs: "no",
|
|
3999
|
-
language: input.language,
|
|
4000
|
-
/**
|
|
4001
|
-
* `limit: 0` fetches NO products — only the rule verdict is needed. Safe because the engine
|
|
4002
|
-
* derives `matchedRules` from rule-config evaluation, not from fetched results (traced through
|
|
4003
|
-
* `api.php`), so the metadata is complete at limit 0. Don't raise it to "fix" a non-bug.
|
|
4004
|
-
*/
|
|
4005
|
-
limit: 0,
|
|
4006
|
-
shop: input.shop,
|
|
4007
|
-
visitorId: input.visitorId
|
|
4008
|
-
},
|
|
4009
|
-
ctx
|
|
4010
|
-
);
|
|
4011
|
-
const meta = metadata ?? void 0;
|
|
4012
|
-
if (!isDefinitiveRuleAnswer(meta) || hasUnsupportedRuleTypes(meta)) return discountLost;
|
|
4013
|
-
const validIds = getValidGwpProductIds(meta.matchedRules);
|
|
4014
|
-
if (validIds.size === 0 && !isConfirmedRuleMiss(meta)) return discountLost;
|
|
4015
|
-
return gifts.filter((gift) => shouldRemoveGift(gift, validIds)).map((gift) => gift.lineId);
|
|
4016
|
-
} catch {
|
|
4017
|
-
return discountLost;
|
|
4018
|
-
}
|
|
4019
|
-
})
|
|
4020
|
-
);
|
|
4021
|
-
return { remove: perWidget.flat() };
|
|
4022
|
-
};
|
|
4023
|
-
|
|
4024
3852
|
// src/server/adsEngine.ts
|
|
4025
3853
|
var import_zod28 = require("zod");
|
|
4026
3854
|
var SUPPLY_KEY = { production: "AQF5RKLR", staging: "Z56LQ7PS" };
|
|
@@ -4067,6 +3895,7 @@ var buildAdsParams = (input, publisherKey, supplyKey, testing, userAgent, ipAddr
|
|
|
4067
3895
|
testing: String(testing),
|
|
4068
3896
|
transaction_total: String(input.transactionTotal ?? input.cart.subtotal),
|
|
4069
3897
|
...items.length > 0 && { transaction_items: JSON.stringify(items) },
|
|
3898
|
+
...input.clientRequestId && { client_request_id: input.clientRequestId },
|
|
4070
3899
|
...input.country && { country_code: input.country },
|
|
4071
3900
|
...input.currency && { currency_code: input.currency },
|
|
4072
3901
|
...input.customerEmail && { customer_email: input.customerEmail },
|
|
@@ -4250,6 +4079,216 @@ var fetchMonetizeOffers = async (input, ctx) => {
|
|
|
4250
4079
|
return { offers };
|
|
4251
4080
|
};
|
|
4252
4081
|
|
|
4082
|
+
// src/server/salvageSections.ts
|
|
4083
|
+
var import_uuid14 = require("uuid");
|
|
4084
|
+
var STUB_SECTION_TYPE = "unknown";
|
|
4085
|
+
var knownSectionTypes = new Set(sectionTypes);
|
|
4086
|
+
var hasSectionsArray = (raw) => typeof raw === "object" && raw !== null && Array.isArray(raw.sections);
|
|
4087
|
+
var stubSection = (raw, report) => {
|
|
4088
|
+
const record = typeof raw === "object" && raw !== null ? raw : {};
|
|
4089
|
+
const original = record.sectionType;
|
|
4090
|
+
const rawId = record.sectionId;
|
|
4091
|
+
report?.({
|
|
4092
|
+
detail: `invalid section (${typeof original === "string" ? original : "no sectionType"})`,
|
|
4093
|
+
kind: "section"
|
|
4094
|
+
});
|
|
4095
|
+
return {
|
|
4096
|
+
sectionId: typeof rawId === "string" && rawId ? rawId : (0, import_uuid14.v7)(),
|
|
4097
|
+
sectionType: typeof original === "string" && !knownSectionTypes.has(original) ? original : STUB_SECTION_TYPE
|
|
4098
|
+
};
|
|
4099
|
+
};
|
|
4100
|
+
var salvageSection = (raw, report) => {
|
|
4101
|
+
const parsed = CABSection.safeParse(raw);
|
|
4102
|
+
if (parsed.success) return parsed.data;
|
|
4103
|
+
if (hasSectionsArray(raw)) {
|
|
4104
|
+
const shell = CABSection.safeParse({ ...raw, sections: [] });
|
|
4105
|
+
if (shell.success)
|
|
4106
|
+
return { ...shell.data, sections: raw.sections.map((child) => salvageSection(child, report)) };
|
|
4107
|
+
}
|
|
4108
|
+
return stubSection(raw, report);
|
|
4109
|
+
};
|
|
4110
|
+
var salvageRootSection = (raw, report) => {
|
|
4111
|
+
const shell = CABRootSection.safeParse(hasSectionsArray(raw) ? { ...raw, sections: [] } : raw);
|
|
4112
|
+
if (!shell.success) return CABRootSection.parse(raw);
|
|
4113
|
+
const sections = hasSectionsArray(raw) ? raw.sections.map((child) => salvageSection(child, report)) : [];
|
|
4114
|
+
return { ...shell.data, sections };
|
|
4115
|
+
};
|
|
4116
|
+
|
|
4117
|
+
// src/server/widgetSettings.ts
|
|
4118
|
+
var isMissingUpstreamPayload = isEmptyObject;
|
|
4119
|
+
var dispatchWidgetTransform = (id, normalized, report) => {
|
|
4120
|
+
if (isCABRootSection(normalized)) return salvageRootSection(normalizeTextContent(normalized), report);
|
|
4121
|
+
if (isContentBlockV1(normalized)) return convertContentBlockToV2({ id, settings: normalized });
|
|
4122
|
+
if (isLineItemEditorV1(normalized)) return convertLineItemEditorToV2({ id, settings: normalized });
|
|
4123
|
+
if (isMonetizeV1(normalized)) return convertMonetizeToV2({ id, settings: normalized });
|
|
4124
|
+
if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
|
|
4125
|
+
return normalized;
|
|
4126
|
+
};
|
|
4127
|
+
var fetchProgressBarContext = async (shop, ctx) => {
|
|
4128
|
+
const config = await fetchUserConfig({ shop }, ctx).catch(() => void 0);
|
|
4129
|
+
if (!config) return {};
|
|
4130
|
+
if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
|
|
4131
|
+
ctx.reportDrift?.({
|
|
4132
|
+
detail: "shop config active package entry inerted (drifted shape) \u2014 smart-cart entitlement may misgate",
|
|
4133
|
+
kind: "shopConfig"
|
|
4134
|
+
});
|
|
4135
|
+
}
|
|
4136
|
+
const entitled = config.shop.billingVersion !== "v6" || config.shop.activePackages.some((pkg) => pkg.shortName === "cart-merchandising" && pkg.isActive);
|
|
4137
|
+
return {
|
|
4138
|
+
bars: entitled ? extractSmartCartBars(config.smartCart) : [],
|
|
4139
|
+
progressColor: await fetchThemeColor(config.shop.shopId, ctx)
|
|
4140
|
+
};
|
|
4141
|
+
};
|
|
4142
|
+
var fetchThemeColor = async (shopId, ctx) => {
|
|
4143
|
+
if (!shopId) return void 0;
|
|
4144
|
+
try {
|
|
4145
|
+
const theme = await ctx.fetchUpstream(`/admin/api/v1/shop/theme/${shopId}`, {}, { host: ctx.host });
|
|
4146
|
+
const color = theme.theme?.settings?.buttonBackground;
|
|
4147
|
+
return typeof color === "string" && color ? color : void 0;
|
|
4148
|
+
} catch {
|
|
4149
|
+
return void 0;
|
|
4150
|
+
}
|
|
4151
|
+
};
|
|
4152
|
+
var colorlessProgressBars = (section) => {
|
|
4153
|
+
const own = section.sectionType === SectionType.progressBar && section.progressColor === void 0;
|
|
4154
|
+
const nested = (section.sections ?? []).flatMap((child) => colorlessProgressBars(child));
|
|
4155
|
+
return own ? [section, ...nested] : nested;
|
|
4156
|
+
};
|
|
4157
|
+
var isCABTree = (result) => result.version === 2;
|
|
4158
|
+
var findMonetizeMarker = (section) => {
|
|
4159
|
+
if (section.sectionType === SectionType.monetize && section.offers === void 0) return section;
|
|
4160
|
+
if (!Array.isArray(section.sections)) return void 0;
|
|
4161
|
+
for (const child of section.sections) {
|
|
4162
|
+
const found = findMonetizeMarker(child);
|
|
4163
|
+
if (found) return found;
|
|
4164
|
+
}
|
|
4165
|
+
return void 0;
|
|
4166
|
+
};
|
|
4167
|
+
var hydrateMonetizeMarkers = async (converted, input, ctx) => {
|
|
4168
|
+
if (!input.monetize || !isCABTree(converted)) return;
|
|
4169
|
+
const first = findMonetizeMarker(converted);
|
|
4170
|
+
if (!first) return;
|
|
4171
|
+
try {
|
|
4172
|
+
const { offers } = await fetchMonetizeOffers(
|
|
4173
|
+
{
|
|
4174
|
+
...input.monetize,
|
|
4175
|
+
privacyPolicyUrl: first.privacyPolicyUrl,
|
|
4176
|
+
shop: input.shop,
|
|
4177
|
+
visitorId: input.monetize.visitorId || input.visitorId
|
|
4178
|
+
},
|
|
4179
|
+
ctx
|
|
4180
|
+
);
|
|
4181
|
+
first.offers = offers;
|
|
4182
|
+
} catch (err) {
|
|
4183
|
+
ctx.reportDrift?.({
|
|
4184
|
+
detail: `inline hydration failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
4185
|
+
kind: "monetizeOffer"
|
|
4186
|
+
});
|
|
4187
|
+
}
|
|
4188
|
+
};
|
|
4189
|
+
var fetchAndConvert = async (id, input, ctx) => {
|
|
4190
|
+
const params = { id: String(id), shop: input.shop };
|
|
4191
|
+
if (input.cacheKey != null) params.cache_key = String(input.cacheKey);
|
|
4192
|
+
if (input.key) params.key = input.key;
|
|
4193
|
+
let upstream;
|
|
4194
|
+
try {
|
|
4195
|
+
upstream = await ctx.fetchUpstream("/api/v1/widgets/settings", params, { host: ctx.host });
|
|
4196
|
+
} catch (err) {
|
|
4197
|
+
if (err instanceof UpstreamError && [400, 404, 410].includes(err.status)) {
|
|
4198
|
+
throw new NotFoundError(`Widget not found: ${id}`);
|
|
4199
|
+
}
|
|
4200
|
+
throw err;
|
|
4201
|
+
}
|
|
4202
|
+
const data = unwrapData(upstream);
|
|
4203
|
+
if (isMissingUpstreamPayload(data)) throw new NotFoundError(`Widget not found: ${id}`);
|
|
4204
|
+
const normalized = convertNumericObjects(deepCamelCase(data));
|
|
4205
|
+
if (isProgressBarV1(normalized) && normalized.barSettings.barType === "smart-cart") {
|
|
4206
|
+
const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
4207
|
+
return convertSmartCartProgressBarToV2({ bars: bars ?? [], id, progressColor, settings: normalized });
|
|
4208
|
+
}
|
|
4209
|
+
if (isConvertibleProgressBarV1(normalized)) {
|
|
4210
|
+
const { progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
4211
|
+
return convertProgressBarToV2({ id, progressColor, settings: normalized });
|
|
4212
|
+
}
|
|
4213
|
+
const converted = dispatchWidgetTransform(id, normalized, ctx.reportDrift);
|
|
4214
|
+
const colorless = isCABRootSection(normalized) ? colorlessProgressBars(converted) : [];
|
|
4215
|
+
if (colorless.length > 0) {
|
|
4216
|
+
const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx);
|
|
4217
|
+
if (color) for (const bar of colorless) bar.progressColor = color;
|
|
4218
|
+
}
|
|
4219
|
+
return converted;
|
|
4220
|
+
};
|
|
4221
|
+
var resolveWidgetExperiment = async (input, ctx) => {
|
|
4222
|
+
if (!input.visitorId) return void 0;
|
|
4223
|
+
try {
|
|
4224
|
+
const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
|
|
4225
|
+
return resolveExperiment(shop.activeExperiments, input.id, input.visitorId);
|
|
4226
|
+
} catch {
|
|
4227
|
+
return void 0;
|
|
4228
|
+
}
|
|
4229
|
+
};
|
|
4230
|
+
var convertWidgetSettings = async (input, ctx) => {
|
|
4231
|
+
const experiment = await resolveWidgetExperiment(input, ctx);
|
|
4232
|
+
const result = await fetchAndConvert(experiment?.elementId ?? input.id, input, ctx);
|
|
4233
|
+
await hydrateMonetizeMarkers(result, input, ctx);
|
|
4234
|
+
return experiment && isCABTree(result) ? { ...result, experiment } : result;
|
|
4235
|
+
};
|
|
4236
|
+
|
|
4237
|
+
// src/server/giftValidation.ts
|
|
4238
|
+
var resolveDataSourcePath = async (widgetId, shop, ctx) => {
|
|
4239
|
+
const raw = await ctx.fetchUpstream("/api/v1/widgets/settings", { id: String(widgetId), shop }, { host: ctx.host });
|
|
4240
|
+
const data = raw && typeof raw === "object" && "data" in raw ? raw.data : raw;
|
|
4241
|
+
const endpoint = deepCamelCase(data)?.endpoint;
|
|
4242
|
+
return typeof endpoint === "string" && endpoint ? endpoint : void 0;
|
|
4243
|
+
};
|
|
4244
|
+
var groupByWidget = (gifts) => {
|
|
4245
|
+
const byWidget = /* @__PURE__ */ new Map();
|
|
4246
|
+
for (const gift of gifts) {
|
|
4247
|
+
byWidget.set(gift.widgetId, [...byWidget.get(gift.widgetId) ?? [], gift]);
|
|
4248
|
+
}
|
|
4249
|
+
return byWidget;
|
|
4250
|
+
};
|
|
4251
|
+
var validateGifts = async (input, ctx) => {
|
|
4252
|
+
const perWidget = await Promise.all(
|
|
4253
|
+
[...groupByWidget(input.gifts)].map(async ([widgetId, gifts]) => {
|
|
4254
|
+
const discountLost = gifts.filter(hasLostDiscount).map((gift) => gift.lineId);
|
|
4255
|
+
try {
|
|
4256
|
+
const dataSourcePath = await resolveDataSourcePath(widgetId, input.shop, ctx);
|
|
4257
|
+
if (!dataSourcePath) return discountLost;
|
|
4258
|
+
const { metadata } = await fetchDataSourceResults(
|
|
4259
|
+
{
|
|
4260
|
+
cart: input.cart,
|
|
4261
|
+
/** Market context threads through so a market-gated gift rule answers for the buyer's market — as engine params, inheriting the shop's markets-toggle gate (REB-21688). */
|
|
4262
|
+
country: input.country,
|
|
4263
|
+
customerId: input.customerId,
|
|
4264
|
+
dataSourcePath,
|
|
4265
|
+
/** The gift being validated is in the cart — input filtering would erase it from the answer. */
|
|
4266
|
+
filterInputs: "no",
|
|
4267
|
+
language: input.language,
|
|
4268
|
+
/**
|
|
4269
|
+
* `limit: 0` fetches NO products — only the rule verdict is needed. Safe because the engine
|
|
4270
|
+
* derives `matchedRules` from rule-config evaluation, not from fetched results (traced through
|
|
4271
|
+
* `api.php`), so the metadata is complete at limit 0. Don't raise it to "fix" a non-bug.
|
|
4272
|
+
*/
|
|
4273
|
+
limit: 0,
|
|
4274
|
+
shop: input.shop,
|
|
4275
|
+
visitorId: input.visitorId
|
|
4276
|
+
},
|
|
4277
|
+
ctx
|
|
4278
|
+
);
|
|
4279
|
+
const meta = metadata ?? void 0;
|
|
4280
|
+
if (!isDefinitiveRuleAnswer(meta) || hasUnsupportedRuleTypes(meta)) return discountLost;
|
|
4281
|
+
const validIds = getValidGwpProductIds(meta.matchedRules);
|
|
4282
|
+
if (validIds.size === 0 && !isConfirmedRuleMiss(meta)) return discountLost;
|
|
4283
|
+
return gifts.filter((gift) => shouldRemoveGift(gift, validIds)).map((gift) => gift.lineId);
|
|
4284
|
+
} catch {
|
|
4285
|
+
return discountLost;
|
|
4286
|
+
}
|
|
4287
|
+
})
|
|
4288
|
+
);
|
|
4289
|
+
return { remove: perWidget.flat() };
|
|
4290
|
+
};
|
|
4291
|
+
|
|
4253
4292
|
// src/server/analytics.ts
|
|
4254
4293
|
var import_es_toolkit7 = require("es-toolkit");
|
|
4255
4294
|
var EXPERIMENT_TYPE_MAP = {
|
|
@@ -4421,14 +4460,6 @@ var CartInputSchema = import_zod29.z.object({
|
|
|
4421
4460
|
/** Presentment subtotal in cents. */
|
|
4422
4461
|
subtotal: import_zod29.z.number()
|
|
4423
4462
|
});
|
|
4424
|
-
var WidgetSettingsInputSchema = import_zod29.z.object({
|
|
4425
|
-
cacheKey: import_zod29.z.number().optional(),
|
|
4426
|
-
id: import_zod29.z.number(),
|
|
4427
|
-
key: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4428
|
-
shop: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX),
|
|
4429
|
-
/** Stable per-buyer id; when present, enables sticky server-side A/B variant selection. */
|
|
4430
|
-
visitorId: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
|
|
4431
|
-
});
|
|
4432
4463
|
var UserConfigInputSchema = import_zod29.z.object({ shop: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX) });
|
|
4433
4464
|
var DataSourceInputSchema = import_zod29.z.object({
|
|
4434
4465
|
cart: CartInputSchema,
|
|
@@ -4511,6 +4542,8 @@ var MonetizeTransactionItemSchema = import_zod29.z.object({
|
|
|
4511
4542
|
});
|
|
4512
4543
|
var MonetizeInputSchema = import_zod29.z.object({
|
|
4513
4544
|
cart: CartInputSchema,
|
|
4545
|
+
/** Client-minted render-beacon correlation id; forwarded to the ads engine as `client_request_id`. */
|
|
4546
|
+
clientRequestId: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4514
4547
|
country: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4515
4548
|
currency: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4516
4549
|
/** Ads-targeting identity/geo/locale fields (React parity); folded into the engine query verbatim. */
|
|
@@ -4540,6 +4573,22 @@ var MonetizeInputSchema = import_zod29.z.object({
|
|
|
4540
4573
|
visitorId: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4541
4574
|
zipCode: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
|
|
4542
4575
|
});
|
|
4576
|
+
var WidgetSettingsInputSchema = import_zod29.z.object({
|
|
4577
|
+
cacheKey: import_zod29.z.number().optional(),
|
|
4578
|
+
id: import_zod29.z.number(),
|
|
4579
|
+
key: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4580
|
+
/**
|
|
4581
|
+
* Monetize context for inline hydration: when present AND the widget converts to a Monetize marker,
|
|
4582
|
+
* the server fetches/composes the offer cards inside this same request (no second `/cab/monetize`
|
|
4583
|
+
* round trip). Presence doubles as the capability signal — an absent block keeps the legacy marker
|
|
4584
|
+
* flow, so old/new client×server pairs need no lockstep deploy. `shop` rides the outer input;
|
|
4585
|
+
* `privacyPolicyUrl` comes from the widget settings server-side (more authoritative than a relay).
|
|
4586
|
+
*/
|
|
4587
|
+
monetize: MonetizeInputSchema.omit({ privacyPolicyUrl: true, shop: true }).optional(),
|
|
4588
|
+
shop: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX),
|
|
4589
|
+
/** Stable per-buyer id; when present, enables sticky server-side A/B variant selection. */
|
|
4590
|
+
visitorId: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
|
|
4591
|
+
});
|
|
4543
4592
|
var AnalyticsEventInputSchema = import_zod29.z.object({
|
|
4544
4593
|
name: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_KEY_MAX),
|
|
4545
4594
|
properties: import_zod29.z.record(import_zod29.z.string().max(REQUEST_BOUNDS.STRING_KEY_MAX), import_zod29.z.unknown()).optional(),
|
package/dist/server/index.mjs
CHANGED
|
@@ -1430,7 +1430,13 @@ import { z as z20 } from "zod";
|
|
|
1430
1430
|
var CABMonetizeSection = z20.object({
|
|
1431
1431
|
...CABVariableFields,
|
|
1432
1432
|
name: z20.string().optional(),
|
|
1433
|
-
/**
|
|
1433
|
+
/**
|
|
1434
|
+
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
1435
|
+
* context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
|
|
1436
|
+
* nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
|
|
1437
|
+
*/
|
|
1438
|
+
offers: z20.array(z20.lazy(() => CABSection)).optional(),
|
|
1439
|
+
/** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
|
|
1434
1440
|
privacyPolicyUrl: z20.string().optional(),
|
|
1435
1441
|
sectionId: z20.uuid().default(() => uuidv78()),
|
|
1436
1442
|
sectionType: z20.literal(SectionType.monetize).default(SectionType.monetize),
|
|
@@ -3795,184 +3801,6 @@ var fetchDataSourceResults = async (input, ctx) => {
|
|
|
3795
3801
|
};
|
|
3796
3802
|
};
|
|
3797
3803
|
|
|
3798
|
-
// src/server/salvageSections.ts
|
|
3799
|
-
import { v7 as uuidv713 } from "uuid";
|
|
3800
|
-
var STUB_SECTION_TYPE = "unknown";
|
|
3801
|
-
var knownSectionTypes = new Set(sectionTypes);
|
|
3802
|
-
var hasSectionsArray = (raw) => typeof raw === "object" && raw !== null && Array.isArray(raw.sections);
|
|
3803
|
-
var stubSection = (raw, report) => {
|
|
3804
|
-
const record = typeof raw === "object" && raw !== null ? raw : {};
|
|
3805
|
-
const original = record.sectionType;
|
|
3806
|
-
const rawId = record.sectionId;
|
|
3807
|
-
report?.({
|
|
3808
|
-
detail: `invalid section (${typeof original === "string" ? original : "no sectionType"})`,
|
|
3809
|
-
kind: "section"
|
|
3810
|
-
});
|
|
3811
|
-
return {
|
|
3812
|
-
sectionId: typeof rawId === "string" && rawId ? rawId : uuidv713(),
|
|
3813
|
-
sectionType: typeof original === "string" && !knownSectionTypes.has(original) ? original : STUB_SECTION_TYPE
|
|
3814
|
-
};
|
|
3815
|
-
};
|
|
3816
|
-
var salvageSection = (raw, report) => {
|
|
3817
|
-
const parsed = CABSection.safeParse(raw);
|
|
3818
|
-
if (parsed.success) return parsed.data;
|
|
3819
|
-
if (hasSectionsArray(raw)) {
|
|
3820
|
-
const shell = CABSection.safeParse({ ...raw, sections: [] });
|
|
3821
|
-
if (shell.success)
|
|
3822
|
-
return { ...shell.data, sections: raw.sections.map((child) => salvageSection(child, report)) };
|
|
3823
|
-
}
|
|
3824
|
-
return stubSection(raw, report);
|
|
3825
|
-
};
|
|
3826
|
-
var salvageRootSection = (raw, report) => {
|
|
3827
|
-
const shell = CABRootSection.safeParse(hasSectionsArray(raw) ? { ...raw, sections: [] } : raw);
|
|
3828
|
-
if (!shell.success) return CABRootSection.parse(raw);
|
|
3829
|
-
const sections = hasSectionsArray(raw) ? raw.sections.map((child) => salvageSection(child, report)) : [];
|
|
3830
|
-
return { ...shell.data, sections };
|
|
3831
|
-
};
|
|
3832
|
-
|
|
3833
|
-
// src/server/widgetSettings.ts
|
|
3834
|
-
var isMissingUpstreamPayload = isEmptyObject;
|
|
3835
|
-
var dispatchWidgetTransform = (id, normalized, report) => {
|
|
3836
|
-
if (isCABRootSection(normalized)) return salvageRootSection(normalizeTextContent(normalized), report);
|
|
3837
|
-
if (isContentBlockV1(normalized)) return convertContentBlockToV2({ id, settings: normalized });
|
|
3838
|
-
if (isLineItemEditorV1(normalized)) return convertLineItemEditorToV2({ id, settings: normalized });
|
|
3839
|
-
if (isMonetizeV1(normalized)) return convertMonetizeToV2({ id, settings: normalized });
|
|
3840
|
-
if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
|
|
3841
|
-
return normalized;
|
|
3842
|
-
};
|
|
3843
|
-
var fetchProgressBarContext = async (shop, ctx) => {
|
|
3844
|
-
const config = await fetchUserConfig({ shop }, ctx).catch(() => void 0);
|
|
3845
|
-
if (!config) return {};
|
|
3846
|
-
if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
|
|
3847
|
-
ctx.reportDrift?.({
|
|
3848
|
-
detail: "shop config active package entry inerted (drifted shape) \u2014 smart-cart entitlement may misgate",
|
|
3849
|
-
kind: "shopConfig"
|
|
3850
|
-
});
|
|
3851
|
-
}
|
|
3852
|
-
const entitled = config.shop.billingVersion !== "v6" || config.shop.activePackages.some((pkg) => pkg.shortName === "cart-merchandising" && pkg.isActive);
|
|
3853
|
-
return {
|
|
3854
|
-
bars: entitled ? extractSmartCartBars(config.smartCart) : [],
|
|
3855
|
-
progressColor: await fetchThemeColor(config.shop.shopId, ctx)
|
|
3856
|
-
};
|
|
3857
|
-
};
|
|
3858
|
-
var fetchThemeColor = async (shopId, ctx) => {
|
|
3859
|
-
if (!shopId) return void 0;
|
|
3860
|
-
try {
|
|
3861
|
-
const theme = await ctx.fetchUpstream(`/admin/api/v1/shop/theme/${shopId}`, {}, { host: ctx.host });
|
|
3862
|
-
const color = theme.theme?.settings?.buttonBackground;
|
|
3863
|
-
return typeof color === "string" && color ? color : void 0;
|
|
3864
|
-
} catch {
|
|
3865
|
-
return void 0;
|
|
3866
|
-
}
|
|
3867
|
-
};
|
|
3868
|
-
var colorlessProgressBars = (section) => {
|
|
3869
|
-
const own = section.sectionType === SectionType.progressBar && section.progressColor === void 0;
|
|
3870
|
-
const nested = (section.sections ?? []).flatMap((child) => colorlessProgressBars(child));
|
|
3871
|
-
return own ? [section, ...nested] : nested;
|
|
3872
|
-
};
|
|
3873
|
-
var fetchAndConvert = async (id, input, ctx) => {
|
|
3874
|
-
const params = { id: String(id), shop: input.shop };
|
|
3875
|
-
if (input.cacheKey != null) params.cache_key = String(input.cacheKey);
|
|
3876
|
-
if (input.key) params.key = input.key;
|
|
3877
|
-
let upstream;
|
|
3878
|
-
try {
|
|
3879
|
-
upstream = await ctx.fetchUpstream("/api/v1/widgets/settings", params, { host: ctx.host });
|
|
3880
|
-
} catch (err) {
|
|
3881
|
-
if (err instanceof UpstreamError && [400, 404, 410].includes(err.status)) {
|
|
3882
|
-
throw new NotFoundError(`Widget not found: ${id}`);
|
|
3883
|
-
}
|
|
3884
|
-
throw err;
|
|
3885
|
-
}
|
|
3886
|
-
const data = unwrapData(upstream);
|
|
3887
|
-
if (isMissingUpstreamPayload(data)) throw new NotFoundError(`Widget not found: ${id}`);
|
|
3888
|
-
const normalized = convertNumericObjects(deepCamelCase(data));
|
|
3889
|
-
if (isProgressBarV1(normalized) && normalized.barSettings.barType === "smart-cart") {
|
|
3890
|
-
const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
3891
|
-
return convertSmartCartProgressBarToV2({ bars: bars ?? [], id, progressColor, settings: normalized });
|
|
3892
|
-
}
|
|
3893
|
-
if (isConvertibleProgressBarV1(normalized)) {
|
|
3894
|
-
const { progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
3895
|
-
return convertProgressBarToV2({ id, progressColor, settings: normalized });
|
|
3896
|
-
}
|
|
3897
|
-
const converted = dispatchWidgetTransform(id, normalized, ctx.reportDrift);
|
|
3898
|
-
const colorless = isCABRootSection(normalized) ? colorlessProgressBars(converted) : [];
|
|
3899
|
-
if (colorless.length > 0) {
|
|
3900
|
-
const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx);
|
|
3901
|
-
if (color) for (const bar of colorless) bar.progressColor = color;
|
|
3902
|
-
}
|
|
3903
|
-
return converted;
|
|
3904
|
-
};
|
|
3905
|
-
var resolveWidgetExperiment = async (input, ctx) => {
|
|
3906
|
-
if (!input.visitorId) return void 0;
|
|
3907
|
-
try {
|
|
3908
|
-
const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
|
|
3909
|
-
return resolveExperiment(shop.activeExperiments, input.id, input.visitorId);
|
|
3910
|
-
} catch {
|
|
3911
|
-
return void 0;
|
|
3912
|
-
}
|
|
3913
|
-
};
|
|
3914
|
-
var isCABTree = (result) => result.version === 2;
|
|
3915
|
-
var convertWidgetSettings = async (input, ctx) => {
|
|
3916
|
-
const experiment = await resolveWidgetExperiment(input, ctx);
|
|
3917
|
-
const result = await fetchAndConvert(experiment?.elementId ?? input.id, input, ctx);
|
|
3918
|
-
return experiment && isCABTree(result) ? { ...result, experiment } : result;
|
|
3919
|
-
};
|
|
3920
|
-
|
|
3921
|
-
// src/server/giftValidation.ts
|
|
3922
|
-
var resolveDataSourcePath = async (widgetId, shop, ctx) => {
|
|
3923
|
-
const raw = await ctx.fetchUpstream("/api/v1/widgets/settings", { id: String(widgetId), shop }, { host: ctx.host });
|
|
3924
|
-
const data = raw && typeof raw === "object" && "data" in raw ? raw.data : raw;
|
|
3925
|
-
const endpoint = deepCamelCase(data)?.endpoint;
|
|
3926
|
-
return typeof endpoint === "string" && endpoint ? endpoint : void 0;
|
|
3927
|
-
};
|
|
3928
|
-
var groupByWidget = (gifts) => {
|
|
3929
|
-
const byWidget = /* @__PURE__ */ new Map();
|
|
3930
|
-
for (const gift of gifts) {
|
|
3931
|
-
byWidget.set(gift.widgetId, [...byWidget.get(gift.widgetId) ?? [], gift]);
|
|
3932
|
-
}
|
|
3933
|
-
return byWidget;
|
|
3934
|
-
};
|
|
3935
|
-
var validateGifts = async (input, ctx) => {
|
|
3936
|
-
const perWidget = await Promise.all(
|
|
3937
|
-
[...groupByWidget(input.gifts)].map(async ([widgetId, gifts]) => {
|
|
3938
|
-
const discountLost = gifts.filter(hasLostDiscount).map((gift) => gift.lineId);
|
|
3939
|
-
try {
|
|
3940
|
-
const dataSourcePath = await resolveDataSourcePath(widgetId, input.shop, ctx);
|
|
3941
|
-
if (!dataSourcePath) return discountLost;
|
|
3942
|
-
const { metadata } = await fetchDataSourceResults(
|
|
3943
|
-
{
|
|
3944
|
-
cart: input.cart,
|
|
3945
|
-
/** Market context threads through so a market-gated gift rule answers for the buyer's market — as engine params, inheriting the shop's markets-toggle gate (REB-21688). */
|
|
3946
|
-
country: input.country,
|
|
3947
|
-
customerId: input.customerId,
|
|
3948
|
-
dataSourcePath,
|
|
3949
|
-
/** The gift being validated is in the cart — input filtering would erase it from the answer. */
|
|
3950
|
-
filterInputs: "no",
|
|
3951
|
-
language: input.language,
|
|
3952
|
-
/**
|
|
3953
|
-
* `limit: 0` fetches NO products — only the rule verdict is needed. Safe because the engine
|
|
3954
|
-
* derives `matchedRules` from rule-config evaluation, not from fetched results (traced through
|
|
3955
|
-
* `api.php`), so the metadata is complete at limit 0. Don't raise it to "fix" a non-bug.
|
|
3956
|
-
*/
|
|
3957
|
-
limit: 0,
|
|
3958
|
-
shop: input.shop,
|
|
3959
|
-
visitorId: input.visitorId
|
|
3960
|
-
},
|
|
3961
|
-
ctx
|
|
3962
|
-
);
|
|
3963
|
-
const meta = metadata ?? void 0;
|
|
3964
|
-
if (!isDefinitiveRuleAnswer(meta) || hasUnsupportedRuleTypes(meta)) return discountLost;
|
|
3965
|
-
const validIds = getValidGwpProductIds(meta.matchedRules);
|
|
3966
|
-
if (validIds.size === 0 && !isConfirmedRuleMiss(meta)) return discountLost;
|
|
3967
|
-
return gifts.filter((gift) => shouldRemoveGift(gift, validIds)).map((gift) => gift.lineId);
|
|
3968
|
-
} catch {
|
|
3969
|
-
return discountLost;
|
|
3970
|
-
}
|
|
3971
|
-
})
|
|
3972
|
-
);
|
|
3973
|
-
return { remove: perWidget.flat() };
|
|
3974
|
-
};
|
|
3975
|
-
|
|
3976
3804
|
// src/server/adsEngine.ts
|
|
3977
3805
|
import { z as z28 } from "zod";
|
|
3978
3806
|
var SUPPLY_KEY = { production: "AQF5RKLR", staging: "Z56LQ7PS" };
|
|
@@ -4019,6 +3847,7 @@ var buildAdsParams = (input, publisherKey, supplyKey, testing, userAgent, ipAddr
|
|
|
4019
3847
|
testing: String(testing),
|
|
4020
3848
|
transaction_total: String(input.transactionTotal ?? input.cart.subtotal),
|
|
4021
3849
|
...items.length > 0 && { transaction_items: JSON.stringify(items) },
|
|
3850
|
+
...input.clientRequestId && { client_request_id: input.clientRequestId },
|
|
4022
3851
|
...input.country && { country_code: input.country },
|
|
4023
3852
|
...input.currency && { currency_code: input.currency },
|
|
4024
3853
|
...input.customerEmail && { customer_email: input.customerEmail },
|
|
@@ -4202,6 +4031,216 @@ var fetchMonetizeOffers = async (input, ctx) => {
|
|
|
4202
4031
|
return { offers };
|
|
4203
4032
|
};
|
|
4204
4033
|
|
|
4034
|
+
// src/server/salvageSections.ts
|
|
4035
|
+
import { v7 as uuidv713 } from "uuid";
|
|
4036
|
+
var STUB_SECTION_TYPE = "unknown";
|
|
4037
|
+
var knownSectionTypes = new Set(sectionTypes);
|
|
4038
|
+
var hasSectionsArray = (raw) => typeof raw === "object" && raw !== null && Array.isArray(raw.sections);
|
|
4039
|
+
var stubSection = (raw, report) => {
|
|
4040
|
+
const record = typeof raw === "object" && raw !== null ? raw : {};
|
|
4041
|
+
const original = record.sectionType;
|
|
4042
|
+
const rawId = record.sectionId;
|
|
4043
|
+
report?.({
|
|
4044
|
+
detail: `invalid section (${typeof original === "string" ? original : "no sectionType"})`,
|
|
4045
|
+
kind: "section"
|
|
4046
|
+
});
|
|
4047
|
+
return {
|
|
4048
|
+
sectionId: typeof rawId === "string" && rawId ? rawId : uuidv713(),
|
|
4049
|
+
sectionType: typeof original === "string" && !knownSectionTypes.has(original) ? original : STUB_SECTION_TYPE
|
|
4050
|
+
};
|
|
4051
|
+
};
|
|
4052
|
+
var salvageSection = (raw, report) => {
|
|
4053
|
+
const parsed = CABSection.safeParse(raw);
|
|
4054
|
+
if (parsed.success) return parsed.data;
|
|
4055
|
+
if (hasSectionsArray(raw)) {
|
|
4056
|
+
const shell = CABSection.safeParse({ ...raw, sections: [] });
|
|
4057
|
+
if (shell.success)
|
|
4058
|
+
return { ...shell.data, sections: raw.sections.map((child) => salvageSection(child, report)) };
|
|
4059
|
+
}
|
|
4060
|
+
return stubSection(raw, report);
|
|
4061
|
+
};
|
|
4062
|
+
var salvageRootSection = (raw, report) => {
|
|
4063
|
+
const shell = CABRootSection.safeParse(hasSectionsArray(raw) ? { ...raw, sections: [] } : raw);
|
|
4064
|
+
if (!shell.success) return CABRootSection.parse(raw);
|
|
4065
|
+
const sections = hasSectionsArray(raw) ? raw.sections.map((child) => salvageSection(child, report)) : [];
|
|
4066
|
+
return { ...shell.data, sections };
|
|
4067
|
+
};
|
|
4068
|
+
|
|
4069
|
+
// src/server/widgetSettings.ts
|
|
4070
|
+
var isMissingUpstreamPayload = isEmptyObject;
|
|
4071
|
+
var dispatchWidgetTransform = (id, normalized, report) => {
|
|
4072
|
+
if (isCABRootSection(normalized)) return salvageRootSection(normalizeTextContent(normalized), report);
|
|
4073
|
+
if (isContentBlockV1(normalized)) return convertContentBlockToV2({ id, settings: normalized });
|
|
4074
|
+
if (isLineItemEditorV1(normalized)) return convertLineItemEditorToV2({ id, settings: normalized });
|
|
4075
|
+
if (isMonetizeV1(normalized)) return convertMonetizeToV2({ id, settings: normalized });
|
|
4076
|
+
if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
|
|
4077
|
+
return normalized;
|
|
4078
|
+
};
|
|
4079
|
+
var fetchProgressBarContext = async (shop, ctx) => {
|
|
4080
|
+
const config = await fetchUserConfig({ shop }, ctx).catch(() => void 0);
|
|
4081
|
+
if (!config) return {};
|
|
4082
|
+
if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
|
|
4083
|
+
ctx.reportDrift?.({
|
|
4084
|
+
detail: "shop config active package entry inerted (drifted shape) \u2014 smart-cart entitlement may misgate",
|
|
4085
|
+
kind: "shopConfig"
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
4088
|
+
const entitled = config.shop.billingVersion !== "v6" || config.shop.activePackages.some((pkg) => pkg.shortName === "cart-merchandising" && pkg.isActive);
|
|
4089
|
+
return {
|
|
4090
|
+
bars: entitled ? extractSmartCartBars(config.smartCart) : [],
|
|
4091
|
+
progressColor: await fetchThemeColor(config.shop.shopId, ctx)
|
|
4092
|
+
};
|
|
4093
|
+
};
|
|
4094
|
+
var fetchThemeColor = async (shopId, ctx) => {
|
|
4095
|
+
if (!shopId) return void 0;
|
|
4096
|
+
try {
|
|
4097
|
+
const theme = await ctx.fetchUpstream(`/admin/api/v1/shop/theme/${shopId}`, {}, { host: ctx.host });
|
|
4098
|
+
const color = theme.theme?.settings?.buttonBackground;
|
|
4099
|
+
return typeof color === "string" && color ? color : void 0;
|
|
4100
|
+
} catch {
|
|
4101
|
+
return void 0;
|
|
4102
|
+
}
|
|
4103
|
+
};
|
|
4104
|
+
var colorlessProgressBars = (section) => {
|
|
4105
|
+
const own = section.sectionType === SectionType.progressBar && section.progressColor === void 0;
|
|
4106
|
+
const nested = (section.sections ?? []).flatMap((child) => colorlessProgressBars(child));
|
|
4107
|
+
return own ? [section, ...nested] : nested;
|
|
4108
|
+
};
|
|
4109
|
+
var isCABTree = (result) => result.version === 2;
|
|
4110
|
+
var findMonetizeMarker = (section) => {
|
|
4111
|
+
if (section.sectionType === SectionType.monetize && section.offers === void 0) return section;
|
|
4112
|
+
if (!Array.isArray(section.sections)) return void 0;
|
|
4113
|
+
for (const child of section.sections) {
|
|
4114
|
+
const found = findMonetizeMarker(child);
|
|
4115
|
+
if (found) return found;
|
|
4116
|
+
}
|
|
4117
|
+
return void 0;
|
|
4118
|
+
};
|
|
4119
|
+
var hydrateMonetizeMarkers = async (converted, input, ctx) => {
|
|
4120
|
+
if (!input.monetize || !isCABTree(converted)) return;
|
|
4121
|
+
const first = findMonetizeMarker(converted);
|
|
4122
|
+
if (!first) return;
|
|
4123
|
+
try {
|
|
4124
|
+
const { offers } = await fetchMonetizeOffers(
|
|
4125
|
+
{
|
|
4126
|
+
...input.monetize,
|
|
4127
|
+
privacyPolicyUrl: first.privacyPolicyUrl,
|
|
4128
|
+
shop: input.shop,
|
|
4129
|
+
visitorId: input.monetize.visitorId || input.visitorId
|
|
4130
|
+
},
|
|
4131
|
+
ctx
|
|
4132
|
+
);
|
|
4133
|
+
first.offers = offers;
|
|
4134
|
+
} catch (err) {
|
|
4135
|
+
ctx.reportDrift?.({
|
|
4136
|
+
detail: `inline hydration failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
4137
|
+
kind: "monetizeOffer"
|
|
4138
|
+
});
|
|
4139
|
+
}
|
|
4140
|
+
};
|
|
4141
|
+
var fetchAndConvert = async (id, input, ctx) => {
|
|
4142
|
+
const params = { id: String(id), shop: input.shop };
|
|
4143
|
+
if (input.cacheKey != null) params.cache_key = String(input.cacheKey);
|
|
4144
|
+
if (input.key) params.key = input.key;
|
|
4145
|
+
let upstream;
|
|
4146
|
+
try {
|
|
4147
|
+
upstream = await ctx.fetchUpstream("/api/v1/widgets/settings", params, { host: ctx.host });
|
|
4148
|
+
} catch (err) {
|
|
4149
|
+
if (err instanceof UpstreamError && [400, 404, 410].includes(err.status)) {
|
|
4150
|
+
throw new NotFoundError(`Widget not found: ${id}`);
|
|
4151
|
+
}
|
|
4152
|
+
throw err;
|
|
4153
|
+
}
|
|
4154
|
+
const data = unwrapData(upstream);
|
|
4155
|
+
if (isMissingUpstreamPayload(data)) throw new NotFoundError(`Widget not found: ${id}`);
|
|
4156
|
+
const normalized = convertNumericObjects(deepCamelCase(data));
|
|
4157
|
+
if (isProgressBarV1(normalized) && normalized.barSettings.barType === "smart-cart") {
|
|
4158
|
+
const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
4159
|
+
return convertSmartCartProgressBarToV2({ bars: bars ?? [], id, progressColor, settings: normalized });
|
|
4160
|
+
}
|
|
4161
|
+
if (isConvertibleProgressBarV1(normalized)) {
|
|
4162
|
+
const { progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
4163
|
+
return convertProgressBarToV2({ id, progressColor, settings: normalized });
|
|
4164
|
+
}
|
|
4165
|
+
const converted = dispatchWidgetTransform(id, normalized, ctx.reportDrift);
|
|
4166
|
+
const colorless = isCABRootSection(normalized) ? colorlessProgressBars(converted) : [];
|
|
4167
|
+
if (colorless.length > 0) {
|
|
4168
|
+
const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx);
|
|
4169
|
+
if (color) for (const bar of colorless) bar.progressColor = color;
|
|
4170
|
+
}
|
|
4171
|
+
return converted;
|
|
4172
|
+
};
|
|
4173
|
+
var resolveWidgetExperiment = async (input, ctx) => {
|
|
4174
|
+
if (!input.visitorId) return void 0;
|
|
4175
|
+
try {
|
|
4176
|
+
const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
|
|
4177
|
+
return resolveExperiment(shop.activeExperiments, input.id, input.visitorId);
|
|
4178
|
+
} catch {
|
|
4179
|
+
return void 0;
|
|
4180
|
+
}
|
|
4181
|
+
};
|
|
4182
|
+
var convertWidgetSettings = async (input, ctx) => {
|
|
4183
|
+
const experiment = await resolveWidgetExperiment(input, ctx);
|
|
4184
|
+
const result = await fetchAndConvert(experiment?.elementId ?? input.id, input, ctx);
|
|
4185
|
+
await hydrateMonetizeMarkers(result, input, ctx);
|
|
4186
|
+
return experiment && isCABTree(result) ? { ...result, experiment } : result;
|
|
4187
|
+
};
|
|
4188
|
+
|
|
4189
|
+
// src/server/giftValidation.ts
|
|
4190
|
+
var resolveDataSourcePath = async (widgetId, shop, ctx) => {
|
|
4191
|
+
const raw = await ctx.fetchUpstream("/api/v1/widgets/settings", { id: String(widgetId), shop }, { host: ctx.host });
|
|
4192
|
+
const data = raw && typeof raw === "object" && "data" in raw ? raw.data : raw;
|
|
4193
|
+
const endpoint = deepCamelCase(data)?.endpoint;
|
|
4194
|
+
return typeof endpoint === "string" && endpoint ? endpoint : void 0;
|
|
4195
|
+
};
|
|
4196
|
+
var groupByWidget = (gifts) => {
|
|
4197
|
+
const byWidget = /* @__PURE__ */ new Map();
|
|
4198
|
+
for (const gift of gifts) {
|
|
4199
|
+
byWidget.set(gift.widgetId, [...byWidget.get(gift.widgetId) ?? [], gift]);
|
|
4200
|
+
}
|
|
4201
|
+
return byWidget;
|
|
4202
|
+
};
|
|
4203
|
+
var validateGifts = async (input, ctx) => {
|
|
4204
|
+
const perWidget = await Promise.all(
|
|
4205
|
+
[...groupByWidget(input.gifts)].map(async ([widgetId, gifts]) => {
|
|
4206
|
+
const discountLost = gifts.filter(hasLostDiscount).map((gift) => gift.lineId);
|
|
4207
|
+
try {
|
|
4208
|
+
const dataSourcePath = await resolveDataSourcePath(widgetId, input.shop, ctx);
|
|
4209
|
+
if (!dataSourcePath) return discountLost;
|
|
4210
|
+
const { metadata } = await fetchDataSourceResults(
|
|
4211
|
+
{
|
|
4212
|
+
cart: input.cart,
|
|
4213
|
+
/** Market context threads through so a market-gated gift rule answers for the buyer's market — as engine params, inheriting the shop's markets-toggle gate (REB-21688). */
|
|
4214
|
+
country: input.country,
|
|
4215
|
+
customerId: input.customerId,
|
|
4216
|
+
dataSourcePath,
|
|
4217
|
+
/** The gift being validated is in the cart — input filtering would erase it from the answer. */
|
|
4218
|
+
filterInputs: "no",
|
|
4219
|
+
language: input.language,
|
|
4220
|
+
/**
|
|
4221
|
+
* `limit: 0` fetches NO products — only the rule verdict is needed. Safe because the engine
|
|
4222
|
+
* derives `matchedRules` from rule-config evaluation, not from fetched results (traced through
|
|
4223
|
+
* `api.php`), so the metadata is complete at limit 0. Don't raise it to "fix" a non-bug.
|
|
4224
|
+
*/
|
|
4225
|
+
limit: 0,
|
|
4226
|
+
shop: input.shop,
|
|
4227
|
+
visitorId: input.visitorId
|
|
4228
|
+
},
|
|
4229
|
+
ctx
|
|
4230
|
+
);
|
|
4231
|
+
const meta = metadata ?? void 0;
|
|
4232
|
+
if (!isDefinitiveRuleAnswer(meta) || hasUnsupportedRuleTypes(meta)) return discountLost;
|
|
4233
|
+
const validIds = getValidGwpProductIds(meta.matchedRules);
|
|
4234
|
+
if (validIds.size === 0 && !isConfirmedRuleMiss(meta)) return discountLost;
|
|
4235
|
+
return gifts.filter((gift) => shouldRemoveGift(gift, validIds)).map((gift) => gift.lineId);
|
|
4236
|
+
} catch {
|
|
4237
|
+
return discountLost;
|
|
4238
|
+
}
|
|
4239
|
+
})
|
|
4240
|
+
);
|
|
4241
|
+
return { remove: perWidget.flat() };
|
|
4242
|
+
};
|
|
4243
|
+
|
|
4205
4244
|
// src/server/analytics.ts
|
|
4206
4245
|
import { chunk } from "es-toolkit";
|
|
4207
4246
|
var EXPERIMENT_TYPE_MAP = {
|
|
@@ -4373,14 +4412,6 @@ var CartInputSchema = z29.object({
|
|
|
4373
4412
|
/** Presentment subtotal in cents. */
|
|
4374
4413
|
subtotal: z29.number()
|
|
4375
4414
|
});
|
|
4376
|
-
var WidgetSettingsInputSchema = z29.object({
|
|
4377
|
-
cacheKey: z29.number().optional(),
|
|
4378
|
-
id: z29.number(),
|
|
4379
|
-
key: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4380
|
-
shop: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX),
|
|
4381
|
-
/** Stable per-buyer id; when present, enables sticky server-side A/B variant selection. */
|
|
4382
|
-
visitorId: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
|
|
4383
|
-
});
|
|
4384
4415
|
var UserConfigInputSchema = z29.object({ shop: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX) });
|
|
4385
4416
|
var DataSourceInputSchema = z29.object({
|
|
4386
4417
|
cart: CartInputSchema,
|
|
@@ -4463,6 +4494,8 @@ var MonetizeTransactionItemSchema = z29.object({
|
|
|
4463
4494
|
});
|
|
4464
4495
|
var MonetizeInputSchema = z29.object({
|
|
4465
4496
|
cart: CartInputSchema,
|
|
4497
|
+
/** Client-minted render-beacon correlation id; forwarded to the ads engine as `client_request_id`. */
|
|
4498
|
+
clientRequestId: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4466
4499
|
country: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4467
4500
|
currency: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4468
4501
|
/** Ads-targeting identity/geo/locale fields (React parity); folded into the engine query verbatim. */
|
|
@@ -4492,6 +4525,22 @@ var MonetizeInputSchema = z29.object({
|
|
|
4492
4525
|
visitorId: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4493
4526
|
zipCode: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
|
|
4494
4527
|
});
|
|
4528
|
+
var WidgetSettingsInputSchema = z29.object({
|
|
4529
|
+
cacheKey: z29.number().optional(),
|
|
4530
|
+
id: z29.number(),
|
|
4531
|
+
key: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4532
|
+
/**
|
|
4533
|
+
* Monetize context for inline hydration: when present AND the widget converts to a Monetize marker,
|
|
4534
|
+
* the server fetches/composes the offer cards inside this same request (no second `/cab/monetize`
|
|
4535
|
+
* round trip). Presence doubles as the capability signal — an absent block keeps the legacy marker
|
|
4536
|
+
* flow, so old/new client×server pairs need no lockstep deploy. `shop` rides the outer input;
|
|
4537
|
+
* `privacyPolicyUrl` comes from the widget settings server-side (more authoritative than a relay).
|
|
4538
|
+
*/
|
|
4539
|
+
monetize: MonetizeInputSchema.omit({ privacyPolicyUrl: true, shop: true }).optional(),
|
|
4540
|
+
shop: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX),
|
|
4541
|
+
/** Stable per-buyer id; when present, enables sticky server-side A/B variant selection. */
|
|
4542
|
+
visitorId: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
|
|
4543
|
+
});
|
|
4495
4544
|
var AnalyticsEventInputSchema = z29.object({
|
|
4496
4545
|
name: z29.string().max(REQUEST_BOUNDS.STRING_KEY_MAX),
|
|
4497
4546
|
properties: z29.record(z29.string().max(REQUEST_BOUNDS.STRING_KEY_MAX), z29.unknown()).optional(),
|
|
@@ -79,14 +79,6 @@ export declare const CartInputSchema: z.ZodObject<{
|
|
|
79
79
|
}, z.core.$strip>>;
|
|
80
80
|
subtotal: z.ZodNumber;
|
|
81
81
|
}, z.core.$strip>;
|
|
82
|
-
/** Input for `convertWidgetSettings`. */
|
|
83
|
-
export declare const WidgetSettingsInputSchema: z.ZodObject<{
|
|
84
|
-
cacheKey: z.ZodOptional<z.ZodNumber>;
|
|
85
|
-
id: z.ZodNumber;
|
|
86
|
-
key: z.ZodOptional<z.ZodString>;
|
|
87
|
-
shop: z.ZodString;
|
|
88
|
-
visitorId: z.ZodOptional<z.ZodString>;
|
|
89
|
-
}, z.core.$strip>;
|
|
90
82
|
/** Input for `fetchUserConfig`. */
|
|
91
83
|
export declare const UserConfigInputSchema: z.ZodObject<{
|
|
92
84
|
shop: z.ZodString;
|
|
@@ -208,6 +200,7 @@ export declare const MonetizeInputSchema: z.ZodObject<{
|
|
|
208
200
|
}, z.core.$strip>>;
|
|
209
201
|
subtotal: z.ZodNumber;
|
|
210
202
|
}, z.core.$strip>;
|
|
203
|
+
clientRequestId: z.ZodOptional<z.ZodString>;
|
|
211
204
|
country: z.ZodOptional<z.ZodString>;
|
|
212
205
|
currency: z.ZodOptional<z.ZodString>;
|
|
213
206
|
customerEmail: z.ZodOptional<z.ZodString>;
|
|
@@ -238,6 +231,64 @@ export declare const MonetizeInputSchema: z.ZodObject<{
|
|
|
238
231
|
visitorId: z.ZodOptional<z.ZodString>;
|
|
239
232
|
zipCode: z.ZodOptional<z.ZodString>;
|
|
240
233
|
}, z.core.$strip>;
|
|
234
|
+
/** Input for `convertWidgetSettings`. */
|
|
235
|
+
export declare const WidgetSettingsInputSchema: z.ZodObject<{
|
|
236
|
+
cacheKey: z.ZodOptional<z.ZodNumber>;
|
|
237
|
+
id: z.ZodNumber;
|
|
238
|
+
key: z.ZodOptional<z.ZodString>;
|
|
239
|
+
monetize: z.ZodOptional<z.ZodObject<{
|
|
240
|
+
language: z.ZodOptional<z.ZodString>;
|
|
241
|
+
currency: z.ZodOptional<z.ZodString>;
|
|
242
|
+
cart: z.ZodObject<{
|
|
243
|
+
attributes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
244
|
+
key: z.ZodString;
|
|
245
|
+
value: z.ZodOptional<z.ZodString>;
|
|
246
|
+
}, z.core.$strip>>>;
|
|
247
|
+
currencyRate: z.ZodOptional<z.ZodNumber>;
|
|
248
|
+
discountCodes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
249
|
+
discountTotal: z.ZodOptional<z.ZodNumber>;
|
|
250
|
+
items: z.ZodArray<z.ZodObject<{
|
|
251
|
+
productId: z.ZodString;
|
|
252
|
+
properties: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
253
|
+
key: z.ZodString;
|
|
254
|
+
value: z.ZodOptional<z.ZodString>;
|
|
255
|
+
}, z.core.$strip>>>;
|
|
256
|
+
quantity: z.ZodNumber;
|
|
257
|
+
subscriptionId: z.ZodOptional<z.ZodString>;
|
|
258
|
+
variantId: z.ZodString;
|
|
259
|
+
}, z.core.$strip>>;
|
|
260
|
+
subtotal: z.ZodNumber;
|
|
261
|
+
}, z.core.$strip>;
|
|
262
|
+
country: z.ZodOptional<z.ZodString>;
|
|
263
|
+
customerId: z.ZodOptional<z.ZodString>;
|
|
264
|
+
visitorId: z.ZodOptional<z.ZodString>;
|
|
265
|
+
clientRequestId: z.ZodOptional<z.ZodString>;
|
|
266
|
+
customerEmail: z.ZodOptional<z.ZodString>;
|
|
267
|
+
customerFirstName: z.ZodOptional<z.ZodString>;
|
|
268
|
+
customerLastName: z.ZodOptional<z.ZodString>;
|
|
269
|
+
orderName: z.ZodOptional<z.ZodString>;
|
|
270
|
+
placement: z.ZodEnum<{
|
|
271
|
+
customer_account: "customer_account";
|
|
272
|
+
order_status: "order_status";
|
|
273
|
+
thank_you_page: "thank_you_page";
|
|
274
|
+
}>;
|
|
275
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
276
|
+
testing: z.ZodOptional<z.ZodBoolean>;
|
|
277
|
+
timezone: z.ZodOptional<z.ZodString>;
|
|
278
|
+
transactionId: z.ZodOptional<z.ZodString>;
|
|
279
|
+
transactionItems: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
280
|
+
id: z.ZodString;
|
|
281
|
+
priceCents: z.ZodNumber;
|
|
282
|
+
quantity: z.ZodNumber;
|
|
283
|
+
title: z.ZodString;
|
|
284
|
+
type: z.ZodString;
|
|
285
|
+
}, z.core.$strip>>>;
|
|
286
|
+
transactionTotal: z.ZodOptional<z.ZodNumber>;
|
|
287
|
+
zipCode: z.ZodOptional<z.ZodString>;
|
|
288
|
+
}, z.core.$strip>>;
|
|
289
|
+
shop: z.ZodString;
|
|
290
|
+
visitorId: z.ZodOptional<z.ZodString>;
|
|
291
|
+
}, z.core.$strip>;
|
|
241
292
|
/**
|
|
242
293
|
* A generic analytics event from the extension; the server maps `name` → the engine's noun/verb/subject.
|
|
243
294
|
* `properties` VALUES stay unbounded (`z.unknown`): the analytics rail is lenient-parsed (an over-limit
|
|
@@ -929,7 +929,13 @@ var import_zod14 = require("zod");
|
|
|
929
929
|
var CABMonetizeSection = import_zod14.z.object({
|
|
930
930
|
...CABVariableFields,
|
|
931
931
|
name: import_zod14.z.string().optional(),
|
|
932
|
-
/**
|
|
932
|
+
/**
|
|
933
|
+
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
934
|
+
* context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
|
|
935
|
+
* nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
|
|
936
|
+
*/
|
|
937
|
+
offers: import_zod14.z.array(import_zod14.z.lazy(() => CABSection)).optional(),
|
|
938
|
+
/** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
|
|
933
939
|
privacyPolicyUrl: import_zod14.z.string().optional(),
|
|
934
940
|
sectionId: import_zod14.z.uuid().default(() => (0, import_uuid8.v7)()),
|
|
935
941
|
sectionType: import_zod14.z.literal(SectionType.monetize).default(SectionType.monetize),
|
|
@@ -872,7 +872,13 @@ import { z as z14 } from "zod";
|
|
|
872
872
|
var CABMonetizeSection = z14.object({
|
|
873
873
|
...CABVariableFields,
|
|
874
874
|
name: z14.string().optional(),
|
|
875
|
-
/**
|
|
875
|
+
/**
|
|
876
|
+
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
877
|
+
* context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
|
|
878
|
+
* nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
|
|
879
|
+
*/
|
|
880
|
+
offers: z14.array(z14.lazy(() => CABSection)).optional(),
|
|
881
|
+
/** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
|
|
876
882
|
privacyPolicyUrl: z14.string().optional(),
|
|
877
883
|
sectionId: z14.uuid().default(() => uuidv78()),
|
|
878
884
|
sectionType: z14.literal(SectionType.monetize).default(SectionType.monetize),
|
|
@@ -3,8 +3,9 @@ import { type V1MonetizeConvertInput } from '../../transforms/monetizeV1/types';
|
|
|
3
3
|
/**
|
|
4
4
|
* Translate a v1 Monetize (standalone `ui_extension_ad`) widget into a `CABRootSection`: a stub root
|
|
5
5
|
* holding one `monetize` marker section. The marker carries the widget's only real config — the Privacy
|
|
6
|
-
* Policy href and the ads-taxonomy `vertical`/`subVertical` ids;
|
|
7
|
-
*
|
|
6
|
+
* Policy href and the ads-taxonomy `vertical`/`subVertical` ids; no ad content is converted here — the
|
|
7
|
+
* live offer cards are hydrated onto the marker by `convertWidgetSettings` when the request carries a
|
|
8
|
+
* `monetize` context block, else composed by `POST /cab/monetize` when the client mounts it. The root
|
|
8
9
|
* keeps `type: ui_extension_ad` (the type is the widget's identity, not the converter's), and the
|
|
9
10
|
* marker's `visibility` stays unset: React always mounts Monetize on its target surfaces, so the
|
|
10
11
|
* converted tree must not invent a gate.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebuy/rebuy",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Shared zod schemas, legacy-to-CAB widget transforms, and server orchestrators for Rebuy's Shopify consumers (rebuy-api, admin-nextjs, rebuy-shopify-extensions)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Rebuy, Inc.",
|