@zoxllc/shopify-checkout-extensions 0.0.8 → 0.1.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.
- package/CLAUDE.md +136 -0
- package/EXAMPLES.md +384 -0
- package/INTEGRATION.md +482 -0
- package/README.md +5 -1
- package/dist/common/AndSelector.d.ts +11 -0
- package/dist/common/AndSelector.d.ts.map +1 -0
- package/dist/common/AndSelector.js +30 -0
- package/dist/common/Campaign.d.ts +24 -0
- package/dist/common/Campaign.d.ts.map +1 -0
- package/dist/common/Campaign.js +41 -0
- package/dist/common/CampaignConfiguration.d.ts +10 -0
- package/dist/common/CampaignConfiguration.d.ts.map +1 -0
- package/dist/common/CampaignConfiguration.js +7 -0
- package/dist/common/CampaignFactory.d.ts +22 -0
- package/dist/common/CampaignFactory.d.ts.map +1 -0
- package/dist/common/CampaignFactory.js +95 -0
- package/dist/common/CartAmountQualifier.d.ts +22 -0
- package/dist/common/CartAmountQualifier.d.ts.map +1 -0
- package/dist/common/CartAmountQualifier.js +46 -0
- package/dist/common/CartHasItemQualifier.d.ts +21 -0
- package/dist/common/CartHasItemQualifier.d.ts.map +1 -0
- package/dist/common/CartHasItemQualifier.js +54 -0
- package/dist/common/CartQuantityQualifier.d.ts +22 -0
- package/dist/common/CartQuantityQualifier.d.ts.map +1 -0
- package/dist/common/CartQuantityQualifier.js +62 -0
- package/dist/common/ConfigSchema.d.ts +71 -0
- package/dist/common/ConfigSchema.d.ts.map +1 -0
- package/dist/common/ConfigSchema.js +69 -0
- package/dist/common/ConfigValidator.d.ts +29 -0
- package/dist/common/ConfigValidator.d.ts.map +1 -0
- package/dist/common/ConfigValidator.js +84 -0
- package/dist/common/CountryCodeQualifier.d.ts +15 -0
- package/dist/common/CountryCodeQualifier.d.ts.map +1 -0
- package/dist/common/CountryCodeQualifier.js +30 -0
- package/dist/common/CustomerEmailQualifier.d.ts +16 -0
- package/dist/common/CustomerEmailQualifier.d.ts.map +1 -0
- package/dist/common/CustomerEmailQualifier.js +52 -0
- package/dist/common/CustomerSubscriberQualifier.d.ts +18 -0
- package/dist/common/CustomerSubscriberQualifier.d.ts.map +1 -0
- package/dist/common/CustomerSubscriberQualifier.js +28 -0
- package/dist/common/CustomerTagQualifier.d.ts +16 -0
- package/dist/common/CustomerTagQualifier.d.ts.map +1 -0
- package/dist/common/CustomerTagQualifier.js +45 -0
- package/dist/common/DiscountCart.d.ts +23 -0
- package/dist/common/DiscountCart.d.ts.map +1 -0
- package/dist/common/DiscountCart.js +103 -0
- package/dist/common/DiscountInterface.d.ts +17 -0
- package/dist/common/DiscountInterface.d.ts.map +1 -0
- package/dist/common/DiscountInterface.js +2 -0
- package/dist/common/OrSelector.d.ts +11 -0
- package/dist/common/OrSelector.d.ts.map +1 -0
- package/dist/common/OrSelector.js +30 -0
- package/dist/common/PostCartAmountQualifier.d.ts +9 -0
- package/dist/common/PostCartAmountQualifier.d.ts.map +1 -0
- package/dist/common/PostCartAmountQualifier.js +17 -0
- package/dist/common/ProductHandleSelector.d.ts +8 -0
- package/dist/common/ProductHandleSelector.d.ts.map +1 -0
- package/dist/common/ProductHandleSelector.js +22 -0
- package/dist/common/ProductIdSelector.d.ts +8 -0
- package/dist/common/ProductIdSelector.d.ts.map +1 -0
- package/dist/common/ProductIdSelector.js +22 -0
- package/dist/common/ProductTagSelector.d.ts +10 -0
- package/dist/common/ProductTagSelector.d.ts.map +1 -0
- package/dist/common/ProductTagSelector.js +39 -0
- package/dist/common/ProductTypeSelector.d.ts +8 -0
- package/dist/common/ProductTypeSelector.d.ts.map +1 -0
- package/dist/common/ProductTypeSelector.js +25 -0
- package/dist/common/Qualifier.d.ts +30 -0
- package/dist/common/Qualifier.d.ts.map +1 -0
- package/dist/common/Qualifier.js +61 -0
- package/dist/common/SaleItemSelector.d.ts +11 -0
- package/dist/common/SaleItemSelector.d.ts.map +1 -0
- package/dist/common/SaleItemSelector.js +30 -0
- package/dist/common/Selector.d.ts +27 -0
- package/dist/common/Selector.d.ts.map +1 -0
- package/dist/common/Selector.js +51 -0
- package/dist/common/SubscriptionItemSelector.d.ts +7 -0
- package/dist/common/SubscriptionItemSelector.d.ts.map +1 -0
- package/dist/common/SubscriptionItemSelector.js +19 -0
- package/dist/form/CampaignForm.d.ts +1 -0
- package/dist/form/CampaignForm.d.ts.map +1 -0
- package/dist/form/CampaignForm.js +1 -0
- package/{src/index.ts → dist/index.d.ts} +22 -27
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/lineItem/BuyXGetY.d.ts +18 -0
- package/dist/lineItem/BuyXGetY.d.ts.map +1 -0
- package/dist/lineItem/BuyXGetY.js +85 -0
- package/dist/lineItem/ConditionalDiscount.d.ts +14 -0
- package/dist/lineItem/ConditionalDiscount.d.ts.map +1 -0
- package/dist/lineItem/ConditionalDiscount.js +81 -0
- package/dist/lineItem/FixedItemDiscount.d.ts +9 -0
- package/dist/lineItem/FixedItemDiscount.d.ts.map +1 -0
- package/dist/lineItem/FixedItemDiscount.js +35 -0
- package/dist/lineItem/PercentageDiscount.d.ts +8 -0
- package/dist/lineItem/PercentageDiscount.d.ts.map +1 -0
- package/dist/lineItem/PercentageDiscount.js +32 -0
- package/dist/lineItem/api.d.ts +2026 -0
- package/dist/lineItem/api.d.ts.map +1 -0
- package/dist/lineItem/api.js +1157 -0
- package/dist/shipping/FixedDiscount.d.ts +8 -0
- package/dist/shipping/FixedDiscount.d.ts.map +1 -0
- package/dist/shipping/FixedDiscount.js +30 -0
- package/dist/shipping/RateNameSelector.d.ts +10 -0
- package/dist/shipping/RateNameSelector.d.ts.map +1 -0
- package/dist/shipping/RateNameSelector.js +45 -0
- package/dist/shipping/ShippingDiscount.d.ts +14 -0
- package/dist/shipping/ShippingDiscount.d.ts.map +1 -0
- package/dist/shipping/ShippingDiscount.js +48 -0
- package/dist/shipping/api.d.ts +2019 -0
- package/dist/shipping/api.d.ts.map +1 -0
- package/dist/shipping/api.js +1147 -0
- package/package.json +26 -7
- package/migrate.ts +0 -64
- package/src/common/AndSelector.ts +0 -42
- package/src/common/Campaign.ts +0 -70
- package/src/common/CampaignConfiguration.ts +0 -10
- package/src/common/CampaignFactory.ts +0 -233
- package/src/common/CartAmountQualifier.ts +0 -64
- package/src/common/CartHasItemQualifier.ts +0 -80
- package/src/common/CartQuantityQualifier.ts +0 -94
- package/src/common/CountryCodeQualifier.ts +0 -49
- package/src/common/CustomerEmailQualifier.ts +0 -67
- package/src/common/CustomerSubscriberQualifier.ts +0 -32
- package/src/common/CustomerTagQualifier.ts +0 -69
- package/src/common/DiscountCart.ts +0 -161
- package/src/common/DiscountInterface.ts +0 -26
- package/src/common/OrSelector.ts +0 -40
- package/src/common/PostCartAmountQualifier.ts +0 -21
- package/src/common/ProductHandleSelector.ts +0 -32
- package/src/common/ProductIdSelector.ts +0 -34
- package/src/common/ProductTagSelector.ts +0 -63
- package/src/common/ProductTypeSelector.ts +0 -40
- package/src/common/Qualifier.ts +0 -67
- package/src/common/SaleItemSelector.ts +0 -36
- package/src/common/Selector.ts +0 -66
- package/src/common/SubscriptionItemSelector.ts +0 -23
- package/src/lineItem/BuyXGetY.ts +0 -131
- package/src/lineItem/ConditionalDiscount.ts +0 -102
- package/src/lineItem/FixedItemDiscount.ts +0 -51
- package/src/lineItem/PercentageDiscount.ts +0 -44
- package/src/lineItem/api.ts +0 -2103
- package/src/shipping/FixedDiscount.ts +0 -37
- package/src/shipping/RateNameSelector.ts +0 -54
- package/src/shipping/ShippingDiscount.ts +0 -75
- package/src/shipping/api.ts +0 -2014
- package/tests/AndSelector.test.ts +0 -27
- package/tests/CartQuantityQualifier.test.ts +0 -381
- package/tests/CountryCodeQualifier.test.ts +0 -55
- package/tests/CustomerSubscriberQualifier.test.ts +0 -101
- package/tests/CustomerTagQualifier.test.ts +0 -71
- package/tests/DiscountCart.test.ts +0 -115
- package/tests/OrSelector.test.ts +0 -27
- package/tests/ProductIdSelector.test.ts +0 -83
- package/tests/ProductTagSelector.test.ts +0 -75
- package/tests/Qualifier.test.ts +0 -193
- package/tests/RateNameSelector.test.ts +0 -107
- package/tests/SaleItemSelector.test.ts +0 -113
- package/tests/Selector.test.ts +0 -83
- package/tests/ShippingDiscount.test.ts +0 -147
- 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,384 @@
|
|
|
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
|
+
## Tips for Your UI App
|
|
364
|
+
|
|
365
|
+
1. **Store configurations in your database** with fields:
|
|
366
|
+
- `id`, `shop_id`, `campaign_config` (JSON), `priority`, `created_at`, `updated_at`
|
|
367
|
+
|
|
368
|
+
2. **Use the ConfigValidator** before saving:
|
|
369
|
+
```typescript
|
|
370
|
+
import { ConfigValidator } from '@zoxllc/shopify-checkout-extensions';
|
|
371
|
+
|
|
372
|
+
const result = ConfigValidator.validate(userConfig);
|
|
373
|
+
if (!result.valid) {
|
|
374
|
+
return { errors: result.errors };
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
3. **Pass configurations via metafields** to your Shopify Function:
|
|
379
|
+
- Store the JSON configuration in a discount metafield
|
|
380
|
+
- Read it in your Function's `input.discountNode.metafield.value`
|
|
381
|
+
|
|
382
|
+
4. **Test configurations** before activating:
|
|
383
|
+
- Use the `DiscountCart` class with sample cart data
|
|
384
|
+
- Preview what discounts would be applied
|