@teleporthq/teleport-shared 0.43.67 → 0.43.68

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.
Files changed (87) hide show
  1. package/__tests__/utils/discount-engine.ts +1548 -0
  2. package/__tests__/utils/product-options.ts +2458 -0
  3. package/__tests__/utils/subscription-access.ts +37 -0
  4. package/dist/cjs/index.d.ts +9 -1
  5. package/dist/cjs/index.d.ts.map +1 -1
  6. package/dist/cjs/index.js +17 -1
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  9. package/dist/cjs/utils/cart-currency.d.ts +27 -0
  10. package/dist/cjs/utils/cart-currency.d.ts.map +1 -0
  11. package/dist/cjs/utils/cart-currency.js +31 -0
  12. package/dist/cjs/utils/cart-currency.js.map +1 -0
  13. package/dist/cjs/utils/cart-line-pricing.d.ts +25 -0
  14. package/dist/cjs/utils/cart-line-pricing.d.ts.map +1 -0
  15. package/dist/cjs/utils/cart-line-pricing.js +29 -0
  16. package/dist/cjs/utils/cart-line-pricing.js.map +1 -0
  17. package/dist/cjs/utils/discount-engine.d.ts +74 -0
  18. package/dist/cjs/utils/discount-engine.d.ts.map +1 -0
  19. package/dist/cjs/utils/discount-engine.js +435 -0
  20. package/dist/cjs/utils/discount-engine.js.map +1 -0
  21. package/dist/cjs/utils/product-options.d.ts +25 -0
  22. package/dist/cjs/utils/product-options.d.ts.map +1 -0
  23. package/dist/cjs/utils/product-options.js +1155 -0
  24. package/dist/cjs/utils/product-options.js.map +1 -0
  25. package/dist/cjs/utils/session-cookie-resolver.d.ts +85 -0
  26. package/dist/cjs/utils/session-cookie-resolver.d.ts.map +1 -0
  27. package/dist/cjs/utils/session-cookie-resolver.js +108 -0
  28. package/dist/cjs/utils/session-cookie-resolver.js.map +1 -0
  29. package/dist/cjs/utils/subscription-access.d.ts +36 -0
  30. package/dist/cjs/utils/subscription-access.d.ts.map +1 -0
  31. package/dist/cjs/utils/subscription-access.js +61 -0
  32. package/dist/cjs/utils/subscription-access.js.map +1 -0
  33. package/dist/cjs/utils/table-access.d.ts +114 -0
  34. package/dist/cjs/utils/table-access.d.ts.map +1 -0
  35. package/dist/cjs/utils/table-access.js +238 -0
  36. package/dist/cjs/utils/table-access.js.map +1 -0
  37. package/dist/cjs/utils/web-push.d.ts +43 -0
  38. package/dist/cjs/utils/web-push.d.ts.map +1 -0
  39. package/dist/cjs/utils/web-push.js +62 -0
  40. package/dist/cjs/utils/web-push.js.map +1 -0
  41. package/dist/esm/index.d.ts +9 -1
  42. package/dist/esm/index.d.ts.map +1 -1
  43. package/dist/esm/index.js +9 -1
  44. package/dist/esm/index.js.map +1 -1
  45. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  46. package/dist/esm/utils/cart-currency.d.ts +27 -0
  47. package/dist/esm/utils/cart-currency.d.ts.map +1 -0
  48. package/dist/esm/utils/cart-currency.js +27 -0
  49. package/dist/esm/utils/cart-currency.js.map +1 -0
  50. package/dist/esm/utils/cart-line-pricing.d.ts +25 -0
  51. package/dist/esm/utils/cart-line-pricing.d.ts.map +1 -0
  52. package/dist/esm/utils/cart-line-pricing.js +25 -0
  53. package/dist/esm/utils/cart-line-pricing.js.map +1 -0
  54. package/dist/esm/utils/discount-engine.d.ts +74 -0
  55. package/dist/esm/utils/discount-engine.d.ts.map +1 -0
  56. package/dist/esm/utils/discount-engine.js +431 -0
  57. package/dist/esm/utils/discount-engine.js.map +1 -0
  58. package/dist/esm/utils/product-options.d.ts +25 -0
  59. package/dist/esm/utils/product-options.d.ts.map +1 -0
  60. package/dist/esm/utils/product-options.js +1151 -0
  61. package/dist/esm/utils/product-options.js.map +1 -0
  62. package/dist/esm/utils/session-cookie-resolver.d.ts +85 -0
  63. package/dist/esm/utils/session-cookie-resolver.d.ts.map +1 -0
  64. package/dist/esm/utils/session-cookie-resolver.js +103 -0
  65. package/dist/esm/utils/session-cookie-resolver.js.map +1 -0
  66. package/dist/esm/utils/subscription-access.d.ts +36 -0
  67. package/dist/esm/utils/subscription-access.d.ts.map +1 -0
  68. package/dist/esm/utils/subscription-access.js +57 -0
  69. package/dist/esm/utils/subscription-access.js.map +1 -0
  70. package/dist/esm/utils/table-access.d.ts +114 -0
  71. package/dist/esm/utils/table-access.d.ts.map +1 -0
  72. package/dist/esm/utils/table-access.js +233 -0
  73. package/dist/esm/utils/table-access.js.map +1 -0
  74. package/dist/esm/utils/web-push.d.ts +43 -0
  75. package/dist/esm/utils/web-push.d.ts.map +1 -0
  76. package/dist/esm/utils/web-push.js +59 -0
  77. package/dist/esm/utils/web-push.js.map +1 -0
  78. package/package.json +3 -3
  79. package/src/index.ts +16 -0
  80. package/src/utils/cart-currency.ts +76 -0
  81. package/src/utils/cart-line-pricing.ts +151 -0
  82. package/src/utils/discount-engine.ts +437 -0
  83. package/src/utils/product-options.ts +1150 -0
  84. package/src/utils/session-cookie-resolver.ts +305 -0
  85. package/src/utils/subscription-access.ts +59 -0
  86. package/src/utils/table-access.ts +516 -0
  87. package/src/utils/web-push.ts +61 -0
