@zoxllc/shopify-checkout-extensions 0.1.0 → 0.2.1
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 +153 -0
- package/dist/common/CampaignFactory.d.ts +1 -1
- package/dist/common/CampaignFactory.d.ts.map +1 -1
- package/dist/common/CampaignFactory.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/lineItem/MultiTierDiscount.d.ts +51 -0
- package/dist/lineItem/MultiTierDiscount.d.ts.map +1 -0
- package/dist/lineItem/MultiTierDiscount.js +159 -0
- package/package.json +1 -1
package/EXAMPLES.md
CHANGED
|
@@ -360,6 +360,159 @@ export function run(input) {
|
|
|
360
360
|
|
|
361
361
|
---
|
|
362
362
|
|
|
363
|
+
## Example 11: Multi-Tier Gift with Purchase (Cumulative)
|
|
364
|
+
|
|
365
|
+
**Use Case:** Offer cumulative tiered gifts based on cart spend threshold. All qualifying tiers are applied (stackTiers: true).
|
|
366
|
+
|
|
367
|
+
```json
|
|
368
|
+
{
|
|
369
|
+
"__type": "MultiTierDiscount",
|
|
370
|
+
"label": "Winter 2025 Tiered GWP",
|
|
371
|
+
"active": true,
|
|
372
|
+
"description": "Spend more, get better gifts!",
|
|
373
|
+
"inputs": [
|
|
374
|
+
":all",
|
|
375
|
+
[],
|
|
376
|
+
[
|
|
377
|
+
{
|
|
378
|
+
"threshold": 75,
|
|
379
|
+
"discount": {
|
|
380
|
+
"__type": "PercentageDiscount",
|
|
381
|
+
"inputs": [100, "Free Colorwheel F&F"]
|
|
382
|
+
},
|
|
383
|
+
"productIds": ["6638547533896"],
|
|
384
|
+
"itemsToDiscount": 1,
|
|
385
|
+
"label": "Free Colorwheel F&F"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"threshold": 100,
|
|
389
|
+
"discount": {
|
|
390
|
+
"__type": "PercentageDiscount",
|
|
391
|
+
"inputs": [100, "Free Goldie Mystery ZOX"]
|
|
392
|
+
},
|
|
393
|
+
"productIds": ["6640591011912"],
|
|
394
|
+
"itemsToDiscount": 1,
|
|
395
|
+
"label": "Free Goldie Mystery ZOX"
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"threshold": 250,
|
|
399
|
+
"discount": {
|
|
400
|
+
"__type": "PercentageDiscount",
|
|
401
|
+
"inputs": [100, "Free Cosmic Drift Daily"]
|
|
402
|
+
},
|
|
403
|
+
"productIds": ["6980288053320"],
|
|
404
|
+
"itemsToDiscount": 1,
|
|
405
|
+
"label": "Free Cosmic Drift Daily"
|
|
406
|
+
}
|
|
407
|
+
],
|
|
408
|
+
["meta-exclude-gift"],
|
|
409
|
+
true
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**How It Works (stackTiers: true):**
|
|
415
|
+
- Customer adds items to cart totaling $150
|
|
416
|
+
- Cart subtotal is calculated (excluding items tagged "meta-exclude-gift")
|
|
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
|
+
---
|
|
491
|
+
|
|
492
|
+
**Key Features:**
|
|
493
|
+
- Automatically excludes gift items from threshold calculation
|
|
494
|
+
- **stackTiers: true** - Applies ALL qualifying tiers (cumulative)
|
|
495
|
+
- **stackTiers: false** - Applies ONLY the highest qualifying tier
|
|
496
|
+
- Supports product IDs or product tags per tier
|
|
497
|
+
- Perfect for seasonal promotions with start/end dates
|
|
498
|
+
|
|
499
|
+
### MultiTierDiscount Inputs Array:
|
|
500
|
+
1. **QualifierBehavior** - `:all` or `:any`
|
|
501
|
+
2. **Qualifiers Array** - Array of cart-level qualifiers (optional, can be empty `[]`)
|
|
502
|
+
3. **Tiers Array** - Array of tier objects, each containing:
|
|
503
|
+
- `threshold` - Minimum cart amount to qualify
|
|
504
|
+
- `discount` - Discount configuration (typically 100% for free gifts)
|
|
505
|
+
- `productIds` - Array of product IDs to discount (optional)
|
|
506
|
+
- `productTags` - Array of product tags to discount (optional)
|
|
507
|
+
- `itemsToDiscount` - Maximum items to discount (optional, defaults to all)
|
|
508
|
+
- `label` - Display name for the tier (optional)
|
|
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)
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
363
516
|
## Tips for Your UI App
|
|
364
517
|
|
|
365
518
|
1. **Store configurations in your database** with fields:
|
|
@@ -9,7 +9,7 @@ export type InputConfig = {
|
|
|
9
9
|
inputs: (boolean | number | string | InputConfig | boolean[] | number[] | string[] | InputConfig[])[];
|
|
10
10
|
};
|
|
11
11
|
export type CampaignInputConfig = InputConfig & {
|
|
12
|
-
__type: 'BuyXGetY' | 'ConditionalDiscount' | 'ShippingDiscount';
|
|
12
|
+
__type: 'BuyXGetY' | 'ConditionalDiscount' | 'ShippingDiscount' | 'MultiTierDiscount';
|
|
13
13
|
label: string;
|
|
14
14
|
active: boolean;
|
|
15
15
|
description?: string;
|
|
@@ -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;
|
|
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"}
|
|
@@ -17,6 +17,7 @@ const ConditionalDiscount_1 = require("../lineItem/ConditionalDiscount");
|
|
|
17
17
|
const FixedItemDiscount_1 = require("../lineItem/FixedItemDiscount");
|
|
18
18
|
const PercentageDiscount_1 = require("../lineItem/PercentageDiscount");
|
|
19
19
|
const BuyXGetY_1 = require("../lineItem/BuyXGetY");
|
|
20
|
+
const MultiTierDiscount_1 = require("../lineItem/MultiTierDiscount");
|
|
20
21
|
const SubscriptionItemSelector_1 = require("./SubscriptionItemSelector");
|
|
21
22
|
const CountryCodeQualifier_1 = require("./CountryCodeQualifier");
|
|
22
23
|
const RateNameSelector_1 = require("../shipping/RateNameSelector");
|
|
@@ -53,6 +54,8 @@ class CampaignFactory {
|
|
|
53
54
|
return new BuyXGetY_1.BuyXGetY(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
|
|
54
55
|
case 'ShippingDiscount':
|
|
55
56
|
return new ShippingDiscount_1.ShippingDiscount(args[0], args[1], args[2], args[3]);
|
|
57
|
+
case 'MultiTierDiscount':
|
|
58
|
+
return new MultiTierDiscount_1.MultiTierDiscount(args[0], args[1], args[2], args[3], args[4]);
|
|
56
59
|
case 'AndSelector':
|
|
57
60
|
return new AndSelector_1.AndSelector([...args]);
|
|
58
61
|
case 'OrSelector':
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { ConditionalDiscount } from './lineItem/ConditionalDiscount';
|
|
2
2
|
export { BuyXGetY } from './lineItem/BuyXGetY';
|
|
3
3
|
export { ShippingDiscount } from './shipping/ShippingDiscount';
|
|
4
|
+
export { MultiTierDiscount, type Tier } from './lineItem/MultiTierDiscount';
|
|
4
5
|
export { Campaign } from './common/Campaign';
|
|
5
6
|
export { FixedItemDiscount } from './lineItem/FixedItemDiscount';
|
|
6
7
|
export { PercentageDiscount } from './lineItem/PercentageDiscount';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAG/D,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAGrE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EACL,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EACL,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,wBAAwB,GAC9B,MAAM,uBAAuB,CAAC;AAG/B,YAAY,EACV,YAAY,EACZ,qBAAqB,GACtB,MAAM,gCAAgC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,KAAK,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAG/D,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAGrE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EACL,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EACL,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,wBAAwB,GAC9B,MAAM,uBAAuB,CAAC;AAG/B,YAAY,EACV,YAAY,EACZ,qBAAqB,GACtB,MAAM,gCAAgC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.StringComparisonSchema = exports.NumericalComparisonSchema = exports.QualifierMatchTypeSchema = exports.MatchTypeSchema = exports.QualifierBehaviorSchema = exports.CampaignConfigSchema = exports.ConfigValidator = exports.CampaignFactory = exports.DiscountCart = exports.CountryCodeQualifier = exports.PostCartAmountQualifier = exports.CustomerTagQualifier = exports.CustomerSubscriberQualifier = exports.CustomerEmailQualifier = exports.CartQuantityQualifier = exports.CartHasItemQualifier = exports.CartAmountQualifier = exports.StringComparisonType = exports.NumericalComparisonType = exports.QualifierMatchType = exports.QualifierBehavior = exports.Qualifier = exports.RateNameSelector = exports.SubscriptionItemSelector = exports.SaleItemSelector = exports.ProductTypeSelector = exports.ProductTagSelector = exports.ProductIdSelector = exports.ProductHandleSelector = exports.Selector = exports.OrSelector = exports.AndSelector = exports.FixedDiscount = exports.PercentageDiscount = exports.FixedItemDiscount = exports.Campaign = exports.ShippingDiscount = exports.BuyXGetY = exports.ConditionalDiscount = void 0;
|
|
3
|
+
exports.StringComparisonSchema = exports.NumericalComparisonSchema = exports.QualifierMatchTypeSchema = exports.MatchTypeSchema = exports.QualifierBehaviorSchema = exports.CampaignConfigSchema = exports.ConfigValidator = exports.CampaignFactory = exports.DiscountCart = exports.CountryCodeQualifier = exports.PostCartAmountQualifier = exports.CustomerTagQualifier = exports.CustomerSubscriberQualifier = exports.CustomerEmailQualifier = exports.CartQuantityQualifier = exports.CartHasItemQualifier = exports.CartAmountQualifier = exports.StringComparisonType = exports.NumericalComparisonType = exports.QualifierMatchType = exports.QualifierBehavior = exports.Qualifier = exports.RateNameSelector = exports.SubscriptionItemSelector = exports.SaleItemSelector = exports.ProductTypeSelector = exports.ProductTagSelector = exports.ProductIdSelector = exports.ProductHandleSelector = exports.Selector = exports.OrSelector = exports.AndSelector = exports.FixedDiscount = exports.PercentageDiscount = exports.FixedItemDiscount = exports.Campaign = exports.MultiTierDiscount = exports.ShippingDiscount = exports.BuyXGetY = exports.ConditionalDiscount = void 0;
|
|
4
4
|
// Campaign Types
|
|
5
5
|
var ConditionalDiscount_1 = require("./lineItem/ConditionalDiscount");
|
|
6
6
|
Object.defineProperty(exports, "ConditionalDiscount", { enumerable: true, get: function () { return ConditionalDiscount_1.ConditionalDiscount; } });
|
|
@@ -8,6 +8,8 @@ var BuyXGetY_1 = require("./lineItem/BuyXGetY");
|
|
|
8
8
|
Object.defineProperty(exports, "BuyXGetY", { enumerable: true, get: function () { return BuyXGetY_1.BuyXGetY; } });
|
|
9
9
|
var ShippingDiscount_1 = require("./shipping/ShippingDiscount");
|
|
10
10
|
Object.defineProperty(exports, "ShippingDiscount", { enumerable: true, get: function () { return ShippingDiscount_1.ShippingDiscount; } });
|
|
11
|
+
var MultiTierDiscount_1 = require("./lineItem/MultiTierDiscount");
|
|
12
|
+
Object.defineProperty(exports, "MultiTierDiscount", { enumerable: true, get: function () { return MultiTierDiscount_1.MultiTierDiscount; } });
|
|
11
13
|
var Campaign_1 = require("./common/Campaign");
|
|
12
14
|
Object.defineProperty(exports, "Campaign", { enumerable: true, get: function () { return Campaign_1.Campaign; } });
|
|
13
15
|
// Discount Types
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Campaign } from '../common/Campaign';
|
|
2
|
+
import type { DiscountCart } from '../common/DiscountCart';
|
|
3
|
+
import type { DiscountInterface } from '../common/DiscountInterface';
|
|
4
|
+
import type { QualifierBehavior, Qualifier } from '../common/Qualifier';
|
|
5
|
+
import type { AndSelector } from '../common/AndSelector';
|
|
6
|
+
import type { OrSelector } from '../common/OrSelector';
|
|
7
|
+
export interface Tier {
|
|
8
|
+
threshold: number;
|
|
9
|
+
discount: DiscountInterface;
|
|
10
|
+
productIds?: string[];
|
|
11
|
+
productTags?: string[];
|
|
12
|
+
itemsToDiscount?: number;
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* MultiTierDiscount - Applies tiered discounts based on cart subtotal
|
|
17
|
+
*
|
|
18
|
+
* This campaign evaluates the cart total (excluding gifts) and applies
|
|
19
|
+
* discounts based on the stackTiers configuration:
|
|
20
|
+
*
|
|
21
|
+
* - stackTiers: true (default) - Applies ALL qualifying tiers (cumulative)
|
|
22
|
+
* Example: Spend $150 → Get products from both $75 and $100 tiers
|
|
23
|
+
*
|
|
24
|
+
* - stackTiers: false - Applies ONLY the highest qualifying tier
|
|
25
|
+
* Example: Spend $150 → Get product from $100 tier only
|
|
26
|
+
*
|
|
27
|
+
* Use cases:
|
|
28
|
+
* - Cumulative GWP: Spend more, get more gifts (stackTiers: true)
|
|
29
|
+
* - Bundle upgrades: Spend more, get better bundle (stackTiers: false)
|
|
30
|
+
*/
|
|
31
|
+
export declare class MultiTierDiscount extends Campaign {
|
|
32
|
+
tiers: Tier[];
|
|
33
|
+
excludeGiftTags: string[];
|
|
34
|
+
stackTiers: boolean;
|
|
35
|
+
constructor(behavior: QualifierBehavior, qualifiers: [Qualifier | AndSelector | OrSelector], tiers: Tier[], excludeGiftTags?: string[], stackTiers?: boolean);
|
|
36
|
+
/**
|
|
37
|
+
* Calculate cart subtotal excluding gift items
|
|
38
|
+
*/
|
|
39
|
+
private calculateCartSubtotal;
|
|
40
|
+
/**
|
|
41
|
+
* Find all qualifying tiers based on cart subtotal
|
|
42
|
+
* Returns tiers in descending order by threshold (highest first)
|
|
43
|
+
*/
|
|
44
|
+
private findQualifyingTiers;
|
|
45
|
+
/**
|
|
46
|
+
* Filter line items that match the tier's product criteria
|
|
47
|
+
*/
|
|
48
|
+
private getApplicableLineItems;
|
|
49
|
+
run(discountCart: DiscountCart): DiscountCart;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=MultiTierDiscount.d.ts.map
|
|
@@ -0,0 +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;;;;;;;;;;;;;;;GAeG;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;IA8B7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAI3B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA+B9B,GAAG,CAAC,YAAY,EAAE,YAAY,GAAG,YAAY;CAqF9C"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MultiTierDiscount = void 0;
|
|
4
|
+
const Campaign_1 = require("../common/Campaign");
|
|
5
|
+
/**
|
|
6
|
+
* MultiTierDiscount - Applies tiered discounts based on cart subtotal
|
|
7
|
+
*
|
|
8
|
+
* This campaign evaluates the cart total (excluding gifts) and applies
|
|
9
|
+
* discounts based on the stackTiers configuration:
|
|
10
|
+
*
|
|
11
|
+
* - stackTiers: true (default) - Applies ALL qualifying tiers (cumulative)
|
|
12
|
+
* Example: Spend $150 → Get products from both $75 and $100 tiers
|
|
13
|
+
*
|
|
14
|
+
* - stackTiers: false - Applies ONLY the highest qualifying tier
|
|
15
|
+
* Example: Spend $150 → Get product from $100 tier only
|
|
16
|
+
*
|
|
17
|
+
* Use cases:
|
|
18
|
+
* - Cumulative GWP: Spend more, get more gifts (stackTiers: true)
|
|
19
|
+
* - Bundle upgrades: Spend more, get better bundle (stackTiers: false)
|
|
20
|
+
*/
|
|
21
|
+
class MultiTierDiscount extends Campaign_1.Campaign {
|
|
22
|
+
tiers;
|
|
23
|
+
excludeGiftTags;
|
|
24
|
+
stackTiers;
|
|
25
|
+
constructor(behavior, qualifiers, tiers, excludeGiftTags = ['meta-exclude-gift'], stackTiers = true) {
|
|
26
|
+
super(behavior, qualifiers);
|
|
27
|
+
// Sort tiers by threshold descending (highest first)
|
|
28
|
+
this.tiers = [...tiers].sort((a, b) => b.threshold - a.threshold);
|
|
29
|
+
this.excludeGiftTags = excludeGiftTags;
|
|
30
|
+
this.stackTiers = stackTiers;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Calculate cart subtotal excluding gift items
|
|
34
|
+
*/
|
|
35
|
+
calculateCartSubtotal(discountCart) {
|
|
36
|
+
let subtotal = 0;
|
|
37
|
+
for (const line of discountCart.cart.lines) {
|
|
38
|
+
// Check if this item should be excluded from subtotal calculation
|
|
39
|
+
let isExcludedGift = false;
|
|
40
|
+
// Type guard: Only ProductVariant has product property
|
|
41
|
+
if (line.merchandise.__typename === 'ProductVariant') {
|
|
42
|
+
const variant = line.merchandise;
|
|
43
|
+
// Check if product has any of the exclude tags
|
|
44
|
+
isExcludedGift = this.excludeGiftTags.some(tag => variant.product.hasTags.filter((t) => t.hasTag && t.tag === tag).length > 0);
|
|
45
|
+
}
|
|
46
|
+
if (isExcludedGift) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Add line total to subtotal
|
|
50
|
+
const lineTotal = parseFloat(String(line.cost.totalAmount.amount));
|
|
51
|
+
subtotal += lineTotal;
|
|
52
|
+
}
|
|
53
|
+
return subtotal;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Find all qualifying tiers based on cart subtotal
|
|
57
|
+
* Returns tiers in descending order by threshold (highest first)
|
|
58
|
+
*/
|
|
59
|
+
findQualifyingTiers(cartSubtotal) {
|
|
60
|
+
return this.tiers.filter(tier => cartSubtotal >= tier.threshold);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Filter line items that match the tier's product criteria
|
|
64
|
+
*/
|
|
65
|
+
getApplicableLineItems(discountCart, tier) {
|
|
66
|
+
return discountCart.cart.lines.filter((line) => {
|
|
67
|
+
// Type guard: Only ProductVariant has product property
|
|
68
|
+
if (line.merchandise.__typename !== 'ProductVariant') {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
// If tier specifies product IDs, match against those
|
|
72
|
+
if (tier.productIds && tier.productIds.length > 0) {
|
|
73
|
+
const productId = line.merchandise.product.id.split('/').pop();
|
|
74
|
+
return tier.productIds.includes(productId || '');
|
|
75
|
+
}
|
|
76
|
+
// If tier specifies product tags, match against those
|
|
77
|
+
if (tier.productTags && tier.productTags.length > 0) {
|
|
78
|
+
const variant = line.merchandise;
|
|
79
|
+
return tier.productTags.some((tag) => variant.product.hasTags.filter((t) => t.hasTag && t.tag === tag).length > 0);
|
|
80
|
+
}
|
|
81
|
+
// If no product criteria specified, apply to all items
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
run(discountCart) {
|
|
86
|
+
// First check if cart qualifies for any discount
|
|
87
|
+
if (!this.qualifies(discountCart)) {
|
|
88
|
+
return discountCart;
|
|
89
|
+
}
|
|
90
|
+
// Calculate cart subtotal (excluding gifts)
|
|
91
|
+
const cartSubtotal = this.calculateCartSubtotal(discountCart);
|
|
92
|
+
// Find qualifying tiers based on stackTiers configuration
|
|
93
|
+
const allQualifyingTiers = this.findQualifyingTiers(cartSubtotal);
|
|
94
|
+
if (allQualifyingTiers.length === 0) {
|
|
95
|
+
// Cart doesn't meet minimum threshold
|
|
96
|
+
return discountCart;
|
|
97
|
+
}
|
|
98
|
+
// Select tiers to apply based on stackTiers configuration
|
|
99
|
+
// Note: allQualifyingTiers is sorted by threshold descending (from constructor)
|
|
100
|
+
// so allQualifyingTiers[0] is the highest qualifying tier
|
|
101
|
+
const tiersToApply = this.stackTiers
|
|
102
|
+
? allQualifyingTiers // Apply all qualifying tiers (cumulative)
|
|
103
|
+
: [allQualifyingTiers[0]]; // Apply only highest tier
|
|
104
|
+
// Apply each selected tier
|
|
105
|
+
for (const tier of tiersToApply) {
|
|
106
|
+
// Get line items that match this tier's product criteria
|
|
107
|
+
const applicableLineItems = this.getApplicableLineItems(discountCart, tier);
|
|
108
|
+
if (applicableLineItems.length === 0) {
|
|
109
|
+
// No matching products found for this tier
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Sort by price (ascending) to discount cheapest items first
|
|
113
|
+
applicableLineItems.sort((a, b) => {
|
|
114
|
+
const priceA = parseFloat(String(a.cost.amountPerQuantity.amount));
|
|
115
|
+
const priceB = parseFloat(String(b.cost.amountPerQuantity.amount));
|
|
116
|
+
return priceA - priceB;
|
|
117
|
+
});
|
|
118
|
+
const itemsToApplyDiscounts = [];
|
|
119
|
+
let remainingDiscounts = tier.itemsToDiscount;
|
|
120
|
+
// Apply discount to matching items up to the limit
|
|
121
|
+
applicableLineItems.forEach((lineItem) => {
|
|
122
|
+
if (remainingDiscounts === 0)
|
|
123
|
+
return;
|
|
124
|
+
if (remainingDiscounts !== undefined) {
|
|
125
|
+
// Apply to as many items as possible up to the limit
|
|
126
|
+
if (lineItem.quantity >= remainingDiscounts) {
|
|
127
|
+
itemsToApplyDiscounts.push({
|
|
128
|
+
lineItem,
|
|
129
|
+
maxDiscounts: remainingDiscounts,
|
|
130
|
+
});
|
|
131
|
+
remainingDiscounts = 0;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Apply to all items in this line
|
|
135
|
+
itemsToApplyDiscounts.push({
|
|
136
|
+
lineItem,
|
|
137
|
+
maxDiscounts: lineItem.quantity,
|
|
138
|
+
});
|
|
139
|
+
remainingDiscounts -= lineItem.quantity;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// No limit, apply to all matching items
|
|
144
|
+
itemsToApplyDiscounts.push({
|
|
145
|
+
lineItem,
|
|
146
|
+
maxDiscounts: lineItem.quantity,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
// Apply the tier's discount to all selected items
|
|
151
|
+
itemsToApplyDiscounts.forEach((item) => {
|
|
152
|
+
const discount = tier.discount;
|
|
153
|
+
discount.apply([item]);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return discountCart;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.MultiTierDiscount = MultiTierDiscount;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zoxllc/shopify-checkout-extensions",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|