@zoxllc/shopify-checkout-extensions 0.0.8 → 0.2.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.
Files changed (164) hide show
  1. package/CLAUDE.md +136 -0
  2. package/EXAMPLES.md +463 -0
  3. package/INTEGRATION.md +482 -0
  4. package/README.md +5 -1
  5. package/dist/common/AndSelector.d.ts +11 -0
  6. package/dist/common/AndSelector.d.ts.map +1 -0
  7. package/dist/common/AndSelector.js +30 -0
  8. package/dist/common/Campaign.d.ts +24 -0
  9. package/dist/common/Campaign.d.ts.map +1 -0
  10. package/dist/common/Campaign.js +41 -0
  11. package/dist/common/CampaignConfiguration.d.ts +10 -0
  12. package/dist/common/CampaignConfiguration.d.ts.map +1 -0
  13. package/dist/common/CampaignConfiguration.js +7 -0
  14. package/dist/common/CampaignFactory.d.ts +22 -0
  15. package/dist/common/CampaignFactory.d.ts.map +1 -0
  16. package/dist/common/CampaignFactory.js +98 -0
  17. package/dist/common/CartAmountQualifier.d.ts +22 -0
  18. package/dist/common/CartAmountQualifier.d.ts.map +1 -0
  19. package/dist/common/CartAmountQualifier.js +46 -0
  20. package/dist/common/CartHasItemQualifier.d.ts +21 -0
  21. package/dist/common/CartHasItemQualifier.d.ts.map +1 -0
  22. package/dist/common/CartHasItemQualifier.js +54 -0
  23. package/dist/common/CartQuantityQualifier.d.ts +22 -0
  24. package/dist/common/CartQuantityQualifier.d.ts.map +1 -0
  25. package/dist/common/CartQuantityQualifier.js +62 -0
  26. package/dist/common/ConfigSchema.d.ts +71 -0
  27. package/dist/common/ConfigSchema.d.ts.map +1 -0
  28. package/dist/common/ConfigSchema.js +69 -0
  29. package/dist/common/ConfigValidator.d.ts +29 -0
  30. package/dist/common/ConfigValidator.d.ts.map +1 -0
  31. package/dist/common/ConfigValidator.js +84 -0
  32. package/dist/common/CountryCodeQualifier.d.ts +15 -0
  33. package/dist/common/CountryCodeQualifier.d.ts.map +1 -0
  34. package/dist/common/CountryCodeQualifier.js +30 -0
  35. package/dist/common/CustomerEmailQualifier.d.ts +16 -0
  36. package/dist/common/CustomerEmailQualifier.d.ts.map +1 -0
  37. package/dist/common/CustomerEmailQualifier.js +52 -0
  38. package/dist/common/CustomerSubscriberQualifier.d.ts +18 -0
  39. package/dist/common/CustomerSubscriberQualifier.d.ts.map +1 -0
  40. package/dist/common/CustomerSubscriberQualifier.js +28 -0
  41. package/dist/common/CustomerTagQualifier.d.ts +16 -0
  42. package/dist/common/CustomerTagQualifier.d.ts.map +1 -0
  43. package/dist/common/CustomerTagQualifier.js +45 -0
  44. package/dist/common/DiscountCart.d.ts +23 -0
  45. package/dist/common/DiscountCart.d.ts.map +1 -0
  46. package/dist/common/DiscountCart.js +103 -0
  47. package/dist/common/DiscountInterface.d.ts +17 -0
  48. package/dist/common/DiscountInterface.d.ts.map +1 -0
  49. package/dist/common/DiscountInterface.js +2 -0
  50. package/dist/common/OrSelector.d.ts +11 -0
  51. package/dist/common/OrSelector.d.ts.map +1 -0
  52. package/dist/common/OrSelector.js +30 -0
  53. package/dist/common/PostCartAmountQualifier.d.ts +9 -0
  54. package/dist/common/PostCartAmountQualifier.d.ts.map +1 -0
  55. package/dist/common/PostCartAmountQualifier.js +17 -0
  56. package/dist/common/ProductHandleSelector.d.ts +8 -0
  57. package/dist/common/ProductHandleSelector.d.ts.map +1 -0
  58. package/dist/common/ProductHandleSelector.js +22 -0
  59. package/dist/common/ProductIdSelector.d.ts +8 -0
  60. package/dist/common/ProductIdSelector.d.ts.map +1 -0
  61. package/dist/common/ProductIdSelector.js +22 -0
  62. package/dist/common/ProductTagSelector.d.ts +10 -0
  63. package/dist/common/ProductTagSelector.d.ts.map +1 -0
  64. package/dist/common/ProductTagSelector.js +39 -0
  65. package/dist/common/ProductTypeSelector.d.ts +8 -0
  66. package/dist/common/ProductTypeSelector.d.ts.map +1 -0
  67. package/dist/common/ProductTypeSelector.js +25 -0
  68. package/dist/common/Qualifier.d.ts +30 -0
  69. package/dist/common/Qualifier.d.ts.map +1 -0
  70. package/dist/common/Qualifier.js +61 -0
  71. package/dist/common/SaleItemSelector.d.ts +11 -0
  72. package/dist/common/SaleItemSelector.d.ts.map +1 -0
  73. package/dist/common/SaleItemSelector.js +30 -0
  74. package/dist/common/Selector.d.ts +27 -0
  75. package/dist/common/Selector.d.ts.map +1 -0
  76. package/dist/common/Selector.js +51 -0
  77. package/dist/common/SubscriptionItemSelector.d.ts +7 -0
  78. package/dist/common/SubscriptionItemSelector.d.ts.map +1 -0
  79. package/dist/common/SubscriptionItemSelector.js +19 -0
  80. package/dist/form/CampaignForm.d.ts +1 -0
  81. package/dist/form/CampaignForm.d.ts.map +1 -0
  82. package/dist/form/CampaignForm.js +1 -0
  83. package/{src/index.ts → dist/index.d.ts} +23 -27
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +80 -0
  86. package/dist/lineItem/BuyXGetY.d.ts +18 -0
  87. package/dist/lineItem/BuyXGetY.d.ts.map +1 -0
  88. package/dist/lineItem/BuyXGetY.js +85 -0
  89. package/dist/lineItem/ConditionalDiscount.d.ts +14 -0
  90. package/dist/lineItem/ConditionalDiscount.d.ts.map +1 -0
  91. package/dist/lineItem/ConditionalDiscount.js +81 -0
  92. package/dist/lineItem/FixedItemDiscount.d.ts +9 -0
  93. package/dist/lineItem/FixedItemDiscount.d.ts.map +1 -0
  94. package/dist/lineItem/FixedItemDiscount.js +35 -0
  95. package/dist/lineItem/MultiTierDiscount.d.ts +47 -0
  96. package/dist/lineItem/MultiTierDiscount.d.ts.map +1 -0
  97. package/dist/lineItem/MultiTierDiscount.js +150 -0
  98. package/dist/lineItem/PercentageDiscount.d.ts +8 -0
  99. package/dist/lineItem/PercentageDiscount.d.ts.map +1 -0
  100. package/dist/lineItem/PercentageDiscount.js +32 -0
  101. package/dist/lineItem/api.d.ts +2026 -0
  102. package/dist/lineItem/api.d.ts.map +1 -0
  103. package/dist/lineItem/api.js +1157 -0
  104. package/dist/shipping/FixedDiscount.d.ts +8 -0
  105. package/dist/shipping/FixedDiscount.d.ts.map +1 -0
  106. package/dist/shipping/FixedDiscount.js +30 -0
  107. package/dist/shipping/RateNameSelector.d.ts +10 -0
  108. package/dist/shipping/RateNameSelector.d.ts.map +1 -0
  109. package/dist/shipping/RateNameSelector.js +45 -0
  110. package/dist/shipping/ShippingDiscount.d.ts +14 -0
  111. package/dist/shipping/ShippingDiscount.d.ts.map +1 -0
  112. package/dist/shipping/ShippingDiscount.js +48 -0
  113. package/dist/shipping/api.d.ts +2019 -0
  114. package/dist/shipping/api.d.ts.map +1 -0
  115. package/dist/shipping/api.js +1147 -0
  116. package/package.json +26 -7
  117. package/migrate.ts +0 -64
  118. package/src/common/AndSelector.ts +0 -42
  119. package/src/common/Campaign.ts +0 -70
  120. package/src/common/CampaignConfiguration.ts +0 -10
  121. package/src/common/CampaignFactory.ts +0 -233
  122. package/src/common/CartAmountQualifier.ts +0 -64
  123. package/src/common/CartHasItemQualifier.ts +0 -80
  124. package/src/common/CartQuantityQualifier.ts +0 -94
  125. package/src/common/CountryCodeQualifier.ts +0 -49
  126. package/src/common/CustomerEmailQualifier.ts +0 -67
  127. package/src/common/CustomerSubscriberQualifier.ts +0 -32
  128. package/src/common/CustomerTagQualifier.ts +0 -69
  129. package/src/common/DiscountCart.ts +0 -161
  130. package/src/common/DiscountInterface.ts +0 -26
  131. package/src/common/OrSelector.ts +0 -40
  132. package/src/common/PostCartAmountQualifier.ts +0 -21
  133. package/src/common/ProductHandleSelector.ts +0 -32
  134. package/src/common/ProductIdSelector.ts +0 -34
  135. package/src/common/ProductTagSelector.ts +0 -63
  136. package/src/common/ProductTypeSelector.ts +0 -40
  137. package/src/common/Qualifier.ts +0 -67
  138. package/src/common/SaleItemSelector.ts +0 -36
  139. package/src/common/Selector.ts +0 -66
  140. package/src/common/SubscriptionItemSelector.ts +0 -23
  141. package/src/lineItem/BuyXGetY.ts +0 -131
  142. package/src/lineItem/ConditionalDiscount.ts +0 -102
  143. package/src/lineItem/FixedItemDiscount.ts +0 -51
  144. package/src/lineItem/PercentageDiscount.ts +0 -44
  145. package/src/lineItem/api.ts +0 -2103
  146. package/src/shipping/FixedDiscount.ts +0 -37
  147. package/src/shipping/RateNameSelector.ts +0 -54
  148. package/src/shipping/ShippingDiscount.ts +0 -75
  149. package/src/shipping/api.ts +0 -2014
  150. package/tests/AndSelector.test.ts +0 -27
  151. package/tests/CartQuantityQualifier.test.ts +0 -381
  152. package/tests/CountryCodeQualifier.test.ts +0 -55
  153. package/tests/CustomerSubscriberQualifier.test.ts +0 -101
  154. package/tests/CustomerTagQualifier.test.ts +0 -71
  155. package/tests/DiscountCart.test.ts +0 -115
  156. package/tests/OrSelector.test.ts +0 -27
  157. package/tests/ProductIdSelector.test.ts +0 -83
  158. package/tests/ProductTagSelector.test.ts +0 -75
  159. package/tests/Qualifier.test.ts +0 -193
  160. package/tests/RateNameSelector.test.ts +0 -107
  161. package/tests/SaleItemSelector.test.ts +0 -113
  162. package/tests/Selector.test.ts +0 -83
  163. package/tests/ShippingDiscount.test.ts +0 -147
  164. package/tsconfig.json +0 -25