@@ -0,0 +1,435 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DISCOUNT_CHOICE_CHANGED_EVENT = exports.DISCOUNT_CHOICE_STORAGE_KEY = exports.CUSTOMER_CHANGED_EVENT = exports.DISCOUNTS_REFRESH_EVENT = exports.GIFT_CARD_CHANGED_EVENT = exports.GIFT_CARD_STORAGE_KEY = exports.DISCOUNT_RULES_LIMIT = exports.DISCOUNTS_TABLE = exports.generateDiscountEngineHelperCode = void 0;
4
+ /**
5
+ * The discount engine — vouchers and automatic (code-less) discounts on a cart
6
+ * — as ES5 source for embedding into generated runtime code (the storefront
7
+ * provider, the `cart-get-total` workflow handler) that cannot import from
8
+ * this package.
9
+ *
10
+ * ⚠️ THIRD MIRROR. The same arithmetic exists in teleport-gui as
11
+ * `features/e-commerce/utils/discount-engine/discount-engine.ts` (the
12
+ * editor/canvas copy) and
13
+ * `features/workflows/templates/builders/ecommerce/discount-engine-script.ts`
14
+ * (the copy baked into the checkout workflows, whose text this file carries
15
+ * verbatim). All three must agree to the cent, or the discount the shopper
16
+ * sees, the discount the payment provider charges and the discount stored on
17
+ * the order will differ. `__tests__/utils/discount-engine.ts` runs this copy
18
+ * over a fixture table that is byte-identical with the GUI's parity spec.
19
+ *
20
+ * The rules, in one place:
21
+ * - discounts are taken on the GROSS eligible subtotal, gross unit price
22
+ * rounded per unit before multiplying;
23
+ * - rules stack sequentially on what is left of their eligible lines, each
24
+ * rounding once, clamped to that remainder;
25
+ * - combinable rules price together by priority, a combinable voucher last;
26
+ * an exclusive discount (`stackable: false`, a rule or the voucher) is
27
+ * priced on its own; when several sets save something the shopper's
28
+ * `choice` picks one and every one comes back as `options`, otherwise the
29
+ * set that saves the most wins — a tie going to an exclusive voucher, then
30
+ * exclusive rules by priority, then the combination; a voucher left out is
31
+ * rejected `not-stackable` when it would have applied alone, else for its
32
+ * own reason;
33
+ * - free shipping waives the fee once; a gift-card line is never eligible;
34
+ * - `legacy` reproduces the pre-engine voucher rule for an older checkout.
35
+ *
36
+ * Everything is prefixed `__de` and declared as `var … = function` EXPRESSIONS
37
+ * so the block can be spliced INTO a serialized handler body — the entry-point
38
+ * resolver would otherwise mistake a declared 2-argument function for the
39
+ * handler itself.
40
+ */
41
+ var generateDiscountEngineHelperCode = function () {
42
+ return [
43
+ 'var __deRound2 = function (n) { return Math.round(n * 100) / 100; };',
44
+ 'var __deSafe = function (n) {',
45
+ ' var x = typeof n === "number" ? n : parseFloat(n);',
46
+ ' return isFinite(x) ? x : 0;',
47
+ '};',
48
+ 'var __deIsArray = function (v) { return Object.prototype.toString.call(v) === "[object Array]"; };',
49
+ // Per-UNIT gross rounding before multiplying by quantity, as every money
50
+ // rule in the store does.
51
+ 'var __deGrossUnit = function (netUnitPrice, taxRatePercent) {',
52
+ ' var net = __deSafe(netUnitPrice);',
53
+ ' if (taxRatePercent > 0) { return __deRound2(net * (1 + taxRatePercent / 100)); }',
54
+ ' return net;',
55
+ '};',
56
+ // TEXT columns holding a JSON array (or, on a document store, a real array).
57
+ // Anything unreadable means "nothing listed", never an exception in a
58
+ // checkout.
59
+ 'var __deStringArray = function (raw) {',
60
+ ' var source = raw;',
61
+ ' if (typeof source === "string") {',
62
+ ' if (source === "") { return []; }',
63
+ ' try { source = JSON.parse(source); } catch (e) { return []; }',
64
+ ' }',
65
+ ' if (!__deIsArray(source)) { return []; }',
66
+ ' var out = [];',
67
+ ' for (var i = 0; i < source.length; i++) {',
68
+ ' if (source[i] === null || source[i] === undefined || source[i] === "") { continue; }',
69
+ ' out.push(String(source[i]));',
70
+ ' }',
71
+ ' return out;',
72
+ '};',
73
+ // Booleans arrive differently per driver (true / "t" / 1), so normalise once.
74
+ 'var __deBool = function (value, fallback) {',
75
+ ' if (typeof value === "boolean") { return value; }',
76
+ ' if (typeof value === "number") { return value !== 0; }',
77
+ ' if (typeof value === "string") {',
78
+ ' var s = value.toLowerCase();',
79
+ ' if (s === "true" || s === "t" || s === "1" || s === "yes") { return true; }',
80
+ ' if (s === "false" || s === "f" || s === "0" || s === "no") { return false; }',
81
+ ' }',
82
+ ' return fallback;',
83
+ '};',
84
+ 'var __deNullableNumber = function (value) {',
85
+ ' if (value === null || value === undefined || value === "") { return null; }',
86
+ ' var x = typeof value === "number" ? value : parseFloat(value);',
87
+ ' return isFinite(x) ? x : null;',
88
+ '};',
89
+ // Maps a `teleport_vouchers` or `teleport_discounts` row onto the rule shape.
90
+ // `discount_type` is kept as read: a type this build does not know prices as
91
+ // a no-op, never as a percentage.
92
+ 'var __deNormalizeRule = function (row, source) {',
93
+ ' if (!row || typeof row !== "object") { return null; }',
94
+ ' var priority = __deNullableNumber(row.priority);',
95
+ ' var code = row.code ? String(row.code) : "";',
96
+ ' return {',
97
+ ' id: row.id ? String(row.id) : "",',
98
+ ' source: source === "voucher" ? "voucher" : "automatic",',
99
+ ' code: code,',
100
+ ' name: row.name ? String(row.name) : code,',
101
+ ' discountType: row.discount_type ? String(row.discount_type) : "percentage",',
102
+ ' discountValue: __deSafe(row.discount_value),',
103
+ ' appliesToAllProducts: __deBool(row.applies_to_all_products, true),',
104
+ ' productIds: __deStringArray(row.product_ids),',
105
+ ' categoryIds: __deStringArray(row.category_ids),',
106
+ ' minOrderValue: __deNullableNumber(row.min_order_value),',
107
+ ' minQuantity: __deNullableNumber(row.min_quantity),',
108
+ ' firstOrderOnly: __deBool(row.first_order_only, false),',
109
+ ' buyQuantity: __deNullableNumber(row.buy_quantity),',
110
+ ' getQuantity: __deNullableNumber(row.get_quantity),',
111
+ ' stackable: __deBool(row.stackable, true),',
112
+ ' priority: priority === null ? 100 : priority',
113
+ ' };',
114
+ '};',
115
+ // Cart lines as `cart-get-items` / the provider hold them: NET prices,
116
+ // categories and the gift-card flag stamped by the add-to-cart node and the
117
+ // provider's hydration. A caller with server product rows overlays those.
118
+ 'var __deLinesFromCart = function (cartItems) {',
119
+ ' var lines = [];',
120
+ ' if (!__deIsArray(cartItems)) { return lines; }',
121
+ ' for (var i = 0; i < cartItems.length; i++) {',
122
+ ' var item = cartItems[i];',
123
+ ' if (!item || typeof item !== "object") { continue; }',
124
+ ' lines.push({',
125
+ ' productId: item.productId ? String(item.productId) : "",',
126
+ ' unitPrice: __deSafe(item.price),',
127
+ ' quantity: Math.max(0, Math.floor(__deSafe(item.quantity))),',
128
+ ' categoryIds: __deStringArray(item.categoryIds),',
129
+ ' isGiftCard: __deBool(item.isGiftCard, false),',
130
+ // A recurring line is billed by the provider at the plan price every
131
+ // cycle, so no discount ever touches it and no gift card pays for it.
132
+ ' isRecurring: __deBool(item.isRecurring, false),',
133
+ ' isDigital: __deBool(item.isDigital, false)',
134
+ ' });',
135
+ ' }',
136
+ ' return lines;',
137
+ '};',
138
+ 'var __deWorkingLines = function (lines, taxRatePercent) {',
139
+ ' var working = [];',
140
+ ' if (!__deIsArray(lines)) { return working; }',
141
+ ' for (var i = 0; i < lines.length; i++) {',
142
+ ' var line = lines[i];',
143
+ ' if (!line || typeof line !== "object") { continue; }',
144
+ ' var quantity = Math.max(0, Math.floor(__deSafe(line.quantity)));',
145
+ ' if (quantity === 0) { continue; }',
146
+ ' var unitGross = __deGrossUnit(line.unitPrice, taxRatePercent);',
147
+ ' working.push({',
148
+ ' productId: line.productId ? String(line.productId) : "",',
149
+ ' quantity: quantity,',
150
+ ' categoryIds: __deIsArray(line.categoryIds) ? line.categoryIds.map(String) : [],',
151
+ ' isGiftCard: line.isGiftCard === true,',
152
+ ' isRecurring: line.isRecurring === true,',
153
+ ' remaining: unitGross * quantity',
154
+ ' });',
155
+ ' }',
156
+ ' return working;',
157
+ '};',
158
+ 'var __deCloneLines = function (lines) {',
159
+ ' var out = [];',
160
+ ' for (var i = 0; i < lines.length; i++) {',
161
+ ' var l = lines[i];',
162
+ ' out.push({ productId: l.productId, quantity: l.quantity, categoryIds: l.categoryIds, isGiftCard: l.isGiftCard, isRecurring: l.isRecurring, remaining: l.remaining });',
163
+ ' }',
164
+ ' return out;',
165
+ '};',
166
+ 'var __deIsEligible = function (line, rule, legacy) {',
167
+ ' if (line.isGiftCard || line.isRecurring) { return false; }',
168
+ ' if (rule.appliesToAllProducts) { return true; }',
169
+ ' if (rule.productIds.indexOf(line.productId) !== -1) { return true; }',
170
+ ' if (legacy) { return false; }',
171
+ ' for (var i = 0; i < rule.categoryIds.length; i++) {',
172
+ ' if (line.categoryIds.indexOf(rule.categoryIds[i]) !== -1) { return true; }',
173
+ ' }',
174
+ ' return false;',
175
+ '};',
176
+ 'var __deCompareRules = function (a, b) {',
177
+ ' if (a.priority !== b.priority) { return a.priority < b.priority ? -1 : 1; }',
178
+ ' if (a.name !== b.name) { return a.name < b.name ? -1 : 1; }',
179
+ ' if (a.id !== b.id) { return a.id < b.id ? -1 : 1; }',
180
+ ' return 0;',
181
+ '};',
182
+ 'var __deRejected = function (reason, eligibleProductIds) {',
183
+ ' return { rejection: reason, goodsDiscount: 0, shippingDiscount: 0, eligibleProductIds: eligibleProductIds };',
184
+ '};',
185
+ /*
186
+ * Prices one rule against what is left of its eligible lines and, when it
187
+ * applies, takes its amount off those lines. Mirrors `evaluateRule` in the
188
+ * TypeScript twin field for field.
189
+ */
190
+ 'var __deEvaluateRule = function (rule, lines, params) {',
191
+ ' var eligible = [];',
192
+ ' var eligibleProductIds = [];',
193
+ ' var eligibleUnits = 0;',
194
+ ' for (var i = 0; i < lines.length; i++) {',
195
+ ' if (!__deIsEligible(lines[i], rule, params.legacy)) { continue; }',
196
+ ' eligible.push(lines[i]);',
197
+ ' eligibleUnits += lines[i].quantity;',
198
+ ' if (eligibleProductIds.indexOf(lines[i].productId) === -1) { eligibleProductIds.push(lines[i].productId); }',
199
+ ' }',
200
+ ' if (!params.legacy) {',
201
+ ' if (rule.minOrderValue !== null && params.cartGoodsGross < rule.minOrderValue) { return __deRejected("min-order", eligibleProductIds); }',
202
+ ' if (rule.minQuantity !== null && eligibleUnits < rule.minQuantity) { return __deRejected("min-quantity", eligibleProductIds); }',
203
+ ' if (rule.firstOrderOnly && params.isFirstOrder !== true) { return __deRejected("first-order", eligibleProductIds); }',
204
+ ' }',
205
+ ' if (rule.discountType === "free_shipping") {',
206
+ ' var waived = __deRound2(Math.max(0, __deSafe(params.shippingPrice)));',
207
+ ' if (params.shippingWaived || waived <= 0) { return __deRejected("no-op", eligibleProductIds); }',
208
+ ' return { rejection: "", goodsDiscount: 0, shippingDiscount: waived, eligibleProductIds: eligibleProductIds };',
209
+ ' }',
210
+ ' var base = 0;',
211
+ ' for (var b = 0; b < eligible.length; b++) { base += eligible[b].remaining; }',
212
+ ' var roundedBase = __deRound2(base);',
213
+ ' if (roundedBase <= 0) { return __deRejected("no-op", eligibleProductIds); }',
214
+ ' if (rule.discountType === "percentage" || rule.discountType === "fixed") {',
215
+ ' var value = Math.max(0, __deSafe(rule.discountValue));',
216
+ ' var raw = rule.discountType === "percentage" ? (base * Math.min(value, 100)) / 100 : value;',
217
+ ' var amount = Math.min(__deRound2(raw), roundedBase);',
218
+ ' if (amount <= 0) { return __deRejected("no-op", eligibleProductIds); }',
219
+ ' for (var p = 0; p < eligible.length; p++) {',
220
+ ' eligible[p].remaining -= (eligible[p].remaining / base) * amount;',
221
+ ' }',
222
+ ' return { rejection: "", goodsDiscount: amount, shippingDiscount: 0, eligibleProductIds: eligibleProductIds };',
223
+ ' }',
224
+ ' if (rule.discountType === "buy_x_get_y" && !params.legacy) {',
225
+ ' var buy = Math.floor(__deSafe(rule.buyQuantity));',
226
+ ' var get = Math.floor(__deSafe(rule.getQuantity));',
227
+ ' if (buy < 1 || get < 1) { return __deRejected("no-op", eligibleProductIds); }',
228
+ ' var units = [];',
229
+ ' for (var u = 0; u < eligible.length; u++) {',
230
+ ' var unitValue = eligible[u].remaining / eligible[u].quantity;',
231
+ ' for (var q = 0; q < eligible[u].quantity; q++) {',
232
+ ' units.push({ line: eligible[u], value: unitValue, order: units.length });',
233
+ ' }',
234
+ ' }',
235
+ ' units.sort(function (a, b) {',
236
+ ' if (a.value !== b.value) { return a.value < b.value ? -1 : 1; }',
237
+ ' return a.order - b.order;',
238
+ ' });',
239
+ ' var freeUnits = Math.floor(units.length / (buy + get)) * get;',
240
+ ' var bogo = 0;',
241
+ ' for (var f = 0; f < freeUnits; f++) {',
242
+ ' bogo += units[f].value;',
243
+ ' units[f].line.remaining -= units[f].value;',
244
+ ' }',
245
+ ' bogo = Math.min(__deRound2(bogo), roundedBase);',
246
+ ' if (bogo <= 0) { return __deRejected("no-op", eligibleProductIds); }',
247
+ ' return { rejection: "", goodsDiscount: bogo, shippingDiscount: 0, eligibleProductIds: eligibleProductIds };',
248
+ ' }',
249
+ ' return __deRejected("no-op", eligibleProductIds);',
250
+ '};',
251
+ /*
252
+ * Prices one set of discounts that may share the order, sequentially on a
253
+ * copy of the lines. Mirrors `priceCandidate` in the TypeScript twin.
254
+ */
255
+ 'var __dePriceCandidate = function (candidate, lines, params) {',
256
+ ' var working = __deCloneLines(lines);',
257
+ ' var applied = [];',
258
+ ' var shippingWaived = false;',
259
+ ' var automaticGoods = 0;',
260
+ ' var automaticShipping = 0;',
261
+ ' var voucherGoods = 0;',
262
+ ' var voucherShipping = 0;',
263
+ ' var holdsVoucher = false;',
264
+ ' var voucherApplied = false;',
265
+ ' var voucherRejection = "";',
266
+ ' var eligibleProductIds = [];',
267
+ ' for (var c = 0; c < candidate.length; c++) {',
268
+ ' var rule = candidate[c];',
269
+ ' var isVoucher = rule.source === "voucher";',
270
+ ' var evaluation = __deEvaluateRule(rule, working, {',
271
+ ' cartGoodsGross: params.cartGoodsGross, shippingPrice: params.shippingPrice, shippingWaived: shippingWaived, isFirstOrder: params.isFirstOrder, legacy: params.legacy',
272
+ ' });',
273
+ ' if (isVoucher) { holdsVoucher = true; eligibleProductIds = evaluation.eligibleProductIds; }',
274
+ ' if (evaluation.rejection !== "") {',
275
+ ' if (isVoucher) { voucherRejection = evaluation.rejection; }',
276
+ ' continue;',
277
+ ' }',
278
+ ' applied.push({ id: rule.id, source: isVoucher ? "voucher" : "automatic", code: isVoucher ? rule.code : "", name: rule.name, goodsDiscount: evaluation.goodsDiscount, shippingDiscount: evaluation.shippingDiscount, eligibleProductIds: evaluation.eligibleProductIds });',
279
+ ' if (isVoucher) {',
280
+ ' voucherApplied = true;',
281
+ ' voucherGoods += evaluation.goodsDiscount;',
282
+ ' voucherShipping += evaluation.shippingDiscount;',
283
+ ' } else {',
284
+ ' automaticGoods += evaluation.goodsDiscount;',
285
+ ' automaticShipping += evaluation.shippingDiscount;',
286
+ ' }',
287
+ ' if (evaluation.shippingDiscount > 0) { shippingWaived = true; }',
288
+ ' }',
289
+ ' return {',
290
+ ' applied: applied,',
291
+ ' automaticGoods: automaticGoods,',
292
+ ' automaticShipping: automaticShipping,',
293
+ ' voucherGoods: voucherGoods,',
294
+ ' voucherShipping: voucherShipping,',
295
+ ' holdsVoucher: holdsVoucher,',
296
+ ' voucherApplied: voucherApplied,',
297
+ ' voucherRejection: voucherRejection,',
298
+ ' eligibleProductIds: eligibleProductIds,',
299
+ ' total: __deRound2(__deRound2(automaticGoods + voucherGoods) + __deRound2(Math.min(params.shippingPrice, automaticShipping + voucherShipping)))',
300
+ ' };',
301
+ '};',
302
+ /*
303
+ * THE rule. Mirrors `resolveCartDiscounts`: every set of discounts that may
304
+ * share the order is priced on its own — each exclusive discount alone, the
305
+ * combinable ones together. When several sets save something the shopper's
306
+ * `choice` picks one and every one is offered back as `options`; otherwise
307
+ * the set that saves the most wins, a tie going to the set listed first.
308
+ */
309
+ 'var __deDescribeOption = function (applied) {',
310
+ ' var names = [];',
311
+ ' for (var d = 0; d < applied.length; d++) { names.push(applied[d].source === "voucher" ? "Code " + applied[d].code : applied[d].name); }',
312
+ ' return names.join(", ");',
313
+ '};',
314
+ 'var __deResolve = function (input) {',
315
+ ' var legacy = input.legacy === true;',
316
+ ' var taxRatePercent = __deSafe(input.taxRatePercent);',
317
+ ' var lines = __deWorkingLines(input.lines, taxRatePercent);',
318
+ ' var shippingPrice = Math.max(0, __deSafe(input.shippingPrice));',
319
+ ' var isFirstOrder = input.customer && typeof input.customer.isFirstOrder === "boolean" ? input.customer.isFirstOrder : null;',
320
+ ' var cartGoodsGross = 0;',
321
+ ' for (var g = 0; g < lines.length; g++) { if (!lines[g].isGiftCard && !lines[g].isRecurring) { cartGoodsGross += lines[g].remaining; } }',
322
+ ' cartGoodsGross = __deRound2(cartGoodsGross);',
323
+ ' var voucher = input.voucher || null;',
324
+ ' var rules = legacy || !__deIsArray(input.rules) ? [] : input.rules.slice().sort(__deCompareRules);',
325
+ ' var params = { cartGoodsGross: cartGoodsGross, shippingPrice: shippingPrice, isFirstOrder: isFirstOrder, legacy: legacy };',
326
+ ' var candidates = [];',
327
+ ' var combination = [];',
328
+ ' if (voucher && !legacy && !voucher.stackable) { candidates.push({ key: "voucher", rules: [voucher] }); }',
329
+ ' for (var r = 0; r < rules.length; r++) {',
330
+ ' if (rules[r].stackable) { combination.push(rules[r]); } else { candidates.push({ key: rules[r].id, rules: [rules[r]] }); }',
331
+ ' }',
332
+ ' if (voucher && (legacy || voucher.stackable)) { combination.push(voucher); }',
333
+ ' candidates.push({ key: "combination", rules: combination });',
334
+ ' var priced = [];',
335
+ ' for (var k = 0; k < candidates.length; k++) { priced.push({ key: candidates[k].key, pricing: __dePriceCandidate(candidates[k].rules, lines, params) }); }',
336
+ ' var winner = priced[0];',
337
+ ' for (var w = 1; w < priced.length; w++) { if (priced[w].pricing.total > winner.pricing.total) { winner = priced[w]; } }',
338
+ ' var choosable = [];',
339
+ ' for (var s = 0; s < priced.length; s++) { if (priced[s].pricing.total > 0) { choosable.push(priced[s]); } }',
340
+ ' var options = [];',
341
+ ' var choice = "";',
342
+ ' if (choosable.length > 1) {',
343
+ ' for (var p = 0; p < choosable.length; p++) { if (choosable[p].key === input.choice) { winner = choosable[p]; } }',
344
+ ' choice = winner.key;',
345
+ ' for (var o = 0; o < choosable.length; o++) {',
346
+ ' options.push({ key: choosable[o].key, label: __deDescribeOption(choosable[o].pricing.applied), saving: choosable[o].pricing.total, selected: choosable[o] === winner });',
347
+ ' }',
348
+ ' }',
349
+ ' var best = winner.pricing;',
350
+ ' var voucherRejection = best.voucherRejection;',
351
+ ' var eligibleProductIds = best.eligibleProductIds;',
352
+ ' if (voucher && !best.holdsVoucher) {',
353
+ ' var alone = __dePriceCandidate([voucher], lines, params);',
354
+ ' voucherRejection = alone.voucherApplied ? "not-stackable" : alone.voucherRejection;',
355
+ ' eligibleProductIds = alone.eligibleProductIds;',
356
+ ' }',
357
+ ' return {',
358
+ ' applied: best.applied,',
359
+ ' goodsDiscount: __deRound2(best.automaticGoods + best.voucherGoods),',
360
+ ' shippingDiscount: __deRound2(Math.min(shippingPrice, best.automaticShipping + best.voucherShipping)),',
361
+ ' automaticGoodsDiscount: __deRound2(best.automaticGoods),',
362
+ ' automaticShippingDiscount: __deRound2(best.automaticShipping),',
363
+ ' voucherGoodsDiscount: __deRound2(best.voucherGoods),',
364
+ ' voucherShippingDiscount: __deRound2(best.voucherShipping),',
365
+ ' voucherApplied: best.voucherApplied,',
366
+ ' voucherRejection: voucherRejection,',
367
+ ' eligibleProductIds: eligibleProductIds,',
368
+ ' options: options,',
369
+ ' choice: choice',
370
+ ' };',
371
+ '};',
372
+ // The provider's published minimums, in major units; default 0.50.
373
+ 'var __deMinimumCharge = function (currency) {',
374
+ ' var table = { USD: 0.5, EUR: 0.5, GBP: 0.3, AUD: 0.5, CAD: 0.5, NZD: 0.5, SGD: 0.5, CHF: 0.5, BRL: 0.5, INR: 0.5, AED: 2, BGN: 1, CZK: 15, DKK: 2.5, HKD: 4, HUF: 175, JPY: 50, MXN: 10, MYR: 2, NOK: 3, PLN: 2, RON: 2, SEK: 3, THB: 10 };',
375
+ ' var code = String(currency || "").toUpperCase();',
376
+ ' return typeof table[code] === "number" ? table[code] : 0.5;',
377
+ '};',
378
+ // How much of an order a gift card pays. Mirrors `resolveGiftCardTender`.
379
+ 'var __deTender = function (input) {',
380
+ ' var balance = Math.max(0, __deSafe(input.balance));',
381
+ ' var total = Math.max(0, __deRound2(__deSafe(input.total)));',
382
+ // A recurring cart whose first charge is a free trial has nothing for a
383
+ // card to pay, and neither has one whose checkout cannot hand the provider
384
+ // a reduced first charge; otherwise the card pays part of the first charge
385
+ // only, and the provider keeps at least its minimum to collect the payment
386
+ // method that bills every later cycle.
387
+ ' var recurringRefused = input.hasRecurringLines && !input.recurringTenderAllowed;',
388
+ ' if (input.hasGiftCardLines || input.hasRecurringTrial || recurringRefused || balance <= 0 || total <= 0) { return { applied: 0, amountDue: total }; }',
389
+ ' var minimum = __deMinimumCharge(input.currency);',
390
+ ' var applied = Math.min(balance, total);',
391
+ ' if (input.hasRecurringLines) { applied = Math.min(applied, Math.max(0, __deRound2(total - minimum))); }',
392
+ ' var remaining = __deRound2(total - applied);',
393
+ ' if (remaining > 0 && remaining < minimum) { applied = Math.max(0, __deRound2(total - minimum)); }',
394
+ ' applied = __deRound2(applied);',
395
+ ' return { applied: applied, amountDue: __deRound2(total - applied) };',
396
+ '};',
397
+ ].join('\n');
398
+ };
399
+ exports.generateDiscountEngineHelperCode = generateDiscountEngineHelperCode;
400
+ /** The automatic-discount rules table the storefront reads. */
401
+ exports.DISCOUNTS_TABLE = 'teleport_discounts';
402
+ /**
403
+ * How many live automatic rules any reader loads — the storefront feed, the
404
+ * place-order workflow and the editor — each ordered the way the engine
405
+ * applies them (priority, name, id), so all three price the same set. The
406
+ * editor refuses a live rule past it.
407
+ */
408
+ exports.DISCOUNT_RULES_LIMIT = 500;
409
+ /**
410
+ * The applied gift card the checkout keeps beside the cart — display data
411
+ * returned by the Apply Gift Card workflow (`{ id, last4, balance, currency,
412
+ * checkedAt }`), never the full code, never trusted for money. ⚠️
413
+ * `cart-get-total` and `cart-clear` spell the same key out as a literal — a
414
+ * serialized handler has no module scope to import it from.
415
+ */
416
+ exports.GIFT_CARD_STORAGE_KEY = 'workflow_gift_card';
417
+ /** Broadcast when the applied gift card changes. */
418
+ exports.GIFT_CARD_CHANGED_EVENT = 'teleport:gift-card-changed';
419
+ /**
420
+ * Broadcast by the place-order workflow when the server priced the order
421
+ * differently from the summary, so the provider re-reads rules and customer.
422
+ */
423
+ exports.DISCOUNTS_REFRESH_EVENT = 'teleport:discounts-refresh';
424
+ /** Broadcast once the checkout page-load workflow has written `customer`. */
425
+ exports.CUSTOMER_CHANGED_EVENT = 'teleport:customer-changed';
426
+ /**
427
+ * The localStorage key holding the set of discounts the shopper chose when
428
+ * several could apply but not together (a `DiscountOption.key`: an exclusive
429
+ * rule's id, `voucher`, or `combination`). ⚠️ `cart-get-total` and `cart-clear`
430
+ * spell the key out as a literal — a serialized handler has no module scope.
431
+ */
432
+ exports.DISCOUNT_CHOICE_STORAGE_KEY = 'workflow_discount_choice';
433
+ /** Broadcast when the shopper's discount choice changes. */
434
+ exports.DISCOUNT_CHOICE_CHANGED_EVENT = 'teleport:discount-choice-changed';
435
+ //# sourceMappingURL=discount-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discount-engine.js","sourceRoot":"","sources":["../../../src/utils/discount-engine.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACI,IAAM,gCAAgC,GAAG;IAC9C,OAAA;QACE,sEAAsE;QACtE,+BAA+B;QAC/B,sDAAsD;QACtD,+BAA+B;QAC/B,IAAI;QACJ,oGAAoG;QACpG,yEAAyE;QACzE,0BAA0B;QAC1B,+DAA+D;QAC/D,qCAAqC;QACrC,oFAAoF;QACpF,eAAe;QACf,IAAI;QACJ,6EAA6E;QAC7E,sEAAsE;QACtE,YAAY;QACZ,wCAAwC;QACxC,qBAAqB;QACrB,qCAAqC;QACrC,uCAAuC;QACvC,mEAAmE;QACnE,KAAK;QACL,4CAA4C;QAC5C,iBAAiB;QACjB,6CAA6C;QAC7C,0FAA0F;QAC1F,kCAAkC;QAClC,KAAK;QACL,eAAe;QACf,IAAI;QACJ,8EAA8E;QAC9E,6CAA6C;QAC7C,qDAAqD;QACrD,0DAA0D;QAC1D,oCAAoC;QACpC,kCAAkC;QAClC,iFAAiF;QACjF,kFAAkF;QAClF,KAAK;QACL,oBAAoB;QACpB,IAAI;QACJ,6CAA6C;QAC7C,+EAA+E;QAC/E,kEAAkE;QAClE,kCAAkC;QAClC,IAAI;QACJ,8EAA8E;QAC9E,6EAA6E;QAC7E,kCAAkC;QAClC,kDAAkD;QAClD,yDAAyD;QACzD,oDAAoD;QACpD,gDAAgD;QAChD,YAAY;QACZ,uCAAuC;QACvC,6DAA6D;QAC7D,iBAAiB;QACjB,+CAA+C;QAC/C,iFAAiF;QACjF,kDAAkD;QAClD,wEAAwE;QACxE,mDAAmD;QACnD,qDAAqD;QACrD,6DAA6D;QAC7D,wDAAwD;QACxD,4DAA4D;QAC5D,wDAAwD;QACxD,wDAAwD;QACxD,+CAA+C;QAC/C,kDAAkD;QAClD,MAAM;QACN,IAAI;QACJ,uEAAuE;QACvE,4EAA4E;QAC5E,0EAA0E;QAC1E,gDAAgD;QAChD,mBAAmB;QACnB,kDAAkD;QAClD,gDAAgD;QAChD,8BAA8B;QAC9B,0DAA0D;QAC1D,kBAAkB;QAClB,gEAAgE;QAChE,wCAAwC;QACxC,mEAAmE;QACnE,uDAAuD;QACvD,qDAAqD;QACrD,qEAAqE;QACrE,sEAAsE;QACtE,uDAAuD;QACvD,kDAAkD;QAClD,SAAS;QACT,KAAK;QACL,iBAAiB;QACjB,IAAI;QACJ,2DAA2D;QAC3D,qBAAqB;QACrB,gDAAgD;QAChD,4CAA4C;QAC5C,0BAA0B;QAC1B,0DAA0D;QAC1D,sEAAsE;QACtE,uCAAuC;QACvC,oEAAoE;QACpE,oBAAoB;QACpB,gEAAgE;QAChE,2BAA2B;QAC3B,uFAAuF;QACvF,6CAA6C;QAC7C,+CAA+C;QAC/C,uCAAuC;QACvC,SAAS;QACT,KAAK;QACL,mBAAmB;QACnB,IAAI;QACJ,yCAAyC;QACzC,iBAAiB;QACjB,4CAA4C;QAC5C,uBAAuB;QACvB,2KAA2K;QAC3K,KAAK;QACL,eAAe;QACf,IAAI;QACJ,sDAAsD;QACtD,8DAA8D;QAC9D,mDAAmD;QACnD,wEAAwE;QACxE,iCAAiC;QACjC,uDAAuD;QACvD,gFAAgF;QAChF,KAAK;QACL,iBAAiB;QACjB,IAAI;QACJ,0CAA0C;QAC1C,+EAA+E;QAC/E,+DAA+D;QAC/D,uDAAuD;QACvD,aAAa;QACb,IAAI;QACJ,4DAA4D;QAC5D,gHAAgH;QAChH,IAAI;QACJ;;;;WAIG;QACH,yDAAyD;QACzD,sBAAsB;QACtB,gCAAgC;QAChC,0BAA0B;QAC1B,4CAA4C;QAC5C,uEAAuE;QACvE,8BAA8B;QAC9B,yCAAyC;QACzC,iHAAiH;QACjH,KAAK;QACL,yBAAyB;QACzB,8IAA8I;QAC9I,qIAAqI;QACrI,0HAA0H;QAC1H,KAAK;QACL,gDAAgD;QAChD,2EAA2E;QAC3E,qGAAqG;QACrG,mHAAmH;QACnH,KAAK;QACL,iBAAiB;QACjB,gFAAgF;QAChF,uCAAuC;QACvC,+EAA+E;QAC/E,8EAA8E;QAC9E,4DAA4D;QAC5D,iGAAiG;QACjG,0DAA0D;QAC1D,4EAA4E;QAC5E,iDAAiD;QACjD,yEAAyE;QACzE,OAAO;QACP,mHAAmH;QACnH,KAAK;QACL,gEAAgE;QAChE,uDAAuD;QACvD,uDAAuD;QACvD,mFAAmF;QACnF,qBAAqB;QACrB,iDAAiD;QACjD,qEAAqE;QACrE,wDAAwD;QACxD,mFAAmF;QACnF,SAAS;QACT,OAAO;QACP,kCAAkC;QAClC,uEAAuE;QACvE,iCAAiC;QACjC,SAAS;QACT,mEAAmE;QACnE,mBAAmB;QACnB,2CAA2C;QAC3C,+BAA+B;QAC/B,kDAAkD;QAClD,OAAO;QACP,qDAAqD;QACrD,0EAA0E;QAC1E,iHAAiH;QACjH,KAAK;QACL,qDAAqD;QACrD,IAAI;QACJ;;;WAGG;QACH,gEAAgE;QAChE,wCAAwC;QACxC,qBAAqB;QACrB,+BAA+B;QAC/B,2BAA2B;QAC3B,8BAA8B;QAC9B,yBAAyB;QACzB,4BAA4B;QAC5B,6BAA6B;QAC7B,+BAA+B;QAC/B,8BAA8B;QAC9B,gCAAgC;QAChC,gDAAgD;QAChD,8BAA8B;QAC9B,gDAAgD;QAChD,wDAAwD;QACxD,4KAA4K;QAC5K,SAAS;QACT,iGAAiG;QACjG,wCAAwC;QACxC,mEAAmE;QACnE,iBAAiB;QACjB,OAAO;QACP,+QAA+Q;QAC/Q,sBAAsB;QACtB,8BAA8B;QAC9B,iDAAiD;QACjD,uDAAuD;QACvD,cAAc;QACd,mDAAmD;QACnD,yDAAyD;QACzD,OAAO;QACP,qEAAqE;QACrE,KAAK;QACL,YAAY;QACZ,uBAAuB;QACvB,qCAAqC;QACrC,2CAA2C;QAC3C,iCAAiC;QACjC,uCAAuC;QACvC,iCAAiC;QACjC,qCAAqC;QACrC,yCAAyC;QACzC,6CAA6C;QAC7C,oJAAoJ;QACpJ,MAAM;QACN,IAAI;QACJ;;;;;;WAMG;QACH,+CAA+C;QAC/C,mBAAmB;QACnB,2IAA2I;QAC3I,4BAA4B;QAC5B,IAAI;QACJ,sCAAsC;QACtC,uCAAuC;QACvC,wDAAwD;QACxD,8DAA8D;QAC9D,mEAAmE;QACnE,+HAA+H;QAC/H,2BAA2B;QAC3B,2IAA2I;QAC3I,gDAAgD;QAChD,wCAAwC;QACxC,sGAAsG;QACtG,8HAA8H;QAC9H,wBAAwB;QACxB,yBAAyB;QACzB,4GAA4G;QAC5G,4CAA4C;QAC5C,gIAAgI;QAChI,KAAK;QACL,gFAAgF;QAChF,gEAAgE;QAChE,oBAAoB;QACpB,6JAA6J;QAC7J,2BAA2B;QAC3B,2HAA2H;QAC3H,uBAAuB;QACvB,+GAA+G;QAC/G,qBAAqB;QACrB,oBAAoB;QACpB,+BAA+B;QAC/B,sHAAsH;QACtH,0BAA0B;QAC1B,kDAAkD;QAClD,gLAAgL;QAChL,OAAO;QACP,KAAK;QACL,8BAA8B;QAC9B,iDAAiD;QACjD,qDAAqD;QACrD,wCAAwC;QACxC,+DAA+D;QAC/D,yFAAyF;QACzF,oDAAoD;QACpD,KAAK;QACL,YAAY;QACZ,4BAA4B;QAC5B,yEAAyE;QACzE,2GAA2G;QAC3G,8DAA8D;QAC9D,oEAAoE;QACpE,0DAA0D;QAC1D,gEAAgE;QAChE,0CAA0C;QAC1C,yCAAyC;QACzC,6CAA6C;QAC7C,uBAAuB;QACvB,oBAAoB;QACpB,MAAM;QACN,IAAI;QACJ,mEAAmE;QACnE,+CAA+C;QAC/C,+OAA+O;QAC/O,oDAAoD;QACpD,+DAA+D;QAC/D,IAAI;QACJ,0EAA0E;QAC1E,qCAAqC;QACrC,uDAAuD;QACvD,+DAA+D;QAC/D,wEAAwE;QACxE,2EAA2E;QAC3E,2EAA2E;QAC3E,2EAA2E;QAC3E,uCAAuC;QACvC,oFAAoF;QACpF,yJAAyJ;QACzJ,oDAAoD;QACpD,2CAA2C;QAC3C,2GAA2G;QAC3G,gDAAgD;QAChD,qGAAqG;QACrG,kCAAkC;QAClC,wEAAwE;QACxE,IAAI;KACL,CAAC,IAAI,CAAC,IAAI,CAAC;AAnWZ,CAmWY,CAAA;AApWD,QAAA,gCAAgC,oCAoW/B;AAEd,+DAA+D;AAClD,QAAA,eAAe,GAAG,oBAAoB,CAAA;AAEnD;;;;;GAKG;AACU,QAAA,oBAAoB,GAAG,GAAG,CAAA;AAEvC;;;;;;GAMG;AACU,QAAA,qBAAqB,GAAG,oBAAoB,CAAA;AAEzD,oDAAoD;AACvC,QAAA,uBAAuB,GAAG,4BAA4B,CAAA;AAEnE;;;GAGG;AACU,QAAA,uBAAuB,GAAG,4BAA4B,CAAA;AAEnE,6EAA6E;AAChE,QAAA,sBAAsB,GAAG,2BAA2B,CAAA;AAEjE;;;;;GAKG;AACU,QAAA,2BAA2B,GAAG,0BAA0B,CAAA;AAErE,4DAA4D;AAC/C,QAAA,6BAA6B,GAAG,kCAAkC,CAAA"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Product options as ES5 source, for generated runtime code that cannot import
3
+ * from this package: the data-source product transform, the storefront cart
4
+ * provider, and every workflow script that prices a configuration.
5
+ *
6
+ * ⚠️ THIRD MIRROR. The same rules exist in teleport-gui as
7
+ * `features/e-commerce/utils/product-options/` (the editor/canvas TypeScript)
8
+ * and `features/workflows/templates/builders/ecommerce/product-options-script.ts`
9
+ * (`PRODUCT_OPTIONS_HELPERS`, whose text this function returns verbatim). All
10
+ * three must agree to the cent, or the price on the product page, the price
11
+ * added to the cart and the price stored on the order will differ.
12
+ * `__tests__/utils/product-options.ts` runs this copy over a fixture table that
13
+ * is byte-identical with the GUI's parity spec.
14
+ *
15
+ * The block: `var` / `function` only, names prefixed `__po`; no backticks, no
16
+ * dollar-brace, no backslash, never `params` followed by a dot; no `__po`
17
+ * function with exactly two parameters (the handler-entry resolver in
18
+ * `teleport-plugin-next-workflows/src/nodes/types.ts` picks the first
19
+ * two-parameter declaration); wrapped in the sentinel comments below so an
20
+ * export-time refresh can replace it.
21
+ */
22
+ export declare const PRODUCT_OPTIONS_HELPERS_OPENING = "/* tq:po-helpers:start v1 */";
23
+ export declare const PRODUCT_OPTIONS_HELPERS_CLOSING = "/* tq:po-helpers:end */";
24
+ export declare const generateProductOptionsHelperCode: () => string;
25
+ //# sourceMappingURL=product-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-options.d.ts","sourceRoot":"","sources":["../../../src/utils/product-options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,+BAA+B,iCAAiC,CAAA;AAC7E,eAAO,MAAM,+BAA+B,4BAA4B,CAAA;AAExE,eAAO,MAAM,gCAAgC,QAAO,MAqmCtC,CAAA"}