@rebuy/rebuy 3.60.5 → 3.62.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.
@@ -5,16 +5,20 @@ import { type SectionDetails } from './shared';
5
5
  * One per-tier exclusion rule (onsite `getTierExclusionTotal`): cart lines matching the rule's `type` list
6
6
  * are subtracted from THIS tier's progress only, after the bar-level exclusions (gift lines, the exclusion
7
7
  * tag, `excludedVariantIds`) have already removed theirs — so a line is never double-counted. The client
8
- * resolves collections and tags per cart product from the Storefront API; `productIds` match the line's
9
- * product directly. Lists default empty so a rule always reads as "match nothing" rather than throwing.
8
+ * resolves collections and tags per cart product from the Storefront API; `products` match the line's
9
+ * product directly, narrowed to the entry's `variantIds` when it lists any. Lists default empty
10
+ * so a rule always reads as "match nothing" rather than throwing.
10
11
  */
11
12
  export declare const CABProgressTierExclusion: z.ZodObject<{
12
13
  collectionIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
13
- productIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
14
+ products: z.ZodDefault<z.ZodArray<z.ZodObject<{
15
+ productId: z.ZodNumber;
16
+ variantIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
17
+ }, z.core.$strip>>>;
14
18
  tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
15
19
  type: z.ZodEnum<{
16
- tags: "tags";
17
20
  products: "products";
21
+ tags: "tags";
18
22
  collections: "collections";
19
23
  }>;
20
24
  }, z.core.$strip>;
@@ -29,11 +33,14 @@ export declare const CABProgressTier: z.ZodObject<{
29
33
  }, z.core.$strip>>;
30
34
  exclusions: z.ZodDefault<z.ZodArray<z.ZodObject<{
31
35
  collectionIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
32
- productIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
36
+ products: z.ZodDefault<z.ZodArray<z.ZodObject<{
37
+ productId: z.ZodNumber;
38
+ variantIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
39
+ }, z.core.$strip>>>;
33
40
  tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
34
41
  type: z.ZodEnum<{
35
- tags: "tags";
36
42
  products: "products";
43
+ tags: "tags";
37
44
  collections: "collections";
38
45
  }>;
39
46
  }, z.core.$strip>>>;
@@ -373,11 +380,14 @@ export declare const CABProgressBarSection: z.ZodLazy<z.ZodObject<{
373
380
  }, z.core.$strip>>;
374
381
  exclusions: z.ZodDefault<z.ZodArray<z.ZodObject<{
375
382
  collectionIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
376
- productIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
383
+ products: z.ZodDefault<z.ZodArray<z.ZodObject<{
384
+ productId: z.ZodNumber;
385
+ variantIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
386
+ }, z.core.$strip>>>;
377
387
  tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
378
388
  type: z.ZodEnum<{
379
- tags: "tags";
380
389
  products: "products";
390
+ tags: "tags";
381
391
  collections: "collections";
382
392
  }>;
383
393
  }, z.core.$strip>>>;
@@ -1576,7 +1576,22 @@ var SmartCartTierProduct = import_zod19.z.looseObject({
1576
1576
  });
1577
1577
  var SmartCartTierExclusion = import_zod19.z.looseObject({
1578
1578
  collectionIds: import_zod19.z.array(import_zod19.z.coerce.number()).optional().catch(void 0),
1579
- productIds: import_zod19.z.array(import_zod19.z.coerce.number()).optional().catch(void 0),
1579
+ /**
1580
+ * onsite keeps each excluded product with ITS OWN variants, the same shape the bar-level
1581
+ * `excludedProducts` uses, so a rule can exclude one variant of B without touching A.
1582
+ * Flat parallel id lists cannot express that mapping.
1583
+ *
1584
+ * camelCase like every other key in this file, NOT the `product_id` / `variant_ids` the onsite
1585
+ * payload spells: `parseShielded` runs `deepCamelCase` over the tree BEFORE any schema sees it.
1586
+ * A snake_case key here reads as absent, every entry drops, and the converter discards the whole
1587
+ * rule in silence — the exclusion just never reaches checkout.
1588
+ */
1589
+ products: import_zod19.z.array(
1590
+ import_zod19.z.looseObject({
1591
+ productId: import_zod19.z.coerce.number().optional().catch(void 0),
1592
+ variantIds: import_zod19.z.array(import_zod19.z.coerce.number()).optional().catch(void 0)
1593
+ })
1594
+ ).optional().catch(void 0),
1580
1595
  tags: import_zod19.z.array(import_zod19.z.string()).optional().catch(void 0),
1581
1596
  type: import_zod19.z.string().optional().catch(void 0)
1582
1597
  });