package/CLAUDE.md ADDED
@@ -0,0 +1,136 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ This is a TypeScript library for Shopify checkout extensions. It provides reusable utility classes for building discount campaigns, qualifiers, and selectors that can be shared across multiple Shopify Functions (Product Discount Functions, Shipping Discount Functions, Cart Validation Functions).
8
+
9
+ ## Commands
10
+
11
+ ### Build
12
+ ```bash
13
+ npm run build
14
+ ```
15
+ Compiles TypeScript to JavaScript in the `dist/` directory. Always run before publishing.
16
+
17
+ ### Testing
18
+ ```bash
19
+ npm test # Run all tests with Vitest
20
+ ```
21
+
22
+ ### Publishing
23
+ ```bash
24
+ npm run prepublishOnly # Automatically runs build before publishing
25
+ ```
26
+
27
+ ## Architecture
28
+
29
+ ### Core Concepts
30
+
31
+ The library is built around three main abstractions:
32
+
33
+ 1. **Campaigns**: Define discount logic and qualification rules (e.g., `BuyXGetY`, `ConditionalDiscount`, `ShippingDiscount`)
34
+ 2. **Qualifiers**: Determine if a cart/customer meets certain conditions (e.g., cart amount, customer tags, country)
35
+ 3. **Selectors**: Target specific products/items in the cart (e.g., by product ID, tag, type)
36
+
37
+ ### Campaign System
38
+
39
+ **Campaign** (src/common/Campaign.ts) is the base class with a lifecycle:
40
+ - `qualifies()`: Checks if all qualifiers match based on `QualifierBehavior` (ALL or ANY)
41
+ - `runWithHooks()`: Executes the campaign lifecycle
42
+ - `run()`: Override this in subclasses to apply discount logic
43
+ - `afterRun()`: Post-processing hook
44
+
45
+ Campaign types:
46
+ - **ConditionalDiscount** (src/lineItem/ConditionalDiscount.ts): Apply discounts when qualifiers match
47
+ - **BuyXGetY** (src/lineItem/BuyXGetY.ts): Buy X items, get Y items with discount
48
+ - **ShippingDiscount** (src/shipping/ShippingDiscount.ts): Apply discounts to shipping rates
49
+
50
+ ### CampaignFactory Pattern
51
+
52
+ **CampaignFactory** (src/common/CampaignFactory.ts) creates campaigns from JSON configuration objects. Configuration has this structure:
53
+
54
+ ```typescript
55
+ {
56
+ __type: 'BuyXGetY' | 'ConditionalDiscount' | 'ShippingDiscount',
57
+ label: string,
58
+ active: boolean,
59
+ inputs: [/* constructor arguments */]
60
+ }
61
+ ```
62
+
63
+ The factory recursively deserializes nested objects (qualifiers, selectors, discounts) using the `__type` field as a discriminator.
64
+
65
+ ### Qualifiers
66
+
67
+ **Qualifier** (src/common/Qualifier.ts) is the base class. Qualifiers check cart/customer conditions:
68
+
69
+ - **AndSelector/OrSelector**: Combine multiple qualifiers with AND/OR logic
70
+ - **CartAmountQualifier**: Check cart subtotal amount
71
+ - **CartQuantityQualifier**: Check total item quantity
72
+ - **PostCartAmountQualifier**: Check cart amount after discounts
73
+ - **CustomerTagQualifier**: Check customer tags
74
+ - **CustomerEmailQualifier**: Match customer email patterns
75
+ - **CustomerSubscriberQualifier**: Check if customer is a subscriber
76
+ - **CountryCodeQualifier**: Match shipping country
77
+ - **CartHasItemQualifier**: Check if cart contains specific items
78
+
79
+ Qualifiers use enums for comparison:
80
+ - `QualifierMatchType`: DOES, DOES_NOT
81
+ - `NumericalComparisonType`: GREATER_THAN, EQUAL_TO, etc.
82
+ - `StringComparisonType`: MATCH, CONTAINS, START_WITH, END_WITH
83
+
84
+ ### Selectors
85
+
86
+ **Selector** (src/common/Selector.ts) targets items in the cart:
87
+
88
+ - **ProductIdSelector**: Match by product variant ID
89
+ - **ProductTagSelector**: Match by product tags
90
+ - **ProductTypeSelector**: Match by product type
91
+ - **ProductHandleSelector**: Match by product handle
92
+ - **SaleItemSelector**: Match items on sale
93
+ - **SubscriptionItemSelector**: Match subscription items
94
+ - **RateNameSelector**: Match shipping rates by name (for shipping discounts)
95
+
96
+ Selectors use `MatchType` enum: ALL, IS_ONE, NOT_ONE, DOES, DOES_NOT
97
+
98
+ ### DiscountCart Wrapper
99
+
100
+ **DiscountCart** (src/common/DiscountCart.ts) wraps Shopify's cart object and provides:
101
+ - `subtotalAmount`: Cart subtotal excluding gifts
102
+ - `appliedDiscountTotal`: Track applied discounts
103
+ - `totalLineItemQuantity()`: Total items in cart
104
+ - `subtotalExcludingGifts()`: Excludes items tagged `meta-exclude-gift`
105
+ - `addDiscount()`, `addShippingDiscount()`: Track applied discounts
106
+ - `reset()`: Reset discount tracking
107
+
108
+ ### API Types
109
+
110
+ - **src/lineItem/api.ts**: TypeScript types for Product Discount Functions
111
+ - **src/shipping/api.ts**: TypeScript types for Shipping Discount Functions
112
+
113
+ These files contain Shopify's GraphQL schema types for cart, products, customers, etc.
114
+
115
+ ### Special Conventions
116
+
117
+ - Items tagged with `meta-exclude-gift` are excluded from cart totals and quantities
118
+ - The library supports both percentage and fixed-amount discounts
119
+ - Discounts can apply per-item or to the entire target group
120
+
121
+ ## Configuration & Validation
122
+
123
+ **ConfigValidator** (src/common/ConfigValidator.ts) validates campaign configurations before use:
124
+ ```typescript
125
+ const result = ConfigValidator.validate(config);
126
+ if (!result.valid) {
127
+ console.error(result.errors);
128
+ }
129
+ ```
130
+
131
+ **ConfigSchema** (src/common/ConfigSchema.ts) provides JSON Schema definitions for configurations, useful for:
132
+ - Form validation in UI applications
133
+ - Documentation of configuration structure
134
+ - Type safety with TypeScript
135
+
136
+ See EXAMPLES.md for complete configuration examples.
package/EXAMPLES.md ADDED
@@ -0,0 +1,463 @@
1
+ # Example Campaign Configurations
2
+
3
+ This document shows example JSON configurations that can be used with the CampaignFactory.
4
+
5
+ ## How to Use
6
+
7
+ In your Shopify app, store these configurations in your database or as metafields. Then, in your Shopify Function:
8
+
9
+ ```typescript
10
+ import { CampaignFactory, DiscountCart } from '@zoxllc/shopify-checkout-extensions';
11
+
12
+ export function run(input) {
13
+ // Get configuration from metafield or API
14
+ const config = JSON.parse(input.discountNode.metafield?.value || '{}');
15
+
16
+ // Create campaign from configuration
17
+ const campaign = CampaignFactory.createFromConfig(config);
18
+
19
+ // Run the campaign
20
+ const cart = new DiscountCart(input.cart);
21
+ campaign.runWithHooks(cart);
22
+
23
+ return { discounts: cart.discounts };
24
+ }
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Example 1: Simple Percentage Discount for VIP Customers
30
+
31
+ **Use Case:** Give 20% off to customers with the "VIP" tag
32
+
33
+ ```json
34
+ {
35
+ "__type": "ConditionalDiscount",
36
+ "label": "VIP Customer Discount",
37
+ "active": true,
38
+ "description": "20% off for VIP customers",
39
+ "inputs": [
40
+ ":all",
41
+ [
42
+ {
43
+ "__type": "CustomerTagQualifier",
44
+ "inputs": [":does", ":match", ["VIP"]]
45
+ }
46
+ ],
47
+ {
48
+ "__type": "PercentageDiscount",
49
+ "inputs": [20, "VIP Discount - 20% Off"]
50
+ },
51
+ null,
52
+ 0
53
+ ]
54
+ }
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Example 2: Spend $100, Get 15% Off
60
+
61
+ **Use Case:** Discount when cart subtotal is over $100
62
+
63
+ ```json
64
+ {
65
+ "__type": "ConditionalDiscount",
66
+ "label": "Spend $100 Get 15% Off",
67
+ "active": true,
68
+ "inputs": [
69
+ ":all",
70
+ [
71
+ {
72
+ "__type": "CartAmountQualifier",
73
+ "inputs": [":subtotal", ":greater_than_or_equal", 100]
74
+ }
75
+ ],
76
+ {
77
+ "__type": "PercentageDiscount",
78
+ "inputs": [15, "Spend $100+ and Save 15%"]
79
+ },
80
+ null,
81
+ 0
82
+ ]
83
+ }
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Example 3: Buy 2, Get 1 Free
89
+
90
+ **Use Case:** Buy 2 of any product with tag "eligible", get 1 free (100% off)
91
+
92
+ ```json
93
+ {
94
+ "__type": "BuyXGetY",
95
+ "label": "Buy 2 Get 1 Free",
96
+ "active": true,
97
+ "inputs": [
98
+ ":all",
99
+ [],
100
+ {
101
+ "__type": "PercentageDiscount",
102
+ "inputs": [100, "Buy 2 Get 1 Free!"]
103
+ },
104
+ {
105
+ "__type": "ProductTagSelector",
106
+ "inputs": [":does", ":match", ["eligible"]]
107
+ },
108
+ {
109
+ "__type": "ProductTagSelector",
110
+ "inputs": [":does", ":match", ["eligible"]]
111
+ },
112
+ 2,
113
+ 1,
114
+ null
115
+ ]
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Example 4: Discount Specific Products
122
+
123
+ **Use Case:** 10% off specific product variants
124
+
125
+ ```json
126
+ {
127
+ "__type": "ConditionalDiscount",
128
+ "label": "10% Off Select Products",
129
+ "active": true,
130
+ "inputs": [
131
+ ":all",
132
+ [],
133
+ {
134
+ "__type": "PercentageDiscount",
135
+ "inputs": [10, "10% Off Select Items"]
136
+ },
137
+ {
138
+ "__type": "ProductIdSelector",
139
+ "inputs": [
140
+ ":is_one",
141
+ [
142
+ "gid://shopify/ProductVariant/123456789",
143
+ "gid://shopify/ProductVariant/987654321"
144
+ ]
145
+ ]
146
+ },
147
+ 0
148
+ ]
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Example 5: Free Shipping for Orders Over $50
155
+
156
+ **Use Case:** Free shipping when cart is over $50
157
+
158
+ ```json
159
+ {
160
+ "__type": "ShippingDiscount",
161
+ "label": "Free Shipping Over $50",
162
+ "active": true,
163
+ "inputs": [
164
+ ":all",
165
+ [
166
+ {
167
+ "__type": "CartAmountQualifier",
168
+ "inputs": [":subtotal", ":greater_than_or_equal", 50]
169
+ }
170
+ ],
171
+ {
172
+ "__type": "PercentageDiscount",
173
+ "inputs": [100, "Free Shipping!"]
174
+ },
175
+ {
176
+ "__type": "RateNameSelector",
177
+ "inputs": [":does", ":contains", ["Standard"]]
178
+ }
179
+ ]
180
+ }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Example 6: Country-Specific Discount
186
+
187
+ **Use Case:** 25% off for Canadian customers
188
+
189
+ ```json
190
+ {
191
+ "__type": "ConditionalDiscount",
192
+ "label": "Canada Day Sale",
193
+ "active": true,
194
+ "inputs": [
195
+ ":all",
196
+ [
197
+ {
198
+ "__type": "CountryCodeQualifier",
199
+ "inputs": [":does", ["CA"]]
200
+ }
201
+ ],
202
+ {
203
+ "__type": "PercentageDiscount",
204
+ "inputs": [25, "Canada Day - 25% Off"]
205
+ },
206
+ null,
207
+ 0
208
+ ]
209
+ }
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Example 7: Discount on Sale Items Only
215
+
216
+ **Use Case:** Extra 10% off items already on sale
217
+
218
+ ```json
219
+ {
220
+ "__type": "ConditionalDiscount",
221
+ "label": "Extra 10% Off Sale Items",
222
+ "active": true,
223
+ "inputs": [
224
+ ":all",
225
+ [],
226
+ {
227
+ "__type": "PercentageDiscount",
228
+ "inputs": [10, "Extra 10% Off Sale!"]
229
+ },
230
+ {
231
+ "__type": "SaleItemSelector",
232
+ "inputs": [":is_on_sale"]
233
+ },
234
+ 0
235
+ ]
236
+ }
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Example 8: Subscriber Discount
242
+
243
+ **Use Case:** 15% off for active subscribers
244
+
245
+ ```json
246
+ {
247
+ "__type": "ConditionalDiscount",
248
+ "label": "Subscriber Discount",
249
+ "active": true,
250
+ "inputs": [
251
+ ":all",
252
+ [
253
+ {
254
+ "__type": "CustomerSubscriberQualifier",
255
+ "inputs": [":does"]
256
+ }
257
+ ],
258
+ {
259
+ "__type": "PercentageDiscount",
260
+ "inputs": [15, "Subscriber Exclusive - 15% Off"]
261
+ },
262
+ null,
263
+ 0
264
+ ]
265
+ }
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Example 9: Multiple Conditions (AND Logic)
271
+
272
+ **Use Case:** 30% off for VIP customers in the US with cart over $75
273
+
274
+ ```json
275
+ {
276
+ "__type": "ConditionalDiscount",
277
+ "label": "VIP US High Value",
278
+ "active": true,
279
+ "inputs": [
280
+ ":all",
281
+ [
282
+ {
283
+ "__type": "CustomerTagQualifier",
284
+ "inputs": [":does", ":match", ["VIP"]]
285
+ },
286
+ {
287
+ "__type": "CountryCodeQualifier",
288
+ "inputs": [":does", ["US"]]
289
+ },
290
+ {
291
+ "__type": "CartAmountQualifier",
292
+ "inputs": [":subtotal", ":greater_than_or_equal", 75]
293
+ }
294
+ ],
295
+ {
296
+ "__type": "PercentageDiscount",
297
+ "inputs": [30, "VIP Exclusive - 30% Off"]
298
+ },
299
+ null,
300
+ 0
301
+ ]
302
+ }
303
+ ```
304
+
305
+ ---
306
+
307
+ ## Example 10: Fixed Amount Discount
308
+
309
+ **Use Case:** $10 off orders over $50
310
+
311
+ ```json
312
+ {
313
+ "__type": "ConditionalDiscount",
314
+ "label": "$10 Off Orders Over $50",
315
+ "active": true,
316
+ "inputs": [
317
+ ":all",
318
+ [
319
+ {
320
+ "__type": "CartAmountQualifier",
321
+ "inputs": [":subtotal", ":greater_than_or_equal", 50]
322
+ }
323
+ ],
324
+ {
325
+ "__type": "FixedItemDiscount",
326
+ "inputs": [10, false, "$10 Off Your Order"]
327
+ },
328
+ null,
329
+ 0
330
+ ]
331
+ }
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Configuration Structure Reference
337
+
338
+ ### ConditionalDiscount Inputs Array:
339
+ 1. **QualifierBehavior** - `:all` or `:any`
340
+ 2. **Qualifiers Array** - Array of qualifier configurations
341
+ 3. **Discount** - Discount configuration object
342
+ 4. **LineItemSelector** - Selector configuration (or `null` for all items)
343
+ 5. **MaxDiscounts** - Number (0 for unlimited, or specific count)
344
+
345
+ ### BuyXGetY Inputs Array:
346
+ 1. **QualifierBehavior** - `:all` or `:any`
347
+ 2. **Qualifiers Array** - Array of qualifier configurations
348
+ 3. **Discount** - Discount configuration object
349
+ 4. **BuyItemSelector** - Selector for items to buy
350
+ 5. **GetItemSelector** - Selector for items to discount
351
+ 6. **BuyX** - Number of items to buy
352
+ 7. **GetY** - Number of items to get discounted
353
+ 8. **MaxSets** - Maximum number of times to apply (or `null` for unlimited)
354
+
355
+ ### ShippingDiscount Inputs Array:
356
+ 1. **QualifierBehavior** - `:all` or `:any`
357
+ 2. **Qualifiers Array** - Array of qualifier configurations
358
+ 3. **Discount** - Discount configuration object
359
+ 4. **RateSelector** - Selector for shipping rates
360
+
361
+ ---
362
+
363
+ ## Example 11: Multi-Tier Gift with Purchase
364
+
365
+ **Use Case:** Offer tiered gifts based on cart spend threshold (e.g., spend $75 get Gift A, $100 get Gift B, $250 get Gift C). Only the highest qualifying tier is applied.
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
+ ]
410
+ }
411
+ ```
412
+
413
+ **How It Works:**
414
+ - Customer adds items to cart totaling $150
415
+ - Cart subtotal is calculated (excluding items tagged "meta-exclude-gift")
416
+ - Tiers are evaluated from highest to lowest
417
+ - $250 tier doesn't qualify ($150 < $250)
418
+ - $100 tier DOES qualify ($150 >= $100)
419
+ - Product "6640591011912" (Goldie Mystery ZOX) gets 100% discount
420
+ - Lower tiers ($75) are NOT applied
421
+
422
+ **Key Features:**
423
+ - Automatically excludes gift items from threshold calculation
424
+ - Only applies the highest qualifying tier (no stacking)
425
+ - Supports product IDs or product tags per tier
426
+ - Perfect for seasonal promotions with start/end dates
427
+
428
+ ### MultiTierDiscount Inputs Array:
429
+ 1. **QualifierBehavior** - `:all` or `:any`
430
+ 2. **Qualifiers Array** - Array of cart-level qualifiers (optional, can be empty `[]`)
431
+ 3. **Tiers Array** - Array of tier objects, each containing:
432
+ - `threshold` - Minimum cart amount to qualify
433
+ - `discount` - Discount configuration (typically 100% for free gifts)
434
+ - `productIds` - Array of product IDs to discount (optional)
435
+ - `productTags` - Array of product tags to discount (optional)
436
+ - `itemsToDiscount` - Maximum items to discount (optional, defaults to all)
437
+ - `label` - Display name for the tier (optional)
438
+ 4. **Exclude Tags** - Array of product tags to exclude from subtotal calculation (default: `["meta-exclude-gift"]`)
439
+
440
+ ---
441
+
442
+ ## Tips for Your UI App
443
+
444
+ 1. **Store configurations in your database** with fields:
445
+ - `id`, `shop_id`, `campaign_config` (JSON), `priority`, `created_at`, `updated_at`
446
+
447
+ 2. **Use the ConfigValidator** before saving:
448
+ ```typescript
449
+ import { ConfigValidator } from '@zoxllc/shopify-checkout-extensions';
450
+
451
+ const result = ConfigValidator.validate(userConfig);
452
+ if (!result.valid) {
453
+ return { errors: result.errors };
454
+ }
455
+ ```
456
+
457
+ 3. **Pass configurations via metafields** to your Shopify Function:
458
+ - Store the JSON configuration in a discount metafield
459
+ - Read it in your Function's `input.discountNode.metafield.value`
460
+
461
+ 4. **Test configurations** before activating:
462
+ - Use the `DiscountCart` class with sample cart data
463
+ - Preview what discounts would be applied