@zoxllc/shopify-checkout-extensions 0.2.0 → 0.2.2
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/EXAMPLES.md +84 -10
- package/dist/common/CampaignFactory.d.ts.map +1 -1
- package/dist/common/CampaignFactory.js +1 -1
- package/dist/lineItem/MultiTierDiscount.d.ts +19 -10
- package/dist/lineItem/MultiTierDiscount.d.ts.map +1 -1
- package/dist/lineItem/MultiTierDiscount.js +93 -61
- package/package.json +1 -1
package/EXAMPLES.md
CHANGED
|
@@ -360,9 +360,9 @@ export function run(input) {
|
|
|
360
360
|
|
|
361
361
|
---
|
|
362
362
|
|
|
363
|
-
## Example 11: Multi-Tier Gift with Purchase
|
|
363
|
+
## Example 11: Multi-Tier Gift with Purchase (Cumulative)
|
|
364
364
|
|
|
365
|
-
**Use Case:** Offer tiered gifts based on cart spend threshold
|
|
365
|
+
**Use Case:** Offer cumulative tiered gifts based on cart spend threshold. All qualifying tiers are applied (stackTiers: true).
|
|
366
366
|
|
|
367
367
|
```json
|
|
368
368
|
{
|
|
@@ -405,23 +405,94 @@ export function run(input) {
|
|
|
405
405
|
"label": "Free Cosmic Drift Daily"
|
|
406
406
|
}
|
|
407
407
|
],
|
|
408
|
-
["meta-exclude-gift"]
|
|
408
|
+
["meta-exclude-gift"],
|
|
409
|
+
true
|
|
409
410
|
]
|
|
410
411
|
}
|
|
411
412
|
```
|
|
412
413
|
|
|
413
|
-
**How It Works:**
|
|
414
|
+
**How It Works (stackTiers: true):**
|
|
414
415
|
- Customer adds items to cart totaling $150
|
|
415
416
|
- Cart subtotal is calculated (excluding items tagged "meta-exclude-gift")
|
|
416
|
-
-
|
|
417
|
-
- $
|
|
418
|
-
- $100 tier
|
|
419
|
-
-
|
|
420
|
-
-
|
|
417
|
+
- All qualifying tiers are found:
|
|
418
|
+
- $75 tier qualifies ($150 >= $75)
|
|
419
|
+
- $100 tier qualifies ($150 >= $100)
|
|
420
|
+
- $250 tier doesn't qualify ($150 < $250)
|
|
421
|
+
- Products get 100% discount:
|
|
422
|
+
- "6638547533896" (Colorwheel F&F) - from $75 tier
|
|
423
|
+
- "6640591011912" (Goldie Mystery ZOX) - from $100 tier
|
|
424
|
+
- Customer receives BOTH gifts (cumulative/stacking)
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Example 12: Multi-Tier Bundle Upgrade (Highest Only)
|
|
429
|
+
|
|
430
|
+
**Use Case:** Offer tiered bundle upgrades where higher spend gets a better bundle product. Only the highest qualifying tier is applied (stackTiers: false).
|
|
431
|
+
|
|
432
|
+
```json
|
|
433
|
+
{
|
|
434
|
+
"__type": "MultiTierDiscount",
|
|
435
|
+
"label": "Spring Bundle Upgrade",
|
|
436
|
+
"active": true,
|
|
437
|
+
"description": "Spend more, get a better bundle!",
|
|
438
|
+
"inputs": [
|
|
439
|
+
":all",
|
|
440
|
+
[],
|
|
441
|
+
[
|
|
442
|
+
{
|
|
443
|
+
"threshold": 75,
|
|
444
|
+
"discount": {
|
|
445
|
+
"__type": "PercentageDiscount",
|
|
446
|
+
"inputs": [100, "Free Starter Bundle"]
|
|
447
|
+
},
|
|
448
|
+
"productIds": ["1111111111111"],
|
|
449
|
+
"itemsToDiscount": 1,
|
|
450
|
+
"label": "Free Starter Bundle"
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"threshold": 150,
|
|
454
|
+
"discount": {
|
|
455
|
+
"__type": "PercentageDiscount",
|
|
456
|
+
"inputs": [100, "Free Premium Bundle"]
|
|
457
|
+
},
|
|
458
|
+
"productIds": ["2222222222222"],
|
|
459
|
+
"itemsToDiscount": 1,
|
|
460
|
+
"label": "Free Premium Bundle"
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
"threshold": 300,
|
|
464
|
+
"discount": {
|
|
465
|
+
"__type": "PercentageDiscount",
|
|
466
|
+
"inputs": [100, "Free Deluxe Bundle"]
|
|
467
|
+
},
|
|
468
|
+
"productIds": ["3333333333333"],
|
|
469
|
+
"itemsToDiscount": 1,
|
|
470
|
+
"label": "Free Deluxe Bundle"
|
|
471
|
+
}
|
|
472
|
+
],
|
|
473
|
+
["meta-exclude-gift"],
|
|
474
|
+
false
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**How It Works (stackTiers: false):**
|
|
480
|
+
- Customer adds items to cart totaling $200
|
|
481
|
+
- Cart subtotal is calculated (excluding items tagged "meta-exclude-gift")
|
|
482
|
+
- Highest qualifying tier is selected:
|
|
483
|
+
- $75 tier qualifies ($200 >= $75)
|
|
484
|
+
- $150 tier qualifies ($200 >= $150) ✓ HIGHEST
|
|
485
|
+
- $300 tier doesn't qualify ($200 < $300)
|
|
486
|
+
- Only the Premium Bundle gets 100% discount:
|
|
487
|
+
- "2222222222222" (Premium Bundle) - from $150 tier
|
|
488
|
+
- Customer receives ONLY the Premium Bundle (not the Starter Bundle)
|
|
489
|
+
|
|
490
|
+
---
|
|
421
491
|
|
|
422
492
|
**Key Features:**
|
|
423
493
|
- Automatically excludes gift items from threshold calculation
|
|
424
|
-
-
|
|
494
|
+
- **stackTiers: true** - Applies ALL qualifying tiers (cumulative)
|
|
495
|
+
- **stackTiers: false** - Applies ONLY the highest qualifying tier
|
|
425
496
|
- Supports product IDs or product tags per tier
|
|
426
497
|
- Perfect for seasonal promotions with start/end dates
|
|
427
498
|
|
|
@@ -436,6 +507,9 @@ export function run(input) {
|
|
|
436
507
|
- `itemsToDiscount` - Maximum items to discount (optional, defaults to all)
|
|
437
508
|
- `label` - Display name for the tier (optional)
|
|
438
509
|
4. **Exclude Tags** - Array of product tags to exclude from subtotal calculation (default: `["meta-exclude-gift"]`)
|
|
510
|
+
5. **Stack Tiers** - Boolean (default: `true`)
|
|
511
|
+
- `true` - Apply all qualifying tiers (cumulative GWP)
|
|
512
|
+
- `false` - Apply only highest tier (bundle upgrades)
|
|
439
513
|
|
|
440
514
|
---
|
|
441
515
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CampaignFactory.d.ts","sourceRoot":"","sources":["../../src/common/CampaignFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAEV,SAAS,EAIV,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAiC5C,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,CACJ,OAAO,GACP,MAAM,GACN,MAAM,GACN,WAAW,GACX,OAAO,EAAE,GACT,MAAM,EAAE,GACR,MAAM,EAAE,GACR,WAAW,EAAE,CAChB,EAAE,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG;IAC9C,MAAM,EAAE,UAAU,GAAG,qBAAqB,GAAG,kBAAkB,GAAG,mBAAmB,CAAC;IACtF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,qBAAa,eAAe;IAC1B,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GACI,QAAQ;IAG/D,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAuB/B,MAAM,CAAC,kBAAkB,CACvB,MAAM,EAAE,WAAW,GAEjB,QAAQ,GACR,SAAS,GACT,WAAW,GACX,UAAU,GACV,QAAQ,GACR,iBAAiB,GACjB,SAAS;
|
|
1
|
+
{"version":3,"file":"CampaignFactory.d.ts","sourceRoot":"","sources":["../../src/common/CampaignFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAEV,SAAS,EAIV,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAiC5C,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,CACJ,OAAO,GACP,MAAM,GACN,MAAM,GACN,WAAW,GACX,OAAO,EAAE,GACT,MAAM,EAAE,GACR,MAAM,EAAE,GACR,WAAW,EAAE,CAChB,EAAE,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG;IAC9C,MAAM,EAAE,UAAU,GAAG,qBAAqB,GAAG,kBAAkB,GAAG,mBAAmB,CAAC;IACtF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,qBAAa,eAAe;IAC1B,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GACI,QAAQ;IAG/D,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAuB/B,MAAM,CAAC,kBAAkB,CACvB,MAAM,EAAE,WAAW,GAEjB,QAAQ,GACR,SAAS,GACT,WAAW,GACX,UAAU,GACV,QAAQ,GACR,iBAAiB,GACjB,SAAS;CA0Hd"}
|
|
@@ -55,7 +55,7 @@ class CampaignFactory {
|
|
|
55
55
|
case 'ShippingDiscount':
|
|
56
56
|
return new ShippingDiscount_1.ShippingDiscount(args[0], args[1], args[2], args[3]);
|
|
57
57
|
case 'MultiTierDiscount':
|
|
58
|
-
return new MultiTierDiscount_1.MultiTierDiscount(args[0], args[1], args[2], args[3]);
|
|
58
|
+
return new MultiTierDiscount_1.MultiTierDiscount(args[0], args[1], args[2], args[3], args[4]);
|
|
59
59
|
case 'AndSelector':
|
|
60
60
|
return new AndSelector_1.AndSelector([...args]);
|
|
61
61
|
case 'OrSelector':
|
|
@@ -16,28 +16,37 @@ export interface Tier {
|
|
|
16
16
|
* MultiTierDiscount - Applies tiered discounts based on cart subtotal
|
|
17
17
|
*
|
|
18
18
|
* This campaign evaluates the cart total (excluding gifts) and applies
|
|
19
|
-
*
|
|
19
|
+
* discounts based on the stackTiers configuration:
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* - Spend $100: Get free product B
|
|
24
|
-
* - Spend $250: Get free product C
|
|
21
|
+
* - stackTiers: true (default) - Applies ALL qualifying tiers (cumulative)
|
|
22
|
+
* Example: Spend $150 → Get products from both $75 and $100 tiers
|
|
25
23
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* - stackTiers: false - Applies ONLY the highest qualifying tier
|
|
25
|
+
* Example: Spend $150 → Get product from $100 tier only
|
|
26
|
+
*
|
|
27
|
+
* Subtotal Calculation:
|
|
28
|
+
* - Automatically excludes items tagged with excludeGiftTags
|
|
29
|
+
* - Automatically excludes tier reward products (by ID or tag)
|
|
30
|
+
* This prevents gaming the system by manually adding gift products
|
|
31
|
+
*
|
|
32
|
+
* Use cases:
|
|
33
|
+
* - Cumulative GWP: Spend more, get more gifts (stackTiers: true)
|
|
34
|
+
* - Bundle upgrades: Spend more, get better bundle (stackTiers: false)
|
|
28
35
|
*/
|
|
29
36
|
export declare class MultiTierDiscount extends Campaign {
|
|
30
37
|
tiers: Tier[];
|
|
31
38
|
excludeGiftTags: string[];
|
|
32
|
-
|
|
39
|
+
stackTiers: boolean;
|
|
40
|
+
constructor(behavior: QualifierBehavior, qualifiers: [Qualifier | AndSelector | OrSelector], tiers: Tier[], excludeGiftTags?: string[], stackTiers?: boolean);
|
|
33
41
|
/**
|
|
34
42
|
* Calculate cart subtotal excluding gift items
|
|
35
43
|
*/
|
|
36
44
|
private calculateCartSubtotal;
|
|
37
45
|
/**
|
|
38
|
-
* Find
|
|
46
|
+
* Find all qualifying tiers based on cart subtotal
|
|
47
|
+
* Returns tiers in descending order by threshold (highest first)
|
|
39
48
|
*/
|
|
40
|
-
private
|
|
49
|
+
private findQualifyingTiers;
|
|
41
50
|
/**
|
|
42
51
|
* Filter line items that match the tier's product criteria
|
|
43
52
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiTierDiscount.d.ts","sourceRoot":"","sources":["../../src/lineItem/MultiTierDiscount.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EACV,iBAAiB,EACjB,SAAS,EACV,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,WAAW,IAAI;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED
|
|
1
|
+
{"version":3,"file":"MultiTierDiscount.d.ts","sourceRoot":"","sources":["../../src/lineItem/MultiTierDiscount.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EACV,iBAAiB,EACjB,SAAS,EACV,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,WAAW,IAAI;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,iBAAkB,SAAQ,QAAQ;IAC7C,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;gBAGlB,QAAQ,EAAE,iBAAiB,EAC3B,UAAU,EAAE,CAAC,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC,EAClD,KAAK,EAAE,IAAI,EAAE,EACb,eAAe,GAAE,MAAM,EAA0B,EACjD,UAAU,GAAE,OAAc;IAU5B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAyD7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAI3B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA+B9B,GAAG,CAAC,YAAY,EAAE,YAAY,GAAG,YAAY;CAqF9C"}
|
|
@@ -6,24 +6,33 @@ const Campaign_1 = require("../common/Campaign");
|
|
|
6
6
|
* MultiTierDiscount - Applies tiered discounts based on cart subtotal
|
|
7
7
|
*
|
|
8
8
|
* This campaign evaluates the cart total (excluding gifts) and applies
|
|
9
|
-
*
|
|
9
|
+
* discounts based on the stackTiers configuration:
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - Spend $100: Get free product B
|
|
14
|
-
* - Spend $250: Get free product C
|
|
11
|
+
* - stackTiers: true (default) - Applies ALL qualifying tiers (cumulative)
|
|
12
|
+
* Example: Spend $150 → Get products from both $75 and $100 tiers
|
|
15
13
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* - stackTiers: false - Applies ONLY the highest qualifying tier
|
|
15
|
+
* Example: Spend $150 → Get product from $100 tier only
|
|
16
|
+
*
|
|
17
|
+
* Subtotal Calculation:
|
|
18
|
+
* - Automatically excludes items tagged with excludeGiftTags
|
|
19
|
+
* - Automatically excludes tier reward products (by ID or tag)
|
|
20
|
+
* This prevents gaming the system by manually adding gift products
|
|
21
|
+
*
|
|
22
|
+
* Use cases:
|
|
23
|
+
* - Cumulative GWP: Spend more, get more gifts (stackTiers: true)
|
|
24
|
+
* - Bundle upgrades: Spend more, get better bundle (stackTiers: false)
|
|
18
25
|
*/
|
|
19
26
|
class MultiTierDiscount extends Campaign_1.Campaign {
|
|
20
27
|
tiers;
|
|
21
28
|
excludeGiftTags;
|
|
22
|
-
|
|
29
|
+
stackTiers;
|
|
30
|
+
constructor(behavior, qualifiers, tiers, excludeGiftTags = ['meta-exclude-gift'], stackTiers = true) {
|
|
23
31
|
super(behavior, qualifiers);
|
|
24
32
|
// Sort tiers by threshold descending (highest first)
|
|
25
33
|
this.tiers = [...tiers].sort((a, b) => b.threshold - a.threshold);
|
|
26
34
|
this.excludeGiftTags = excludeGiftTags;
|
|
35
|
+
this.stackTiers = stackTiers;
|
|
27
36
|
}
|
|
28
37
|
/**
|
|
29
38
|
* Calculate cart subtotal excluding gift items
|
|
@@ -38,6 +47,24 @@ class MultiTierDiscount extends Campaign_1.Campaign {
|
|
|
38
47
|
const variant = line.merchandise;
|
|
39
48
|
// Check if product has any of the exclude tags
|
|
40
49
|
isExcludedGift = this.excludeGiftTags.some(tag => variant.product.hasTags.filter((t) => t.hasTag && t.tag === tag).length > 0);
|
|
50
|
+
// Also exclude if this product is a tier reward product
|
|
51
|
+
// This prevents gaming the system by manually adding gift products
|
|
52
|
+
if (!isExcludedGift) {
|
|
53
|
+
const productId = variant.product.id.split('/').pop();
|
|
54
|
+
isExcludedGift = this.tiers.some(tier => {
|
|
55
|
+
// Check if product ID matches any tier's gift products
|
|
56
|
+
if (tier.productIds && tier.productIds.length > 0) {
|
|
57
|
+
if (tier.productIds.includes(productId || '')) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Check if product tags match any tier's gift product tags
|
|
62
|
+
if (tier.productTags && tier.productTags.length > 0) {
|
|
63
|
+
return tier.productTags.some((tag) => variant.product.hasTags.filter((t) => t.hasTag && t.tag === tag).length > 0);
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
41
68
|
}
|
|
42
69
|
if (isExcludedGift) {
|
|
43
70
|
continue;
|
|
@@ -49,15 +76,11 @@ class MultiTierDiscount extends Campaign_1.Campaign {
|
|
|
49
76
|
return subtotal;
|
|
50
77
|
}
|
|
51
78
|
/**
|
|
52
|
-
* Find
|
|
79
|
+
* Find all qualifying tiers based on cart subtotal
|
|
80
|
+
* Returns tiers in descending order by threshold (highest first)
|
|
53
81
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (cartSubtotal >= tier.threshold) {
|
|
57
|
-
return tier;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
82
|
+
findQualifyingTiers(cartSubtotal) {
|
|
83
|
+
return this.tiers.filter(tier => cartSubtotal >= tier.threshold);
|
|
61
84
|
}
|
|
62
85
|
/**
|
|
63
86
|
* Filter line items that match the tier's product criteria
|
|
@@ -89,61 +112,70 @@ class MultiTierDiscount extends Campaign_1.Campaign {
|
|
|
89
112
|
}
|
|
90
113
|
// Calculate cart subtotal (excluding gifts)
|
|
91
114
|
const cartSubtotal = this.calculateCartSubtotal(discountCart);
|
|
92
|
-
// Find
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
115
|
+
// Find qualifying tiers based on stackTiers configuration
|
|
116
|
+
const allQualifyingTiers = this.findQualifyingTiers(cartSubtotal);
|
|
117
|
+
if (allQualifyingTiers.length === 0) {
|
|
95
118
|
// Cart doesn't meet minimum threshold
|
|
96
119
|
return discountCart;
|
|
97
120
|
}
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
// Select tiers to apply based on stackTiers configuration
|
|
122
|
+
// Note: allQualifyingTiers is sorted by threshold descending (from constructor)
|
|
123
|
+
// so allQualifyingTiers[0] is the highest qualifying tier
|
|
124
|
+
const tiersToApply = this.stackTiers
|
|
125
|
+
? allQualifyingTiers // Apply all qualifying tiers (cumulative)
|
|
126
|
+
: [allQualifyingTiers[0]]; // Apply only highest tier
|
|
127
|
+
// Apply each selected tier
|
|
128
|
+
for (const tier of tiersToApply) {
|
|
129
|
+
// Get line items that match this tier's product criteria
|
|
130
|
+
const applicableLineItems = this.getApplicableLineItems(discountCart, tier);
|
|
131
|
+
if (applicableLineItems.length === 0) {
|
|
132
|
+
// No matching products found for this tier
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// Sort by price (ascending) to discount cheapest items first
|
|
136
|
+
applicableLineItems.sort((a, b) => {
|
|
137
|
+
const priceA = parseFloat(String(a.cost.amountPerQuantity.amount));
|
|
138
|
+
const priceB = parseFloat(String(b.cost.amountPerQuantity.amount));
|
|
139
|
+
return priceA - priceB;
|
|
140
|
+
});
|
|
141
|
+
const itemsToApplyDiscounts = [];
|
|
142
|
+
let remainingDiscounts = tier.itemsToDiscount;
|
|
143
|
+
// Apply discount to matching items up to the limit
|
|
144
|
+
applicableLineItems.forEach((lineItem) => {
|
|
145
|
+
if (remainingDiscounts === 0)
|
|
146
|
+
return;
|
|
147
|
+
if (remainingDiscounts !== undefined) {
|
|
148
|
+
// Apply to as many items as possible up to the limit
|
|
149
|
+
if (lineItem.quantity >= remainingDiscounts) {
|
|
150
|
+
itemsToApplyDiscounts.push({
|
|
151
|
+
lineItem,
|
|
152
|
+
maxDiscounts: remainingDiscounts,
|
|
153
|
+
});
|
|
154
|
+
remainingDiscounts = 0;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// Apply to all items in this line
|
|
158
|
+
itemsToApplyDiscounts.push({
|
|
159
|
+
lineItem,
|
|
160
|
+
maxDiscounts: lineItem.quantity,
|
|
161
|
+
});
|
|
162
|
+
remainingDiscounts -= lineItem.quantity;
|
|
163
|
+
}
|
|
124
164
|
}
|
|
125
165
|
else {
|
|
126
|
-
//
|
|
166
|
+
// No limit, apply to all matching items
|
|
127
167
|
itemsToApplyDiscounts.push({
|
|
128
168
|
lineItem,
|
|
129
169
|
maxDiscounts: lineItem.quantity,
|
|
130
170
|
});
|
|
131
|
-
remainingDiscounts -= lineItem.quantity;
|
|
132
171
|
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
// Apply the tier's discount to all selected items
|
|
143
|
-
itemsToApplyDiscounts.forEach((item) => {
|
|
144
|
-
const discount = qualifyingTier.discount;
|
|
145
|
-
discount.apply([item]);
|
|
146
|
-
});
|
|
172
|
+
});
|
|
173
|
+
// Apply the tier's discount to all selected items
|
|
174
|
+
itemsToApplyDiscounts.forEach((item) => {
|
|
175
|
+
const discount = tier.discount;
|
|
176
|
+
discount.apply([item]);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
147
179
|
return discountCart;
|
|
148
180
|
}
|
|
149
181
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zoxllc/shopify-checkout-extensions",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Configuration-driven runtime engine for Shopify checkout extensions - campaigns, discounts, qualifiers, and selectors",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|