@@ -1711,6 +1726,7 @@ var SmartCartComponent = import_zod19.z.looseObject({
1711
1726
  var SmartCartConfig = import_zod19.z.looseObject({
1712
1727
  components: import_zod19.z.array(SmartCartComponent).optional()
1713
1728
  });
1729
+ var EngineSmartCart = import_zod19.z.preprocess((value) => value === false ? null : value, SmartCartConfig.nullish());
1714
1730
 
1715
1731
  // src/schema/cabShopConfig.ts
1716
1732
  var CabActiveExperiment = import_zod20.z.object({
@@ -1808,7 +1824,7 @@ var CabShopConfig = import_zod20.z.object({
1808
1824
  });
1809
1825
  var CabUserConfig = import_zod20.z.object({
1810
1826
  shop: CabShopConfig,
1811
- smartCart: SmartCartConfig.nullish().catch(DRIFTED_SMART_CART)
1827
+ smartCart: EngineSmartCart.catch(DRIFTED_SMART_CART)
1812
1828
  });
1813
1829
 
1814
1830
  // src/schema/widgets/checkout-and-beyond/image.ts
@@ -2388,7 +2404,17 @@ var GIFT_LABELS = {
2388
2404
  // src/schema/widgets/checkout-and-beyond/progressBar.ts
2389
2405
  var CABProgressTierExclusion = import_zod33.z.object({
2390
2406
  collectionIds: import_zod33.z.array(import_zod33.z.number().int().positive()).default(() => []),
2391
- productIds: import_zod33.z.array(import_zod33.z.number().int().positive()).default(() => []),
2407
+ /**
2408
+ * `products` rules: each excluded product with ITS OWN variants, mirroring the bar-level
2409
+ * excluded products. An empty `variantIds` means "the whole product", which is how the admin
2410
+ * picker stores a whole-product selection, and a populated one means those variants.
2411
+ */
2412
+ products: import_zod33.z.array(
2413
+ import_zod33.z.object({
2414
+ productId: import_zod33.z.number().int().positive(),
2415
+ variantIds: import_zod33.z.array(import_zod33.z.number().int().positive()).default(() => [])
2416
+ })
2417
+ ).default(() => []),
2392
2418
  tags: import_zod33.z.array(import_zod33.z.string().min(1)).default(() => []),
2393
2419
  type: import_zod33.z.enum(progressTierExclusionTypes)
2394
2420
  });
@@ -5325,13 +5351,21 @@ var giftOf = (tier) => {
5325
5351
  variantIds: positiveIds(product.variantIds)
5326
5352
  };
5327
5353
  };
5328
- var RULE_LIST = { collections: "collectionIds", products: "productIds", tags: "tags" };
5354
+ var RULE_LIST = { collections: "collectionIds", products: "products", tags: "tags" };
5355
+ var exclusionProducts = (rule) => (rule.products ?? []).flatMap((entry) => {
5356
+ const productId2 = entry.productId;
5357
+ if (!Number.isInteger(productId2) || !productId2 || productId2 <= 0) return [];
5358
+ const requested = entry.variantIds ?? [];
5359
+ const variantIds = positiveIds(requested);
5360
+ if (requested.length > 0 && variantIds.length === 0) return [];
5361
+ return [{ productId: productId2, variantIds }];
5362
+ });
5329
5363
  var exclusionsOf = (tier) => {
5330
5364
  if (!tier.exclusionsEnabled) return void 0;
5331
5365
  const rules = (tier.exclusions ?? []).flatMap((rule) => {
5332
5366
  const parsed = CABProgressTierExclusion.safeParse({
5333
5367
  ...rule.type === "collections" && { collectionIds: positiveIds(rule.collectionIds) },
5334
- ...rule.type === "products" && { productIds: positiveIds(rule.productIds) },
5368
+ ...rule.type === "products" && { products: exclusionProducts(rule) },
5335
5369
  ...rule.type === "tags" && { tags: (rule.tags ?? []).map((tag) => tag.trim()).filter(Boolean) },
5336
5370
  type: rule.type
5337
5371
  });
@@ -6728,6 +6762,23 @@ var projectPostPurchasePlanCatalog = (normalized, report) => {
6728
6762
  }
6729
6763
  };
6730
6764
 
6765
+ // src/server/postPurchaseUnavailableChoice.ts
6766
+ var choiceUnavailable = (reason) => Object.freeze({ level: "choice", reason });
6767
+ var configurationUnavailable = (reason) => ({
6768
+ level: "configuration",
6769
+ reason
6770
+ });
6771
+ var VARIANT_UNAVAILABLE = choiceUnavailable("The chosen variant is unavailable");
6772
+ var PLAN_UNAVAILABLE = choiceUnavailable("Subscription plan is unavailable");
6773
+ var CHOICE_UNAVAILABLE = choiceUnavailable("Requested purchase choice is unavailable");
6774
+ var GROUP_DEFAULT_UNAVAILABLE = choiceUnavailable("Post-purchase group default is unavailable");
6775
+ var unsupportedPreparation = ({ level, reason }) => ({
6776
+ level,
6777
+ reason,
6778
+ status: "unsupported"
6779
+ });
6780
+ var publicPreparation = (preparation) => preparation.status === "unsupported" ? { reason: preparation.reason, status: "unsupported" } : preparation;
6781
+
6731
6782
  // src/schema/postPurchaseSubscriptionProduct.ts
6732
6783
  var import_zod64 = require("zod");
6733
6784
 
@@ -6866,26 +6917,15 @@ var PostPurchaseSubscriptionSettings = subscription.extend({
6866
6917
  })
6867
6918
  });
6868
6919
 
6869
- // src/server/postPurchaseUnavailableChoice.ts
6870
- var VARIANT_UNAVAILABLE = "The chosen variant is unavailable";
6871
- var PLAN_UNAVAILABLE = "Subscription plan is unavailable";
6872
- var CHOICE_UNAVAILABLE = "Requested purchase choice is unavailable";
6873
- var GROUP_DEFAULT_UNAVAILABLE = "Post-purchase group default is unavailable";
6874
- var choiceLevel = /* @__PURE__ */ new Set([
6875
- VARIANT_UNAVAILABLE,
6876
- PLAN_UNAVAILABLE,
6877
- CHOICE_UNAVAILABLE,
6878
- GROUP_DEFAULT_UNAVAILABLE
6879
- ]);
6880
- var isChoiceUnavailable = (reason) => reason !== void 0 && choiceLevel.has(reason);
6881
-
6882
6920
  // src/server/postPurchaseVariantAvailable.ts
6883
6921
  var postPurchaseVariantAvailable = (variant2) => !(variant2.inventoryManagement && variant2.inventoryPolicy === "deny" && variant2.inventoryQuantity <= 0);
6884
6922
 
6885
6923
  // src/server/postPurchaseSubscriptionEligibility.ts
6886
6924
  var subscriptionUnavailable = (normalized, plan, allowCollection, quantity3) => {
6887
6925
  const { data, effective, original } = normalized;
6888
- const unavailable = "An eligible subscription plan with supported shipping and discount is required";
6926
+ const unavailable = configurationUnavailable(
6927
+ "An eligible subscription plan with supported shipping and discount is required"
6928
+ );
6889
6929
  if (data.length !== 1 || data[0].variants.length !== 1) return unavailable;
6890
6930
  if (
6891
6931
  // Legacy STS ran whatever the product type: its trigger forced the subscription option on.
@@ -7052,9 +7092,9 @@ var projectPostPurchaseProduct = ({ data, effective, metadata, original }, repor
7052
7092
 
7053
7093
  // src/server/prepareNormalizedSubscription.ts
7054
7094
  var prepareNormalizedSubscription = (normalized, report, plan, quantity3, allowCollection = false) => {
7055
- if (!plan) return { reason: PLAN_UNAVAILABLE, status: "unsupported" };
7056
- const reason = subscriptionUnavailable(normalized, plan, allowCollection, quantity3);
7057
- if (reason) return { reason, status: "unsupported" };
7095
+ if (!plan) return unsupportedPreparation(PLAN_UNAVAILABLE);
7096
+ const unavailable = subscriptionUnavailable(normalized, plan, allowCollection, quantity3);
7097
+ if (unavailable) return unsupportedPreparation(unavailable);
7058
7098
  const { effective } = normalized;
7059
7099
  const projected = projectPostPurchaseProduct(normalized, report, quantity3);
7060
7100
  const { discount, shipping } = PostPurchaseSubscriptionSettings.parse(effective.subscription);
@@ -7088,33 +7128,34 @@ var prepareNormalizedSubscription = (normalized, report, plan, quantity3, allowC
7088
7128
  // src/server/preparePostPurchaseSubscription.ts
7089
7129
  var preparePostPurchaseSubscription = (settings, response, report) => {
7090
7130
  const normalized = normalizePostPurchaseProduct(settings, response, report);
7091
- return prepareNormalizedSubscription(
7092
- normalized,
7093
- report,
7094
- projectPostPurchasePlanCatalog(normalized, report)?.plans[0]
7131
+ return publicPreparation(
7132
+ prepareNormalizedSubscription(normalized, report, projectPostPurchasePlanCatalog(normalized, report)?.plans[0])
7095
7133
  );
7096
7134
  };
7097
7135
 
7098
7136
  // src/server/postPurchaseProductEligibility.ts
7099
7137
  var oneTimeUnavailable = (normalized, allowCollection, quantity3) => {
7100
7138
  const { data, effective, original } = normalized;
7101
- if (data.length !== 1 || data[0].variants.length !== 1) return "Only one fixed product and variant are supported";
7139
+ if (data.length !== 1 || data[0].variants.length !== 1)
7140
+ return configurationUnavailable("Only one fixed product and variant are supported");
7102
7141
  if (effective.productType === "subscription" || [original, effective].some(
7103
7142
  (config) => !allowCollection && config.productLimit !== 1 || config.upgradeToSubscription.allow || !quantity3 && config.quantityInputs.enabled && (config.quantityInputs.minValue !== config.quantityInputs.maxValue || config.quantityInputs.defaultQuantity !== config.quantityInputs.minValue)
7104
7143
  ))
7105
- return "Only fixed one-time configuration is supported";
7144
+ return configurationUnavailable("Only fixed one-time configuration is supported");
7106
7145
  const product = data[0];
7107
7146
  const variant2 = product.variants[0];
7108
7147
  if (product.productGroupVariants.length || requiresOwnSellingPlan(product) || product.sellingPlanGroups?.some((group) => group.requiresSellingPlan))
7109
- return "A one-time variant without product groups or a required selling plan is required";
7148
+ return configurationUnavailable(
7149
+ "A one-time variant without product groups or a required selling plan is required"
7150
+ );
7110
7151
  if (!postPurchaseVariantAvailable(variant2)) return VARIANT_UNAVAILABLE;
7111
7152
  if (variant2.productId !== product.id) throw new Error("Product variant identity mismatch");
7112
7153
  };
7113
7154
 
7114
7155
  // src/server/preparePostPurchaseProduct.ts
7115
7156
  var prepareNormalizedProduct = (normalized, report, quantity3, allowCollection = false) => {
7116
- const reason = oneTimeUnavailable(normalized, allowCollection, quantity3);
7117
- if (reason) return { reason, status: "unsupported" };
7157
+ const unavailable = oneTimeUnavailable(normalized, allowCollection, quantity3);
7158
+ if (unavailable) return unsupportedPreparation(unavailable);
7118
7159
  const { effective } = normalized;
7119
7160
  return PreparedPostPurchaseProduct.parse({
7120
7161
  ...projectPostPurchaseProduct(normalized, report, quantity3),
@@ -7125,7 +7166,7 @@ var prepareNormalizedProduct = (normalized, report, quantity3, allowCollection =
7125
7166
  }
7126
7167
  });
7127
7168
  };
7128
- var preparePostPurchaseProduct = (settings, response, report) => prepareNormalizedProduct(normalizePostPurchaseProduct(settings, response, report), report);
7169
+ var preparePostPurchaseProduct = (settings, response, report) => publicPreparation(prepareNormalizedProduct(normalizePostPurchaseProduct(settings, response, report), report));
7129
7170
 
7130
7171
  // src/schema/postPurchaseProductView.ts
7131
7172
  var import_zod73 = require("zod");
@@ -7381,7 +7422,7 @@ var postPurchaseCardSections = (widgetId, {
7381
7422
  var preparePostPurchaseCardSections = (widgetId, settings, response, report) => postPurchaseCardSections(widgetId, normalizePostPurchaseProduct(settings, response, report), report);
7382
7423
 
7383
7424
  // src/server/selectPostPurchaseChoice.ts
7384
- var preferChoiceLevel = (preferred, other) => !isChoiceUnavailable(preferred) && isChoiceUnavailable(other) ? other : preferred;
7425
+ var preferChoiceLevel = (preferred, other) => preferred.level === "configuration" && other.level === "choice" ? other : preferred;
7385
7426
  var selectPostPurchaseChoice = (normalized, projected, requested, allowCollection, quantity3) => {
7386
7427
  const { data, effective } = normalized;
7387
7428
  const modes = [];
@@ -7396,7 +7437,7 @@ var selectPostPurchaseChoice = (normalized, projected, requested, allowCollectio
7396
7437
  choice: void 0,
7397
7438
  options: void 0,
7398
7439
  plan: void 0,
7399
- reason: preferChoiceLevel(
7440
+ unavailable: preferChoiceLevel(
7400
7441
  effective.productType === "one-time" ? oneTimeReason : subscriptionReason,
7401
7442
  effective.productType === "one-time" ? subscriptionReason : oneTimeReason
7402
7443
  )
@@ -7410,7 +7451,7 @@ var selectPostPurchaseChoice = (normalized, projected, requested, allowCollectio
7410
7451
  choice: bound,
7411
7452
  options: options2,
7412
7453
  plan: bound ? plan : void 0,
7413
- reason: bound ? void 0 : CHOICE_UNAVAILABLE
7454
+ unavailable: bound ? void 0 : CHOICE_UNAVAILABLE
7414
7455
  };
7415
7456
  };
7416
7457
 
@@ -7426,13 +7467,13 @@ var prepareNormalizedPostPurchaseView = (widgetId, normalized, report, choice, q
7426
7467
  const labels = { notAvailable: normalized.effective.language.global.notAvailable };
7427
7468
  const sts = quantity3?.sts === true;
7428
7469
  const marker = sts ? { sts: true } : {};
7429
- const preparation = !selected.choice ? { reason: selected.reason, status: "unsupported" } : selected.choice.mode === "subscription" ? prepareNormalizedSubscription(normalized, report, selected.plan, quantity3, allowCollection) : prepareNormalizedProduct(normalized, report, quantity3, allowCollection);
7470
+ const preparation = !selected.choice ? unsupportedPreparation(selected.unavailable) : selected.choice.mode === "subscription" ? prepareNormalizedSubscription(normalized, report, selected.plan, quantity3, allowCollection) : prepareNormalizedProduct(normalized, report, quantity3, allowCollection);
7430
7471
  if (preparation.status === "unsupported") {
7431
- const sections2 = isChoiceUnavailable(preparation.reason) ? postPurchaseCardSections(widgetId, normalized, report, sts) : null;
7472
+ const sections2 = preparation.level === "choice" ? postPurchaseCardSections(widgetId, normalized, report, sts) : null;
7432
7473
  return PostPurchaseProductView.parse({
7433
7474
  labels,
7434
7475
  planCatalog,
7435
- preparation,
7476
+ preparation: publicPreparation(preparation),
7436
7477
  purchaseOptions,
7437
7478
  sections: sections2,
7438
7479
  ...marker
@@ -8162,7 +8203,7 @@ var preparePostPurchaseCollectionGroup = (widgetId, settings, response, member,
8162
8203
  view: prepared ? { ...prepared.view, groupCatalog: prepared.groupCatalog } : {
8163
8204
  groupCatalog: group.catalog,
8164
8205
  labels: { notAvailable: normalized.effective.language.global.notAvailable },
8165
- preparation: { reason: GROUP_DEFAULT_UNAVAILABLE, status: "unsupported" },
8206
+ preparation: publicPreparation(unsupportedPreparation(GROUP_DEFAULT_UNAVAILABLE)),
8166
8207
  sections: postPurchaseCardSections(widgetId, normalized, report)
8167
8208
  }
8168
8209
  };
@@ -1503,7 +1503,22 @@ var SmartCartTierProduct = z19.looseObject({
1503
1503
  });
1504
1504
  var SmartCartTierExclusion = z19.looseObject({
1505
1505
  collectionIds: z19.array(z19.coerce.number()).optional().catch(void 0),
1506
- productIds: z19.array(z19.coerce.number()).optional().catch(void 0),
1506
+ /**
1507
+ * onsite keeps each excluded product with ITS OWN variants, the same shape the bar-level
1508
+ * `excludedProducts` uses, so a rule can exclude one variant of B without touching A.
1509
+ * Flat parallel id lists cannot express that mapping.
1510
+ *
1511
+ * camelCase like every other key in this file, NOT the `product_id` / `variant_ids` the onsite
1512
+ * payload spells: `parseShielded` runs `deepCamelCase` over the tree BEFORE any schema sees it.
1513
+ * A snake_case key here reads as absent, every entry drops, and the converter discards the whole
1514
+ * rule in silence — the exclusion just never reaches checkout.
1515
+ */
1516
+ products: z19.array(
1517
+ z19.looseObject({
1518
+ productId: z19.coerce.number().optional().catch(void 0),
1519
+ variantIds: z19.array(z19.coerce.number()).optional().catch(void 0)
1520
+ })
1521
+ ).optional().catch(void 0),
1507
1522
  tags: z19.array(z19.string()).optional().catch(void 0),
1508
1523
  type: z19.string().optional().catch(void 0)
1509
1524
  });
@@ -1638,6 +1653,7 @@ var SmartCartComponent = z19.looseObject({
1638
1653
  var SmartCartConfig = z19.looseObject({
1639
1654
  components: z19.array(SmartCartComponent).optional()
1640
1655
  });
1656
+ var EngineSmartCart = z19.preprocess((value) => value === false ? null : value, SmartCartConfig.nullish());
1641
1657
 
1642
1658
  // src/schema/cabShopConfig.ts
1643
1659
  var CabActiveExperiment = z20.object({
@@ -1735,7 +1751,7 @@ var CabShopConfig = z20.object({
1735
1751
  });
1736
1752
  var CabUserConfig = z20.object({
1737
1753
  shop: CabShopConfig,
1738
- smartCart: SmartCartConfig.nullish().catch(DRIFTED_SMART_CART)
1754
+ smartCart: EngineSmartCart.catch(DRIFTED_SMART_CART)
1739
1755
  });
1740
1756
 
1741
1757
  // src/schema/widgets/checkout-and-beyond/image.ts
@@ -2315,7 +2331,17 @@ var GIFT_LABELS = {
2315
2331
  // src/schema/widgets/checkout-and-beyond/progressBar.ts
2316
2332
  var CABProgressTierExclusion = z33.object({
2317
2333
  collectionIds: z33.array(z33.number().int().positive()).default(() => []),
2318
- productIds: z33.array(z33.number().int().positive()).default(() => []),
2334
+ /**
2335
+ * `products` rules: each excluded product with ITS OWN variants, mirroring the bar-level
2336
+ * excluded products. An empty `variantIds` means "the whole product", which is how the admin
2337
+ * picker stores a whole-product selection, and a populated one means those variants.
2338
+ */
2339
+ products: z33.array(
2340
+ z33.object({
2341
+ productId: z33.number().int().positive(),
2342
+ variantIds: z33.array(z33.number().int().positive()).default(() => [])
2343
+ })
2344
+ ).default(() => []),
2319
2345
  tags: z33.array(z33.string().min(1)).default(() => []),
2320
2346
  type: z33.enum(progressTierExclusionTypes)
2321
2347
  });
@@ -5252,13 +5278,21 @@ var giftOf = (tier) => {
5252
5278
  variantIds: positiveIds(product.variantIds)
5253
5279
  };
5254
5280
  };
5255
- var RULE_LIST = { collections: "collectionIds", products: "productIds", tags: "tags" };
5281
+ var RULE_LIST = { collections: "collectionIds", products: "products", tags: "tags" };
5282
+ var exclusionProducts = (rule) => (rule.products ?? []).flatMap((entry) => {
5283
+ const productId2 = entry.productId;
5284
+ if (!Number.isInteger(productId2) || !productId2 || productId2 <= 0) return [];
5285
+ const requested = entry.variantIds ?? [];
5286
+ const variantIds = positiveIds(requested);
5287
+ if (requested.length > 0 && variantIds.length === 0) return [];
5288
+ return [{ productId: productId2, variantIds }];
5289
+ });
5256
5290
  var exclusionsOf = (tier) => {
5257
5291
  if (!tier.exclusionsEnabled) return void 0;
5258
5292
  const rules = (tier.exclusions ?? []).flatMap((rule) => {
5259
5293
  const parsed = CABProgressTierExclusion.safeParse({
5260
5294
  ...rule.type === "collections" && { collectionIds: positiveIds(rule.collectionIds) },
5261
- ...rule.type === "products" && { productIds: positiveIds(rule.productIds) },
5295
+ ...rule.type === "products" && { products: exclusionProducts(rule) },
5262
5296
  ...rule.type === "tags" && { tags: (rule.tags ?? []).map((tag) => tag.trim()).filter(Boolean) },
5263
5297
  type: rule.type
5264
5298
  });
@@ -6655,6 +6689,23 @@ var projectPostPurchasePlanCatalog = (normalized, report) => {
6655
6689
  }
6656
6690
  };
6657
6691
 
6692
+ // src/server/postPurchaseUnavailableChoice.ts
6693
+ var choiceUnavailable = (reason) => Object.freeze({ level: "choice", reason });
6694
+ var configurationUnavailable = (reason) => ({
6695
+ level: "configuration",
6696
+ reason
6697
+ });
6698
+ var VARIANT_UNAVAILABLE = choiceUnavailable("The chosen variant is unavailable");
6699
+ var PLAN_UNAVAILABLE = choiceUnavailable("Subscription plan is unavailable");
6700
+ var CHOICE_UNAVAILABLE = choiceUnavailable("Requested purchase choice is unavailable");
6701
+ var GROUP_DEFAULT_UNAVAILABLE = choiceUnavailable("Post-purchase group default is unavailable");
6702
+ var unsupportedPreparation = ({ level, reason }) => ({
6703
+ level,
6704
+ reason,
6705
+ status: "unsupported"
6706
+ });
6707
+ var publicPreparation = (preparation) => preparation.status === "unsupported" ? { reason: preparation.reason, status: "unsupported" } : preparation;
6708
+
6658
6709
  // src/schema/postPurchaseSubscriptionProduct.ts
6659
6710
  import { z as z64 } from "zod";
6660
6711
 
@@ -6793,26 +6844,15 @@ var PostPurchaseSubscriptionSettings = subscription.extend({
6793
6844
  })
6794
6845
  });
6795
6846
 
6796
- // src/server/postPurchaseUnavailableChoice.ts
6797
- var VARIANT_UNAVAILABLE = "The chosen variant is unavailable";
6798
- var PLAN_UNAVAILABLE = "Subscription plan is unavailable";
6799
- var CHOICE_UNAVAILABLE = "Requested purchase choice is unavailable";
6800
- var GROUP_DEFAULT_UNAVAILABLE = "Post-purchase group default is unavailable";
6801
- var choiceLevel = /* @__PURE__ */ new Set([
6802
- VARIANT_UNAVAILABLE,
6803
- PLAN_UNAVAILABLE,
6804
- CHOICE_UNAVAILABLE,
6805
- GROUP_DEFAULT_UNAVAILABLE
6806
- ]);
6807
- var isChoiceUnavailable = (reason) => reason !== void 0 && choiceLevel.has(reason);
6808
-
6809
6847
  // src/server/postPurchaseVariantAvailable.ts
6810
6848
  var postPurchaseVariantAvailable = (variant2) => !(variant2.inventoryManagement && variant2.inventoryPolicy === "deny" && variant2.inventoryQuantity <= 0);
6811
6849
 
6812
6850
  // src/server/postPurchaseSubscriptionEligibility.ts
6813
6851
  var subscriptionUnavailable = (normalized, plan, allowCollection, quantity3) => {
6814
6852
  const { data, effective, original } = normalized;
6815
- const unavailable = "An eligible subscription plan with supported shipping and discount is required";
6853
+ const unavailable = configurationUnavailable(
6854
+ "An eligible subscription plan with supported shipping and discount is required"
6855
+ );
6816
6856
  if (data.length !== 1 || data[0].variants.length !== 1) return unavailable;
6817
6857
  if (
6818
6858
  // Legacy STS ran whatever the product type: its trigger forced the subscription option on.
@@ -6979,9 +7019,9 @@ var projectPostPurchaseProduct = ({ data, effective, metadata, original }, repor
6979
7019
 
6980
7020
  // src/server/prepareNormalizedSubscription.ts
6981
7021
  var prepareNormalizedSubscription = (normalized, report, plan, quantity3, allowCollection = false) => {
6982
- if (!plan) return { reason: PLAN_UNAVAILABLE, status: "unsupported" };
6983
- const reason = subscriptionUnavailable(normalized, plan, allowCollection, quantity3);
6984
- if (reason) return { reason, status: "unsupported" };
7022
+ if (!plan) return unsupportedPreparation(PLAN_UNAVAILABLE);
7023
+ const unavailable = subscriptionUnavailable(normalized, plan, allowCollection, quantity3);
7024
+ if (unavailable) return unsupportedPreparation(unavailable);
6985
7025
  const { effective } = normalized;
6986
7026
  const projected = projectPostPurchaseProduct(normalized, report, quantity3);
6987
7027
  const { discount, shipping } = PostPurchaseSubscriptionSettings.parse(effective.subscription);
@@ -7015,33 +7055,34 @@ var prepareNormalizedSubscription = (normalized, report, plan, quantity3, allowC
7015
7055
  // src/server/preparePostPurchaseSubscription.ts
7016
7056
  var preparePostPurchaseSubscription = (settings, response, report) => {
7017
7057
  const normalized = normalizePostPurchaseProduct(settings, response, report);
7018
- return prepareNormalizedSubscription(
7019
- normalized,
7020
- report,
7021
- projectPostPurchasePlanCatalog(normalized, report)?.plans[0]
7058
+ return publicPreparation(
7059
+ prepareNormalizedSubscription(normalized, report, projectPostPurchasePlanCatalog(normalized, report)?.plans[0])
7022
7060
  );
7023
7061
  };
7024
7062
 
7025
7063
  // src/server/postPurchaseProductEligibility.ts
7026
7064
  var oneTimeUnavailable = (normalized, allowCollection, quantity3) => {
7027
7065
  const { data, effective, original } = normalized;
7028
- if (data.length !== 1 || data[0].variants.length !== 1) return "Only one fixed product and variant are supported";
7066
+ if (data.length !== 1 || data[0].variants.length !== 1)
7067
+ return configurationUnavailable("Only one fixed product and variant are supported");
7029
7068
  if (effective.productType === "subscription" || [original, effective].some(
7030
7069
  (config) => !allowCollection && config.productLimit !== 1 || config.upgradeToSubscription.allow || !quantity3 && config.quantityInputs.enabled && (config.quantityInputs.minValue !== config.quantityInputs.maxValue || config.quantityInputs.defaultQuantity !== config.quantityInputs.minValue)
7031
7070
  ))
7032
- return "Only fixed one-time configuration is supported";
7071
+ return configurationUnavailable("Only fixed one-time configuration is supported");
7033
7072
  const product = data[0];
7034
7073
  const variant2 = product.variants[0];
7035
7074
  if (product.productGroupVariants.length || requiresOwnSellingPlan(product) || product.sellingPlanGroups?.some((group) => group.requiresSellingPlan))
7036
- return "A one-time variant without product groups or a required selling plan is required";
7075
+ return configurationUnavailable(
7076
+ "A one-time variant without product groups or a required selling plan is required"
7077
+ );
7037
7078
  if (!postPurchaseVariantAvailable(variant2)) return VARIANT_UNAVAILABLE;
7038
7079
  if (variant2.productId !== product.id) throw new Error("Product variant identity mismatch");
7039
7080
  };
7040
7081
 
7041
7082
  // src/server/preparePostPurchaseProduct.ts
7042
7083
  var prepareNormalizedProduct = (normalized, report, quantity3, allowCollection = false) => {
7043
- const reason = oneTimeUnavailable(normalized, allowCollection, quantity3);
7044
- if (reason) return { reason, status: "unsupported" };
7084
+ const unavailable = oneTimeUnavailable(normalized, allowCollection, quantity3);
7085
+ if (unavailable) return unsupportedPreparation(unavailable);
7045
7086
  const { effective } = normalized;
7046
7087
  return PreparedPostPurchaseProduct.parse({
7047
7088
  ...projectPostPurchaseProduct(normalized, report, quantity3),
@@ -7052,7 +7093,7 @@ var prepareNormalizedProduct = (normalized, report, quantity3, allowCollection =
7052
7093
  }
7053
7094
  });
7054
7095
  };
7055
- var preparePostPurchaseProduct = (settings, response, report) => prepareNormalizedProduct(normalizePostPurchaseProduct(settings, response, report), report);
7096
+ var preparePostPurchaseProduct = (settings, response, report) => publicPreparation(prepareNormalizedProduct(normalizePostPurchaseProduct(settings, response, report), report));
7056
7097
 
7057
7098
  // src/schema/postPurchaseProductView.ts
7058
7099
  import { z as z73 } from "zod";
@@ -7308,7 +7349,7 @@ var postPurchaseCardSections = (widgetId, {
7308
7349
  var preparePostPurchaseCardSections = (widgetId, settings, response, report) => postPurchaseCardSections(widgetId, normalizePostPurchaseProduct(settings, response, report), report);
7309
7350
 
7310
7351
  // src/server/selectPostPurchaseChoice.ts
7311
- var preferChoiceLevel = (preferred, other) => !isChoiceUnavailable(preferred) && isChoiceUnavailable(other) ? other : preferred;
7352
+ var preferChoiceLevel = (preferred, other) => preferred.level === "configuration" && other.level === "choice" ? other : preferred;
7312
7353
  var selectPostPurchaseChoice = (normalized, projected, requested, allowCollection, quantity3) => {
7313
7354
  const { data, effective } = normalized;
7314
7355
  const modes = [];
@@ -7323,7 +7364,7 @@ var selectPostPurchaseChoice = (normalized, projected, requested, allowCollectio
7323
7364
  choice: void 0,
7324
7365
  options: void 0,
7325
7366
  plan: void 0,
7326
- reason: preferChoiceLevel(
7367
+ unavailable: preferChoiceLevel(
7327
7368
  effective.productType === "one-time" ? oneTimeReason : subscriptionReason,
7328
7369
  effective.productType === "one-time" ? subscriptionReason : oneTimeReason
7329
7370
  )
@@ -7337,7 +7378,7 @@ var selectPostPurchaseChoice = (normalized, projected, requested, allowCollectio
7337
7378
  choice: bound,
7338
7379
  options: options2,
7339
7380
  plan: bound ? plan : void 0,
7340
- reason: bound ? void 0 : CHOICE_UNAVAILABLE
7381
+ unavailable: bound ? void 0 : CHOICE_UNAVAILABLE
7341
7382
  };
7342
7383
  };
7343
7384
 
@@ -7353,13 +7394,13 @@ var prepareNormalizedPostPurchaseView = (widgetId, normalized, report, choice, q
7353
7394
  const labels = { notAvailable: normalized.effective.language.global.notAvailable };
7354
7395
  const sts = quantity3?.sts === true;
7355
7396
  const marker = sts ? { sts: true } : {};
7356
- const preparation = !selected.choice ? { reason: selected.reason, status: "unsupported" } : selected.choice.mode === "subscription" ? prepareNormalizedSubscription(normalized, report, selected.plan, quantity3, allowCollection) : prepareNormalizedProduct(normalized, report, quantity3, allowCollection);
7397
+ const preparation = !selected.choice ? unsupportedPreparation(selected.unavailable) : selected.choice.mode === "subscription" ? prepareNormalizedSubscription(normalized, report, selected.plan, quantity3, allowCollection) : prepareNormalizedProduct(normalized, report, quantity3, allowCollection);
7357
7398
  if (preparation.status === "unsupported") {
7358
- const sections2 = isChoiceUnavailable(preparation.reason) ? postPurchaseCardSections(widgetId, normalized, report, sts) : null;
7399
+ const sections2 = preparation.level === "choice" ? postPurchaseCardSections(widgetId, normalized, report, sts) : null;
7359
7400
  return PostPurchaseProductView.parse({
7360
7401
  labels,
7361
7402
  planCatalog,
7362
- preparation,
7403
+ preparation: publicPreparation(preparation),
7363
7404
  purchaseOptions,
7364
7405
  sections: sections2,
7365
7406
  ...marker
@@ -8089,7 +8130,7 @@ var preparePostPurchaseCollectionGroup = (widgetId, settings, response, member,
8089
8130
  view: prepared ? { ...prepared.view, groupCatalog: prepared.groupCatalog } : {
8090
8131
  groupCatalog: group.catalog,
8091
8132
  labels: { notAvailable: normalized.effective.language.global.notAvailable },
8092
- preparation: { reason: GROUP_DEFAULT_UNAVAILABLE, status: "unsupported" },
8133
+ preparation: publicPreparation(unsupportedPreparation(GROUP_DEFAULT_UNAVAILABLE)),
8093
8134
  sections: postPurchaseCardSections(widgetId, normalized, report)
8094
8135
  }
8095
8136
  };
@@ -1,4 +1,5 @@
1
1
  import type { normalizePostPurchaseProduct } from '../server/normalizePostPurchaseProduct';
2
+ import { type PostPurchaseUnavailable } from '../server/postPurchaseUnavailableChoice';
2
3
  import type { PostPurchaseQuantity } from '../server/readPostPurchaseQuantity';
3
4
  /** The same eligibility rule drives advertised modes and their prepared terms. */
4
- export declare const oneTimeUnavailable: (normalized: ReturnType<typeof normalizePostPurchaseProduct>, allowCollection: boolean, quantity?: PostPurchaseQuantity) => "The chosen variant is unavailable" | "Only one fixed product and variant are supported" | "Only fixed one-time configuration is supported" | "A one-time variant without product groups or a required selling plan is required" | undefined;
5
+ export declare const oneTimeUnavailable: (normalized: ReturnType<typeof normalizePostPurchaseProduct>, allowCollection: boolean, quantity?: PostPurchaseQuantity) => PostPurchaseUnavailable | undefined;
@@ -1,4 +1,5 @@
1
1
  import type { PostPurchasePlanOption } from '../schema/postPurchasePlanCatalog';
2
2
  import type { normalizePostPurchaseProduct } from '../server/normalizePostPurchaseProduct';
3
+ import { type PostPurchaseUnavailable } from '../server/postPurchaseUnavailableChoice';
3
4
  import type { PostPurchaseQuantity } from '../server/readPostPurchaseQuantity';
4
- export declare const subscriptionUnavailable: (normalized: ReturnType<typeof normalizePostPurchaseProduct>, plan: PostPurchasePlanOption | undefined, allowCollection: boolean, quantity?: PostPurchaseQuantity) => "The chosen variant is unavailable" | "Subscription plan is unavailable" | "An eligible subscription plan with supported shipping and discount is required" | undefined;
5
+ export declare const subscriptionUnavailable: (normalized: ReturnType<typeof normalizePostPurchaseProduct>, plan: PostPurchasePlanOption | undefined, allowCollection: boolean, quantity?: PostPurchaseQuantity) => PostPurchaseUnavailable | undefined;
@@ -1,12 +1,45 @@
1
+ import type { PostPurchasePreparation } from '../schema/postPurchaseProduct';
1
2
  /**
2
- * Choice-level unavailability: the widget is readable and its card converts, but the choice the buyer is looking
3
- * at cannot be bought right now — its variant is sold out, its plan is gone, the requested mode is not offered,
4
- * or a group has no available default. Like cab-checkout's sold-out card, such a product still carries its tree
5
- * (values resolve blank; Pay shows `notAvailable`). Every other reason is configuration-level and keeps
6
- * `sections: null`.
3
+ * Why a post-purchase card cannot be bought, and at which level. Choice-level: the widget is readable and its
4
+ * card converts, but the choice the buyer is looking at cannot be bought right now — its variant is sold out,
5
+ * its plan is gone, the requested mode is not offered, or a group has no available default. Like cab-checkout's
6
+ * sold-out card, such a product still carries its tree (values resolve blank; Pay shows `notAvailable`).
7
+ * Configuration-level: the widget itself is unsupported, and the view keeps `sections: null`.
8
+ *
9
+ * The eligibility helpers return the level with the reason, so the tree-or-`null` decision reads `level`
10
+ * instead of matching reason text against a list — rewording a reason can no longer move it between levels.
7
11
  */
8
- export declare const VARIANT_UNAVAILABLE = "The chosen variant is unavailable";
9
- export declare const PLAN_UNAVAILABLE = "Subscription plan is unavailable";
10
- export declare const CHOICE_UNAVAILABLE = "Requested purchase choice is unavailable";
11
- export declare const GROUP_DEFAULT_UNAVAILABLE = "Post-purchase group default is unavailable";
12
- export declare const isChoiceUnavailable: (reason: string | undefined) => boolean;
12
+ export type PostPurchaseUnavailable = Readonly<{
13
+ level: 'choice' | 'configuration';
14
+ reason: string;
15
+ }>;
16
+ /** An unsupported preparation that still carries its level, for the server's own tree-or-`null` decision. */
17
+ export type LeveledPostPurchasePreparation = Exclude<PostPurchasePreparation, {
18
+ status: 'unsupported';
19
+ }> | (PostPurchaseUnavailable & {
20
+ status: 'unsupported';
21
+ });
22
+ export declare const configurationUnavailable: (reason: string) => PostPurchaseUnavailable;
23
+ export declare const VARIANT_UNAVAILABLE: Readonly<{
24
+ level: "choice" | "configuration";
25
+ reason: string;
26
+ }>;
27
+ export declare const PLAN_UNAVAILABLE: Readonly<{
28
+ level: "choice" | "configuration";
29
+ reason: string;
30
+ }>;
31
+ export declare const CHOICE_UNAVAILABLE: Readonly<{
32
+ level: "choice" | "configuration";
33
+ reason: string;
34
+ }>;
35
+ export declare const GROUP_DEFAULT_UNAVAILABLE: Readonly<{
36
+ level: "choice" | "configuration";
37
+ reason: string;
38
+ }>;
39
+ export declare const unsupportedPreparation: ({ level, reason }: PostPurchaseUnavailable) => {
40
+ level: "choice" | "configuration";
41
+ reason: string;
42
+ status: "unsupported";
43
+ };
44
+ /** The public preparation contract carries the reason only; the level is a server-side routing fact. */
45
+ export declare const publicPreparation: (preparation: LeveledPostPurchasePreparation) => PostPurchasePreparation;