@liquidcommerce/elements-sdk 2.3.0 → 2.4.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/README.md +25 -21
- package/dist/index.esm.js +8543 -8169
- package/dist/types/core/client/client-action.service.d.ts +2 -2
- package/dist/types/core/google-tag-manager.service.d.ts +1 -0
- package/dist/types/core/pubsub/interfaces/cart.interface.d.ts +1 -0
- package/dist/types/core/pubsub/interfaces/checkout.interface.d.ts +44 -6
- package/dist/types/core/pubsub/interfaces/product.interface.d.ts +43 -1
- package/dist/types/core/store/interfaces/cart.interface.d.ts +1 -0
- package/dist/types/core/store/interfaces/product.interface.d.ts +18 -6
- package/dist/types/interfaces/cloud/product.interface.d.ts +2 -0
- package/dist/types/modules/cart/components/cart-footer.component.d.ts +1 -0
- package/dist/types/modules/cart/components/cart-item.component.d.ts +1 -0
- package/dist/types/modules/checkout/components/checkout-summary-section.component.d.ts +2 -0
- package/dist/types/modules/checkout/components/summary/checkout-items.component.d.ts +1 -0
- package/dist/types/modules/ui-components/engraving/engraving-view.component.d.ts +1 -0
- package/docs/ACTIONS.md +1237 -0
- package/docs/BROWSER_SUPPORT.md +279 -0
- package/docs/CONFIGURATION.md +613 -0
- package/docs/DOCUMENTATION_INDEX.md +311 -0
- package/docs/EVENTS.md +765 -0
- package/docs/PROXY.md +228 -0
- package/docs/THEMING.md +592 -0
- package/docs/TROUBLESHOOTING.md +755 -0
- package/package.json +10 -7
- package/umd/elements.js +1 -1
package/docs/ACTIONS.md
ADDED
|
@@ -0,0 +1,1237 @@
|
|
|
1
|
+
# Elements SDK Actions - Partner Control Guide
|
|
2
|
+
|
|
3
|
+
> **Browser Support**: This SDK supports 2018+ browsers (Chrome 66+, Firefox 60+, Safari 12+) with comprehensive polyfills. See `docs/BROWSER_SUPPORT.md` for details.
|
|
4
|
+
|
|
5
|
+
## What Are Actions and Why Use Them?
|
|
6
|
+
|
|
7
|
+
Actions let you control the LiquidCommerce components programmatically - like having a remote control for your e-commerce experience. Instead of waiting for customers to click buttons, you can make things happen automatically based on your business logic.
|
|
8
|
+
|
|
9
|
+
**Business Benefits:**
|
|
10
|
+
- **Automate User Flow**: Guide customers through the purchase process
|
|
11
|
+
- **Custom Experiences**: Build unique checkout flows that match your brand
|
|
12
|
+
- **Reduce Cart Abandonment**: Pre-fill information and remove friction
|
|
13
|
+
- **Increase Conversions**: Trigger actions at the perfect moment
|
|
14
|
+
- **Integrate with Existing Systems**: Connect to your CRM, inventory, etc.
|
|
15
|
+
|
|
16
|
+
## How It Works & Getting Feedback
|
|
17
|
+
|
|
18
|
+
Actions are **fire-and-forget** methods that return `void` or `Promise<void>`. Instead of return values, **all feedback comes through events** - this is by design for security and flexibility.
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// Actions are available globally
|
|
22
|
+
const actions = window.elements.actions;
|
|
23
|
+
|
|
24
|
+
// 1. Fire the action (simple, clean)
|
|
25
|
+
await actions.cart.applyPromoCode('SAVE20');
|
|
26
|
+
|
|
27
|
+
// 2. Listen for success/failure feedback via events
|
|
28
|
+
window.addEventListener('lce:actions.cart_promo_code_applied', function(event) {
|
|
29
|
+
const { applied, discountAmount, newTotal } = event.detail.data;
|
|
30
|
+
showSuccessMessage(`🎉 Promo applied! You saved $${discountAmount}!`);
|
|
31
|
+
updateCartDisplay(newTotal);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
window.addEventListener('lce:actions.cart_promo_code_failed', function(event) {
|
|
35
|
+
const { attempted, error } = event.detail.data;
|
|
36
|
+
showErrorMessage('Promo code could not be applied. Please try again.');
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 🔒 **Security-First Design**
|
|
41
|
+
|
|
42
|
+
Events **never expose sensitive information** like promo codes, gift card codes, or balance details that could be intercepted by malicious scripts. You get all the feedback you need without security risks.
|
|
43
|
+
|
|
44
|
+
**✅ Safe Data in Events:**
|
|
45
|
+
- Success/failure status
|
|
46
|
+
- Discount amounts (already visible in UI)
|
|
47
|
+
- Total amounts (already visible in UI)
|
|
48
|
+
- Item counts and identifiers
|
|
49
|
+
- Generic error messages
|
|
50
|
+
|
|
51
|
+
**🚫 Never Exposed:**
|
|
52
|
+
- Promo codes or gift card codes
|
|
53
|
+
- Gift card balances or details
|
|
54
|
+
- Specific error messages that reveal codes
|
|
55
|
+
- Any sensitive financial information
|
|
56
|
+
|
|
57
|
+
## Your Action Toolkit
|
|
58
|
+
|
|
59
|
+
### 🛍️ Product Control (`actions.product`)
|
|
60
|
+
|
|
61
|
+
**What it does**: Get information about products to make smart business decisions
|
|
62
|
+
|
|
63
|
+
#### Get Product Details
|
|
64
|
+
```javascript
|
|
65
|
+
// Check product info before making decisions
|
|
66
|
+
const product = actions.product.getDetails('product-123');
|
|
67
|
+
|
|
68
|
+
// Use product attributes for recommendations
|
|
69
|
+
if (product.variety === 'Cabernet Sauvignon' && product.region === 'Napa Valley') {
|
|
70
|
+
showSimilarNapaWines();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Pricing and availability
|
|
74
|
+
if (product.priceInfo && product.priceInfo.avg > 100) {
|
|
75
|
+
showPremiumSupport();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (product.sizes && Object.keys(product.sizes).length > 0) {
|
|
79
|
+
enableAddToCart();
|
|
80
|
+
} else {
|
|
81
|
+
showWaitlistSignup();
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Real Business Uses:**
|
|
86
|
+
- **Dynamic Pricing**: Show different offers based on product price
|
|
87
|
+
- **Inventory Management**: Hide out-of-stock items or show alternatives
|
|
88
|
+
- **Personalization**: Recommend accessories based on main product
|
|
89
|
+
- **A/B Testing**: Show different CTAs for different product categories
|
|
90
|
+
|
|
91
|
+
### 📍 Address Control (`actions.address`)
|
|
92
|
+
|
|
93
|
+
**What it does**: Manage shipping addresses to reduce checkout friction
|
|
94
|
+
|
|
95
|
+
#### Set Address by Google Places ID
|
|
96
|
+
```javascript
|
|
97
|
+
// Set address using Google Places ID - fastest way to capture accurate addresses
|
|
98
|
+
try {
|
|
99
|
+
await actions.address.setAddressByPlacesId('ChIJ0SRjyK5ZwokRp1TwT8dJSv8');
|
|
100
|
+
showMessage("Address set successfully!");
|
|
101
|
+
} catch (error) {
|
|
102
|
+
showMessage("Address not found. Please try again.");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Real-world example: Let customers pick from their recent locations
|
|
106
|
+
const recentPlacesIds = getCustomerRecentPlaces();
|
|
107
|
+
if (recentPlacesIds.length > 0) {
|
|
108
|
+
// Use their most recent address automatically
|
|
109
|
+
await actions.address.setAddressByPlacesId(recentPlacesIds[0]);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Find Google Places IDs**: Use the [Google Places ID Finder](https://developers.google.com/maps/documentation/places/web-service/place-id#find-id) to get Place IDs for any location.
|
|
114
|
+
|
|
115
|
+
#### Set Address Manually
|
|
116
|
+
```javascript
|
|
117
|
+
// Set address manually without Google Places - perfect for custom address forms
|
|
118
|
+
try {
|
|
119
|
+
await actions.address.setAddressManually(
|
|
120
|
+
{
|
|
121
|
+
one: '123 Main St',
|
|
122
|
+
two: 'Apt 4B', // Optional apartment/suite
|
|
123
|
+
city: 'New York',
|
|
124
|
+
state: 'NY',
|
|
125
|
+
zip: '10001',
|
|
126
|
+
country: 'United States' // Optional, will be included in formatted address
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
lat: 40.7505045, // Latitude coordinate
|
|
130
|
+
long: -73.9934387 // Longitude coordinate
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
showMessage("Manual address set successfully!");
|
|
134
|
+
} catch (error) {
|
|
135
|
+
showMessage("Invalid address or coordinates. Please check and try again.");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Real-world example: Custom address form integration
|
|
139
|
+
function handleCustomAddressSubmit(formData) {
|
|
140
|
+
const addressData = {
|
|
141
|
+
one: formData.streetAddress,
|
|
142
|
+
two: formData.apartment || '', // Optional
|
|
143
|
+
city: formData.city,
|
|
144
|
+
state: formData.state,
|
|
145
|
+
zip: formData.zipCode,
|
|
146
|
+
country: formData.country || 'United States'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const coordinates = {
|
|
150
|
+
lat: parseFloat(formData.latitude),
|
|
151
|
+
long: parseFloat(formData.longitude)
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Validate coordinates before setting
|
|
155
|
+
if (coordinates.lat >= -90 && coordinates.lat <= 90 &&
|
|
156
|
+
coordinates.long >= -180 && coordinates.long <= 180) {
|
|
157
|
+
await actions.address.setAddressManually(addressData, coordinates);
|
|
158
|
+
} else {
|
|
159
|
+
showMessage("Invalid coordinates provided");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Integration with geocoding services
|
|
164
|
+
async function setAddressWithGeocoding(streetAddress, city, state, zip) {
|
|
165
|
+
try {
|
|
166
|
+
// Use your preferred geocoding service to get coordinates
|
|
167
|
+
const coordinates = await geocodeAddress(`${streetAddress}, ${city}, ${state} ${zip}`);
|
|
168
|
+
|
|
169
|
+
await actions.address.setAddressManually(
|
|
170
|
+
{
|
|
171
|
+
one: streetAddress,
|
|
172
|
+
two: '',
|
|
173
|
+
city: city,
|
|
174
|
+
state: state,
|
|
175
|
+
zip: zip,
|
|
176
|
+
country: 'United States'
|
|
177
|
+
},
|
|
178
|
+
coordinates
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
showMessage("Address geocoded and set successfully!");
|
|
182
|
+
} catch (error) {
|
|
183
|
+
showMessage("Could not geocode address. Please verify the address.");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Key Features:**
|
|
189
|
+
- **Automatic Formatting**: Generates Google Places API-formatted address strings automatically
|
|
190
|
+
- **Empty Places ID**: Places ID is set to empty string (perfect for non-Google Places addresses)
|
|
191
|
+
- **Flexible Input**: Works with any address structure and coordinate source
|
|
192
|
+
- **Validation**: Built-in validation for required fields and coordinate ranges
|
|
193
|
+
- **Same Events**: Triggers the same success/failure events as Google Places addresses
|
|
194
|
+
|
|
195
|
+
#### Check Address
|
|
196
|
+
```javascript
|
|
197
|
+
// See if customer has an address saved
|
|
198
|
+
const address = actions.address.getDetails();
|
|
199
|
+
|
|
200
|
+
if (address) {
|
|
201
|
+
// Address is saved - express checkout available!
|
|
202
|
+
showExpressCheckout();
|
|
203
|
+
} else {
|
|
204
|
+
// No address - guide them through address entry
|
|
205
|
+
highlightAddressForm();
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Clear Address
|
|
210
|
+
```javascript
|
|
211
|
+
// Clear address (useful for guest checkout option)
|
|
212
|
+
actions.address.clear();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Real Business Uses:**
|
|
216
|
+
- **One-Click Address**: Let customers select from saved Google Places
|
|
217
|
+
- **Custom Address Forms**: Build your own address input forms with full control
|
|
218
|
+
- **Third-Party Integration**: Connect with non-Google geocoding services
|
|
219
|
+
- **Legacy System Migration**: Import addresses from existing customer databases
|
|
220
|
+
- **International Addresses**: Handle addresses that may not be in Google Places
|
|
221
|
+
- **Location-Based Services**: Auto-detect customer location and set address
|
|
222
|
+
- **Express Checkout**: Skip address forms for returning customers
|
|
223
|
+
- **Shipping Calculator**: Calculate shipping costs before checkout
|
|
224
|
+
- **Local Pickup**: Show pickup options based on their address
|
|
225
|
+
- **Tax Calculation**: Display accurate taxes before purchase
|
|
226
|
+
|
|
227
|
+
## 📡 **Complete Action Feedback Events Reference**
|
|
228
|
+
|
|
229
|
+
All actions provide feedback through events. Here are all available success/failure events:
|
|
230
|
+
|
|
231
|
+
### **Cart Action Events**
|
|
232
|
+
```javascript
|
|
233
|
+
// Product management
|
|
234
|
+
'lce:actions.cart_product_add_success' // { itemsAdded: number, identifiers: string[] }
|
|
235
|
+
'lce:actions.cart_product_add_failed' // { identifiers: string[], error: string }
|
|
236
|
+
|
|
237
|
+
// Promo code management
|
|
238
|
+
'lce:actions.cart_promo_code_applied' // { applied: true, discountAmount?: number, newTotal?: number }
|
|
239
|
+
'lce:actions.cart_promo_code_removed' // { applied: false }
|
|
240
|
+
'lce:actions.cart_promo_code_failed' // { attempted: true, error: string }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### **Checkout Action Events**
|
|
244
|
+
```javascript
|
|
245
|
+
// Product management
|
|
246
|
+
'lce:actions.checkout_product_add_success' // { itemsAdded: number, identifiers: string[] }
|
|
247
|
+
'lce:actions.checkout_product_add_failed' // { identifiers: string[], error: string }
|
|
248
|
+
|
|
249
|
+
// Promo code management
|
|
250
|
+
'lce:actions.checkout_promo_code_applied' // { applied: true, discountAmount?: number, newTotal?: number }
|
|
251
|
+
'lce:actions.checkout_promo_code_removed' // { applied: false }
|
|
252
|
+
'lce:actions.checkout_promo_code_failed' // { attempted: true, error: string }
|
|
253
|
+
|
|
254
|
+
// Gift card management
|
|
255
|
+
'lce:actions.checkout_gift_card_applied' // { applied: true, newTotal?: number }
|
|
256
|
+
'lce:actions.checkout_gift_card_removed' // { applied: false }
|
|
257
|
+
'lce:actions.checkout_gift_card_failed' // { attempted: true, error: string }
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### **Address Action Events**
|
|
261
|
+
```javascript
|
|
262
|
+
// Address management (already available)
|
|
263
|
+
'lce:actions.address_updated' // { googlePlacesId: string, formattedAddress: string, address: {...}, coordinates: {...} }
|
|
264
|
+
'lce:actions.address_failed' // { error: string, googlePlacesId?: string }
|
|
265
|
+
'lce:actions.address_cleared' // boolean
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### **Event Handling Best Practices**
|
|
269
|
+
|
|
270
|
+
#### **1. Set Up Listeners Before Actions**
|
|
271
|
+
```javascript
|
|
272
|
+
// ✅ Good: Set up listener first
|
|
273
|
+
window.addEventListener('lce:actions.cart_promo_code_applied', handleSuccess);
|
|
274
|
+
await actions.cart.applyPromoCode('SAVE20');
|
|
275
|
+
|
|
276
|
+
// ❌ Bad: Listener after action (might miss event)
|
|
277
|
+
await actions.cart.applyPromoCode('SAVE20');
|
|
278
|
+
window.addEventListener('lce:actions.cart_promo_code_applied', handleSuccess);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### **2. Handle Both Success and Failure**
|
|
282
|
+
```javascript
|
|
283
|
+
// ✅ Always handle both outcomes
|
|
284
|
+
window.addEventListener('lce:actions.cart_promo_code_applied', handleSuccess);
|
|
285
|
+
window.addEventListener('lce:actions.cart_promo_code_failed', handleFailure);
|
|
286
|
+
await actions.cart.applyPromoCode('SAVE20');
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### **3. Use Event Data Effectively**
|
|
290
|
+
```javascript
|
|
291
|
+
// ✅ Use all available event data
|
|
292
|
+
window.addEventListener('lce:actions.cart_product_add_success', function(event) {
|
|
293
|
+
const { itemsAdded, identifiers } = event.detail.data;
|
|
294
|
+
|
|
295
|
+
// Show success message
|
|
296
|
+
showMessage(`Added ${itemsAdded} items to cart`);
|
|
297
|
+
|
|
298
|
+
// Track analytics
|
|
299
|
+
analytics.track('Products Added', { items: identifiers, count: itemsAdded });
|
|
300
|
+
|
|
301
|
+
// Update UI
|
|
302
|
+
updateCartCount();
|
|
303
|
+
showUpsellRecommendations(identifiers);
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### **4. Create Reusable Event Handlers**
|
|
308
|
+
```javascript
|
|
309
|
+
// ✅ Create reusable handlers
|
|
310
|
+
const handlePromoSuccess = (event) => {
|
|
311
|
+
const { applied, discountAmount, newTotal } = event.detail.data;
|
|
312
|
+
showSuccessMessage(`Discount applied! Saved: $${discountAmount}`);
|
|
313
|
+
updatePriceDisplay(newTotal);
|
|
314
|
+
trackPromoSuccess(discountAmount);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const handlePromoFailure = (event) => {
|
|
318
|
+
const { attempted, error } = event.detail.data;
|
|
319
|
+
showErrorMessage('Promo code could not be applied');
|
|
320
|
+
trackPromoFailure();
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// Use for both cart and checkout
|
|
324
|
+
window.addEventListener('lce:actions.cart_promo_code_applied', handlePromoSuccess);
|
|
325
|
+
window.addEventListener('lce:actions.checkout_promo_code_applied', handlePromoSuccess);
|
|
326
|
+
window.addEventListener('lce:actions.cart_promo_code_failed', handlePromoFailure);
|
|
327
|
+
window.addEventListener('lce:actions.checkout_promo_code_failed', handlePromoFailure);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 🛒 Cart Control (`actions.cart`)
|
|
331
|
+
|
|
332
|
+
**What it does**: Control the shopping cart to optimize conversions
|
|
333
|
+
|
|
334
|
+
#### Show/Hide Cart
|
|
335
|
+
```javascript
|
|
336
|
+
// Show cart at strategic moments
|
|
337
|
+
actions.cart.openCart(); // Open cart
|
|
338
|
+
actions.cart.closeCart(); // Close cart
|
|
339
|
+
actions.cart.toggleCart(); // Toggle visibility
|
|
340
|
+
|
|
341
|
+
// Example: Open cart automatically after second product add
|
|
342
|
+
let addToCartCount = 0;
|
|
343
|
+
window.addEventListener('lce:actions.product_add_to_cart', function() {
|
|
344
|
+
addToCartCount++;
|
|
345
|
+
if (addToCartCount === 2) {
|
|
346
|
+
actions.cart.openCart(); // "Look what you've got!"
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
#### Programmatically Add Products to Cart
|
|
352
|
+
```javascript
|
|
353
|
+
// Add products directly to cart without product component
|
|
354
|
+
await actions.cart.addProduct([
|
|
355
|
+
{
|
|
356
|
+
identifier: 'product-123', // Product UPC, ID, or Salsify grouping
|
|
357
|
+
fulfillmentType: 'shipping', // or 'onDemand'
|
|
358
|
+
quantity: 2
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
identifier: 'product-456',
|
|
362
|
+
fulfillmentType: 'onDemand',
|
|
363
|
+
quantity: 1
|
|
364
|
+
}
|
|
365
|
+
], true); // Optional: open cart after adding (default: false)
|
|
366
|
+
|
|
367
|
+
// Listen for success/failure feedback
|
|
368
|
+
window.addEventListener('lce:actions.cart_product_add_success', function(event) {
|
|
369
|
+
const { itemsAdded, identifiers } = event.detail.data;
|
|
370
|
+
console.log(`✅ Added ${itemsAdded} products:`, identifiers);
|
|
371
|
+
showSuccessMessage('Products added to cart!');
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
window.addEventListener('lce:actions.cart_product_add_failed', function(event) {
|
|
375
|
+
const { identifiers, error } = event.detail.data;
|
|
376
|
+
console.log(`❌ Failed to add products:`, identifiers, error);
|
|
377
|
+
showErrorMessage('Could not add products. Please try again.');
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Real Business Use**: Perfect for "Buy Now" buttons, quick add flows, bundle purchases, or any scenario where you want to add products without displaying the full product component first.
|
|
382
|
+
|
|
383
|
+
#### Check Cart Contents
|
|
384
|
+
```javascript
|
|
385
|
+
// See what's in the cart to make smart decisions
|
|
386
|
+
const cart = actions.cart.getDetails();
|
|
387
|
+
|
|
388
|
+
if (cart.amounts.total > 100) {
|
|
389
|
+
showFreeShippingBanner();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (cart.itemCount === 0) {
|
|
393
|
+
showPopularProducts();
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### Add Products to Cart with Event Feedback
|
|
398
|
+
```javascript
|
|
399
|
+
// Add products programmatically
|
|
400
|
+
await actions.cart.addProduct([{
|
|
401
|
+
identifier: 'product-123',
|
|
402
|
+
fulfillmentType: 'shipping',
|
|
403
|
+
quantity: 1
|
|
404
|
+
}]);
|
|
405
|
+
|
|
406
|
+
// Listen for success
|
|
407
|
+
window.addEventListener('lce:actions.cart_product_add_success', function(event) {
|
|
408
|
+
const { itemsAdded, identifiers } = event.detail.data;
|
|
409
|
+
showMessage(`✅ Added ${itemsAdded} items to cart!`);
|
|
410
|
+
|
|
411
|
+
// Track conversion
|
|
412
|
+
analytics.track('Products Added to Cart', {
|
|
413
|
+
items: identifiers,
|
|
414
|
+
count: itemsAdded
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Maybe show related products
|
|
418
|
+
showRelatedProducts(identifiers);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Listen for failures
|
|
422
|
+
window.addEventListener('lce:actions.cart_product_add_failed', function(event) {
|
|
423
|
+
const { identifiers, error } = event.detail.data;
|
|
424
|
+
showErrorMessage('Could not add some products. Please try again.');
|
|
425
|
+
|
|
426
|
+
// Log for debugging
|
|
427
|
+
console.error('Failed to add products:', identifiers, error);
|
|
428
|
+
|
|
429
|
+
// Show alternatives
|
|
430
|
+
showAlternativeProducts(identifiers);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Add bundles or cross-sells
|
|
434
|
+
await actions.cart.addProduct([
|
|
435
|
+
{ identifier: 'phone-123', fulfillmentType: 'shipping', quantity: 1 },
|
|
436
|
+
{ identifier: 'case-456', fulfillmentType: 'shipping', quantity: 1 },
|
|
437
|
+
{ identifier: 'charger-789', fulfillmentType: 'shipping', quantity: 1 }
|
|
438
|
+
]);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
#### Apply Discounts with Event Feedback
|
|
442
|
+
```javascript
|
|
443
|
+
// Apply promo codes automatically
|
|
444
|
+
await actions.cart.applyPromoCode('WELCOME10');
|
|
445
|
+
|
|
446
|
+
// Listen for success
|
|
447
|
+
window.addEventListener('lce:actions.cart_promo_code_applied', function(event) {
|
|
448
|
+
const { applied, discountAmount, newTotal } = event.detail.data;
|
|
449
|
+
showMessage(`Welcome discount applied! You saved $${discountAmount}!`);
|
|
450
|
+
updateCartDisplay(newTotal);
|
|
451
|
+
|
|
452
|
+
// Track successful promo usage
|
|
453
|
+
analytics.track('Promo Applied', { discount: discountAmount });
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// Listen for failure and try fallback
|
|
457
|
+
window.addEventListener('lce:actions.cart_promo_code_failed', function(event) {
|
|
458
|
+
const { attempted, error } = event.detail.data;
|
|
459
|
+
console.log('First promo failed, trying backup promo');
|
|
460
|
+
|
|
461
|
+
// Try another strategy
|
|
462
|
+
actions.cart.applyPromoCode('FIRSTTIME');
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Remove promo code with feedback
|
|
466
|
+
await actions.cart.removePromoCode();
|
|
467
|
+
|
|
468
|
+
// Listen for removal confirmation
|
|
469
|
+
window.addEventListener('lce:actions.cart_promo_code_removed', function(event) {
|
|
470
|
+
const { applied } = event.detail.data;
|
|
471
|
+
showMessage("Previous promo removed. Applying better discount...");
|
|
472
|
+
|
|
473
|
+
// Apply better deal
|
|
474
|
+
actions.cart.applyPromoCode('BETTER20');
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
#### Reset Cart
|
|
479
|
+
```javascript
|
|
480
|
+
// Clear cart for fresh start
|
|
481
|
+
await actions.cart.resetCart();
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**Real Business Uses:**
|
|
485
|
+
- **Smart Cart Timing**: Open cart when customer is most likely to buy
|
|
486
|
+
- **Bundle Sales**: Add complementary products automatically
|
|
487
|
+
- **Discount Strategy**: Apply best available discount automatically
|
|
488
|
+
- **Abandonment Prevention**: Show cart contents at right moment
|
|
489
|
+
|
|
490
|
+
### 💳 Checkout Control (`actions.checkout`)
|
|
491
|
+
|
|
492
|
+
**What it does**: Streamline the checkout process to maximize conversions
|
|
493
|
+
|
|
494
|
+
#### Control Checkout Flow
|
|
495
|
+
```javascript
|
|
496
|
+
// Open checkout at the perfect moment
|
|
497
|
+
actions.checkout.openCheckout();
|
|
498
|
+
actions.checkout.closeCheckout();
|
|
499
|
+
actions.checkout.toggleCheckout();
|
|
500
|
+
|
|
501
|
+
// Example: Open checkout automatically for high-value carts
|
|
502
|
+
const cart = actions.cart.getDetails();
|
|
503
|
+
if (cart.amounts.total > 500) {
|
|
504
|
+
actions.checkout.openCheckout(); // VIP checkout experience
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
#### Programmatically Add Products to Checkout
|
|
509
|
+
```javascript
|
|
510
|
+
// Add products directly to checkout (bypasses cart, goes straight to checkout)
|
|
511
|
+
await actions.checkout.addProduct([
|
|
512
|
+
{
|
|
513
|
+
identifier: 'product-123',
|
|
514
|
+
fulfillmentType: 'shipping',
|
|
515
|
+
quantity: 1
|
|
516
|
+
}
|
|
517
|
+
], true); // Optional: open checkout after adding (default: false)
|
|
518
|
+
|
|
519
|
+
// Listen for success/failure feedback
|
|
520
|
+
window.addEventListener('lce:actions.checkout_product_add_success', function(event) {
|
|
521
|
+
const { itemsAdded, identifiers } = event.detail.data;
|
|
522
|
+
console.log(`✅ Added ${itemsAdded} products to checkout:`, identifiers);
|
|
523
|
+
showSuccessMessage('Ready for checkout!');
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
window.addEventListener('lce:actions.checkout_product_add_failed', function(event) {
|
|
527
|
+
const { identifiers, error } = event.detail.data;
|
|
528
|
+
console.log(`❌ Failed to add products to checkout:`, identifiers, error);
|
|
529
|
+
showErrorMessage('Could not proceed to checkout. Please try again.');
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Real Business Use**: Perfect for "Buy Now" buttons, one-click purchasing, express checkout flows, or any scenario where you want to skip the cart and go straight to checkout.
|
|
534
|
+
|
|
535
|
+
#### Pre-fill Customer Information
|
|
536
|
+
```javascript
|
|
537
|
+
// Speed up checkout by pre-filling known information
|
|
538
|
+
actions.checkout.updateCustomerInfo({
|
|
539
|
+
firstName: 'John',
|
|
540
|
+
lastName: 'Doe',
|
|
541
|
+
email: 'john@example.com',
|
|
542
|
+
phone: '+1234567890'
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Pre-fill billing from your CRM
|
|
546
|
+
const customerData = getCRMData(customerId);
|
|
547
|
+
actions.checkout.updateBillingInfo({
|
|
548
|
+
firstName: customerData.firstName,
|
|
549
|
+
lastName: customerData.lastName,
|
|
550
|
+
street1: customerData.address,
|
|
551
|
+
city: customerData.city,
|
|
552
|
+
state: customerData.state,
|
|
553
|
+
zipCode: customerData.zip
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
#### Smart Discount Application with Event Feedback
|
|
558
|
+
```javascript
|
|
559
|
+
// Apply the best available discount automatically
|
|
560
|
+
const customer = getCurrentCustomer();
|
|
561
|
+
|
|
562
|
+
// Set up event listeners first
|
|
563
|
+
window.addEventListener('lce:actions.checkout_promo_code_applied', function(event) {
|
|
564
|
+
const { applied, discountAmount, newTotal } = event.detail.data;
|
|
565
|
+
showCheckoutSuccess(`Discount applied! You saved $${discountAmount}`);
|
|
566
|
+
updateCheckoutTotal(newTotal);
|
|
567
|
+
|
|
568
|
+
// Track successful conversion optimization
|
|
569
|
+
analytics.track('Checkout Discount Applied', { discount: discountAmount });
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
window.addEventListener('lce:actions.checkout_promo_code_failed', function(event) {
|
|
573
|
+
const { attempted, error } = event.detail.data;
|
|
574
|
+
console.log('Promo code failed:', error);
|
|
575
|
+
|
|
576
|
+
// Try fallback strategies or notify user
|
|
577
|
+
showCheckoutError('Discount could not be applied');
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
// Apply gift card event listeners
|
|
581
|
+
window.addEventListener('lce:actions.checkout_gift_card_applied', function(event) {
|
|
582
|
+
const { applied, newTotal } = event.detail.data;
|
|
583
|
+
showCheckoutSuccess('Gift card applied to your order!');
|
|
584
|
+
updateCheckoutTotal(newTotal);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
window.addEventListener('lce:actions.checkout_gift_card_failed', function(event) {
|
|
588
|
+
const { attempted, error } = event.detail.data;
|
|
589
|
+
showCheckoutError('Gift card could not be applied. Please check and try again.');
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// Now apply the discounts
|
|
593
|
+
if (customer.isFirstTime) {
|
|
594
|
+
await actions.checkout.applyPromoCode('WELCOME20');
|
|
595
|
+
} else if (customer.isVIP) {
|
|
596
|
+
await actions.checkout.applyPromoCode('VIP15');
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Apply gift cards automatically
|
|
600
|
+
if (customer.hasGiftCard) {
|
|
601
|
+
await actions.checkout.applyGiftCard(customer.giftCardCode);
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
#### Optimize Checkout Options
|
|
606
|
+
```javascript
|
|
607
|
+
// Smart defaults based on customer behavior
|
|
608
|
+
if (customer.prefersGifts) {
|
|
609
|
+
await actions.checkout.toggleIsGift(true);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Auto-enable marketing for engaged customers
|
|
613
|
+
if (customer.engagementScore > 8) {
|
|
614
|
+
actions.checkout.toggleMarketingPreferences('canEmail', true);
|
|
615
|
+
actions.checkout.toggleMarketingPreferences('canSms', true);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Smart billing address handling
|
|
619
|
+
if (customer.billingAddress === customer.shippingAddress) {
|
|
620
|
+
await actions.checkout.toggleBillingSameAsShipping(true);
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
#### Get Checkout Details
|
|
625
|
+
```javascript
|
|
626
|
+
// Get safe, non-sensitive checkout information
|
|
627
|
+
const checkout = actions.checkout.getDetails();
|
|
628
|
+
|
|
629
|
+
// Make business decisions based on checkout state
|
|
630
|
+
if (checkout.amounts.total > 1000) {
|
|
631
|
+
// High-value order - offer white glove service
|
|
632
|
+
showWhiteGloveService();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (checkout.itemCount > 5) {
|
|
636
|
+
// Large order - offer bulk discount
|
|
637
|
+
await actions.checkout.applyPromoCode('BULK15');
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (checkout.isGift && checkout.hasAgeVerify) {
|
|
641
|
+
// Gift order with age verification - show age verification requirements
|
|
642
|
+
showAgeVerificationNotice();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (!checkout.hasPromoCode) {
|
|
646
|
+
// No promo code applied - suggest best available discount
|
|
647
|
+
suggestBestPromoCode(checkout.amounts.total);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (checkout.hasGiftCards && checkout.amounts.total < 50) {
|
|
651
|
+
// Gift cards applied but low total - suggest adding more items
|
|
652
|
+
showUpsellSuggestions();
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Available checkout details (all non-sensitive):
|
|
656
|
+
// - amounts: { total, subtotal, shipping, discounts, tax, etc. }
|
|
657
|
+
// - items: { [itemId]: { name, price, quantity, brand, etc. } }
|
|
658
|
+
// - isGift: boolean, hasAgeVerify: boolean
|
|
659
|
+
// - hasPromoCode: boolean, hasGiftCards: boolean
|
|
660
|
+
// - itemCount: number
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**Real Business Uses:**
|
|
664
|
+
- **Reduce Checkout Time**: Pre-fill known customer information
|
|
665
|
+
- **Maximize Discounts**: Apply best available offer automatically
|
|
666
|
+
- **VIP Experience**: Different checkout flow for high-value customers
|
|
667
|
+
- **Gift Optimization**: Auto-enable gift options for gift buyers
|
|
668
|
+
|
|
669
|
+
## Real-World Business Scenarios
|
|
670
|
+
|
|
671
|
+
### 🎯 Scenario 1: VIP Customer Experience
|
|
672
|
+
```javascript
|
|
673
|
+
// Detect VIP customers and give them special treatment
|
|
674
|
+
async function handleVIPCustomer(customer) {
|
|
675
|
+
if (customer.totalSpent > 5000) {
|
|
676
|
+
// Pre-fill everything for express checkout
|
|
677
|
+
actions.checkout.updateCustomerInfo(customer.profile);
|
|
678
|
+
actions.checkout.updateBillingInfo(customer.billingAddress);
|
|
679
|
+
|
|
680
|
+
// Apply VIP discount automatically
|
|
681
|
+
await actions.checkout.applyPromoCode('VIP20');
|
|
682
|
+
|
|
683
|
+
// Enable premium features
|
|
684
|
+
actions.checkout.toggleMarketingPreferences('canEmail', true);
|
|
685
|
+
|
|
686
|
+
// Open checkout immediately - no cart friction
|
|
687
|
+
actions.checkout.openCheckout();
|
|
688
|
+
|
|
689
|
+
showVIPMessage("Welcome back! Your VIP checkout is ready.");
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### 🛍️ Scenario 2: Smart Bundle Sales
|
|
695
|
+
```javascript
|
|
696
|
+
// Automatically add complementary products based on specific product identifiers
|
|
697
|
+
window.addEventListener('lce:actions.product_add_to_cart', async function(event) {
|
|
698
|
+
const data = event.detail.data;
|
|
699
|
+
|
|
700
|
+
// Define cross-sell rules by identifier
|
|
701
|
+
const crossSellMap = {
|
|
702
|
+
'laptop-001': ['laptop-case-123', 'wireless-mouse-456'],
|
|
703
|
+
'camera-001': ['camera-bag-789', 'sd-card-512gb']
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// Check if this product has recommended accessories
|
|
707
|
+
if (crossSellMap[data.identifier]) {
|
|
708
|
+
const accessories = crossSellMap[data.identifier];
|
|
709
|
+
await actions.cart.addProduct(
|
|
710
|
+
accessories.map(id => ({
|
|
711
|
+
identifier: id,
|
|
712
|
+
fulfillmentType: 'shipping',
|
|
713
|
+
quantity: 1
|
|
714
|
+
}))
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
showMessage("We've added recommended accessories to your cart!");
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### 📱 Scenario 3: Mobile-First Quick Checkout
|
|
723
|
+
```javascript
|
|
724
|
+
// Streamlined mobile experience
|
|
725
|
+
function mobileQuickCheckout() {
|
|
726
|
+
if (isMobileDevice()) {
|
|
727
|
+
// Get customer data from their last purchase
|
|
728
|
+
const lastOrder = getLastOrder();
|
|
729
|
+
|
|
730
|
+
if (lastOrder) {
|
|
731
|
+
// Pre-fill everything
|
|
732
|
+
actions.checkout.updateCustomerInfo(lastOrder.customer);
|
|
733
|
+
actions.checkout.updateBillingInfo(lastOrder.billing);
|
|
734
|
+
|
|
735
|
+
// Skip cart, go straight to checkout
|
|
736
|
+
actions.checkout.openCheckout();
|
|
737
|
+
|
|
738
|
+
showMessage("Express checkout ready! Same info as last time.");
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Trigger on Add to Cart for mobile users
|
|
744
|
+
window.addEventListener('lce:actions.product_add_to_cart', function() {
|
|
745
|
+
if (isMobileDevice() && isReturningCustomer()) {
|
|
746
|
+
mobileQuickCheckout();
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
### 💰 Scenario 4: Dynamic Pricing & Promotions
|
|
752
|
+
```javascript
|
|
753
|
+
// Apply the best available discount
|
|
754
|
+
async function optimizeCheckout() {
|
|
755
|
+
const cart = actions.cart.getDetails();
|
|
756
|
+
const customer = getCurrentCustomer();
|
|
757
|
+
|
|
758
|
+
// Try different discount strategies
|
|
759
|
+
const discountStrategies = [
|
|
760
|
+
{ code: 'BULK25', minTotal: 200 },
|
|
761
|
+
{ code: 'RETURNING15', condition: customer.orderCount > 1 },
|
|
762
|
+
{ code: 'FIRSTTIME10', condition: customer.orderCount === 0 },
|
|
763
|
+
{ code: 'SEASONAL20', condition: isHolidaySeason() }
|
|
764
|
+
];
|
|
765
|
+
|
|
766
|
+
for (const strategy of discountStrategies) {
|
|
767
|
+
if (strategy.minTotal && cart.amounts.total >= strategy.minTotal) {
|
|
768
|
+
try {
|
|
769
|
+
await actions.checkout.applyPromoCode(strategy.code);
|
|
770
|
+
break; // Success, stop trying
|
|
771
|
+
} catch (error) {
|
|
772
|
+
continue; // Try next strategy
|
|
773
|
+
}
|
|
774
|
+
} else if (strategy.condition) {
|
|
775
|
+
try {
|
|
776
|
+
await actions.checkout.applyPromoCode(strategy.code);
|
|
777
|
+
break;
|
|
778
|
+
} catch (error) {
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
### 🎁 Scenario 5: Holiday Gift Flow
|
|
787
|
+
```javascript
|
|
788
|
+
// Optimize for gift purchases during holidays
|
|
789
|
+
async function holidayGiftOptimization() {
|
|
790
|
+
if (isHolidaySeason()) {
|
|
791
|
+
// Auto-enable gift features
|
|
792
|
+
actions.checkout.toggleIsGift(true);
|
|
793
|
+
|
|
794
|
+
// Pre-populate gift message templates
|
|
795
|
+
const giftMessages = [
|
|
796
|
+
"Happy Holidays!",
|
|
797
|
+
"Merry Christmas!",
|
|
798
|
+
"With love and best wishes"
|
|
799
|
+
];
|
|
800
|
+
|
|
801
|
+
showGiftMessageSuggestions(giftMessages);
|
|
802
|
+
|
|
803
|
+
// Suggest gift wrapping
|
|
804
|
+
showGiftWrappingOption();
|
|
805
|
+
|
|
806
|
+
// Apply holiday shipping discount
|
|
807
|
+
await actions.checkout.applyPromoCode('FREEHOLIDAYSHIP');
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Trigger during checkout
|
|
812
|
+
window.addEventListener('lce:actions.checkout_opened', holidayGiftOptimization);
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### 📍 Scenario 6: Smart Address Auto-Complete
|
|
816
|
+
```javascript
|
|
817
|
+
// Use Google Places to streamline address entry
|
|
818
|
+
async function smartAddressFlow() {
|
|
819
|
+
const customer = getCurrentCustomer();
|
|
820
|
+
|
|
821
|
+
// For returning customers, use their recent addresses
|
|
822
|
+
if (customer.recentPlacesIds && customer.recentPlacesIds.length > 0) {
|
|
823
|
+
try {
|
|
824
|
+
// Auto-set their most recent address
|
|
825
|
+
await actions.address.setAddressByPlacesId(customer.recentPlacesIds[0]);
|
|
826
|
+
|
|
827
|
+
showMessage("We've set your address to your recent location. Change it if needed!");
|
|
828
|
+
|
|
829
|
+
// Show other recent addresses as quick options
|
|
830
|
+
showRecentAddressOptions(customer.recentPlacesIds.slice(1, 4));
|
|
831
|
+
|
|
832
|
+
} catch (error) {
|
|
833
|
+
// Fallback to manual entry
|
|
834
|
+
showAddressForm();
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// For location-aware features
|
|
839
|
+
if (customer.hasLocationPermission) {
|
|
840
|
+
const nearbyBusinesses = await getNearbyBusinessPlaces();
|
|
841
|
+
|
|
842
|
+
// If they're near a business location, suggest it
|
|
843
|
+
if (nearbyBusinesses.length > 0) {
|
|
844
|
+
showMessage("We found you're near our store location. Use it for pickup?");
|
|
845
|
+
|
|
846
|
+
document.getElementById('use-store-address').onclick = async () => {
|
|
847
|
+
await actions.address.setAddressByPlacesId(nearbyBusinesses[0].placeId);
|
|
848
|
+
showMessage("Store pickup address set!");
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Use when address is updated
|
|
855
|
+
smartAddressFlow();
|
|
856
|
+
|
|
857
|
+
// Quick address selection from saved places
|
|
858
|
+
async function useQuickAddress(placeId, displayName) {
|
|
859
|
+
try {
|
|
860
|
+
await actions.address.setAddressByPlacesId(placeId);
|
|
861
|
+
showMessage(`Address set to ${displayName}`);
|
|
862
|
+
|
|
863
|
+
// Automatically proceed to shipping options
|
|
864
|
+
showShippingOptionsForAddress();
|
|
865
|
+
|
|
866
|
+
} catch (error) {
|
|
867
|
+
showMessage("That address is no longer available. Please select another.");
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
### 🏢 Scenario 7: Custom Address Form Integration
|
|
873
|
+
```javascript
|
|
874
|
+
// Integrate with your own address forms and geocoding services
|
|
875
|
+
async function customAddressFormFlow() {
|
|
876
|
+
const customer = getCurrentCustomer();
|
|
877
|
+
|
|
878
|
+
// If customer has saved addresses in your database (not Google Places)
|
|
879
|
+
if (customer.savedAddresses && customer.savedAddresses.length > 0) {
|
|
880
|
+
showSavedAddressOptions(customer.savedAddresses);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Handle custom form submission
|
|
884
|
+
document.getElementById('custom-address-form').addEventListener('submit', async (event) => {
|
|
885
|
+
event.preventDefault();
|
|
886
|
+
|
|
887
|
+
const formData = new FormData(event.target);
|
|
888
|
+
const addressData = {
|
|
889
|
+
one: formData.get('street'),
|
|
890
|
+
two: formData.get('apartment') || '',
|
|
891
|
+
city: formData.get('city'),
|
|
892
|
+
state: formData.get('state'),
|
|
893
|
+
zip: formData.get('zip'),
|
|
894
|
+
country: formData.get('country') || 'United States'
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
try {
|
|
898
|
+
// Use your geocoding service to get coordinates
|
|
899
|
+
const coordinates = await yourGeocodingService.geocode(
|
|
900
|
+
`${addressData.one}, ${addressData.city}, ${addressData.state} ${addressData.zip}`
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
// Set the address manually
|
|
904
|
+
await actions.address.setAddressManually(addressData, {
|
|
905
|
+
lat: coordinates.latitude,
|
|
906
|
+
long: coordinates.longitude
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
showMessage("Your address has been set successfully!");
|
|
910
|
+
|
|
911
|
+
// Save to your database for future use
|
|
912
|
+
saveCustomerAddress(customer.id, addressData, coordinates);
|
|
913
|
+
|
|
914
|
+
} catch (geocodeError) {
|
|
915
|
+
showError("Could not find coordinates for this address. Please verify and try again.");
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// Handle saved address selection
|
|
920
|
+
async function useSavedAddress(savedAddress) {
|
|
921
|
+
try {
|
|
922
|
+
await actions.address.setAddressManually(
|
|
923
|
+
{
|
|
924
|
+
one: savedAddress.street,
|
|
925
|
+
two: savedAddress.apartment || '',
|
|
926
|
+
city: savedAddress.city,
|
|
927
|
+
state: savedAddress.state,
|
|
928
|
+
zip: savedAddress.zip,
|
|
929
|
+
country: savedAddress.country || 'United States'
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
lat: savedAddress.coordinates.lat,
|
|
933
|
+
long: savedAddress.coordinates.long
|
|
934
|
+
}
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
showMessage(`Address set to your saved location: ${savedAddress.nickname}`);
|
|
938
|
+
|
|
939
|
+
} catch (error) {
|
|
940
|
+
showError("There was an issue with your saved address. Please try entering it again.");
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Import addresses from legacy system
|
|
946
|
+
async function importLegacyAddresses() {
|
|
947
|
+
const legacyAddresses = await getLegacyCustomerAddresses();
|
|
948
|
+
|
|
949
|
+
for (const address of legacyAddresses) {
|
|
950
|
+
try {
|
|
951
|
+
// Convert legacy format to Elements format
|
|
952
|
+
const elementsAddress = {
|
|
953
|
+
one: address.address_line_1,
|
|
954
|
+
two: address.address_line_2 || '',
|
|
955
|
+
city: address.city_name,
|
|
956
|
+
state: address.state_code,
|
|
957
|
+
zip: address.postal_code,
|
|
958
|
+
country: address.country_name || 'United States'
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
const coordinates = {
|
|
962
|
+
lat: address.latitude,
|
|
963
|
+
long: address.longitude
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
// Set address in Elements system
|
|
967
|
+
await actions.address.setAddressManually(elementsAddress, coordinates);
|
|
968
|
+
|
|
969
|
+
console.log(`Successfully imported address for customer ${address.customer_id}`);
|
|
970
|
+
|
|
971
|
+
} catch (error) {
|
|
972
|
+
console.error(`Failed to import address for customer ${address.customer_id}:`, error);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Handle international addresses that might not be in Google Places
|
|
978
|
+
async function handleInternationalAddress(internationalAddressData) {
|
|
979
|
+
try {
|
|
980
|
+
// Use an international geocoding service
|
|
981
|
+
const coordinates = await internationalGeocodingService.geocode(
|
|
982
|
+
internationalAddressData.fullAddress,
|
|
983
|
+
internationalAddressData.country
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
await actions.address.setAddressManually(
|
|
987
|
+
{
|
|
988
|
+
one: internationalAddressData.addressLine1,
|
|
989
|
+
two: internationalAddressData.addressLine2 || '',
|
|
990
|
+
city: internationalAddressData.city,
|
|
991
|
+
state: internationalAddressData.region, // Province, state, etc.
|
|
992
|
+
zip: internationalAddressData.postalCode,
|
|
993
|
+
country: internationalAddressData.country
|
|
994
|
+
},
|
|
995
|
+
coordinates
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
showMessage("International address set successfully!");
|
|
999
|
+
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
showError("Could not process this international address. Please contact support.");
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
## Implementation Strategy
|
|
1007
|
+
|
|
1008
|
+
### 🚀 Quick Start (5-Minute Setup)
|
|
1009
|
+
|
|
1010
|
+
**Step 1: Choose Your Goal**
|
|
1011
|
+
- Increase conversions? → Focus on cart and checkout actions
|
|
1012
|
+
- Reduce abandonment? → Use pre-filling and automation
|
|
1013
|
+
- Boost sales? → Implement bundle and upsell logic
|
|
1014
|
+
- Improve UX? → Create smart defaults and express flows
|
|
1015
|
+
|
|
1016
|
+
**Step 2: Start Simple**
|
|
1017
|
+
```javascript
|
|
1018
|
+
// Copy this basic template and customize
|
|
1019
|
+
window.addEventListener('lce:actions.product_add_to_cart', async function(event) {
|
|
1020
|
+
const product = event.detail.data;
|
|
1021
|
+
|
|
1022
|
+
// Your business logic here
|
|
1023
|
+
if (product.priceInfo && product.priceInfo.avg > 100) {
|
|
1024
|
+
// High-value product - offer premium support
|
|
1025
|
+
showPremiumSupportOffer();
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Maybe add complementary products based on identifier
|
|
1029
|
+
const crossSellMap = {
|
|
1030
|
+
'phone-001': 'phone-case-recommended',
|
|
1031
|
+
'laptop-001': 'laptop-case-recommended'
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
if (crossSellMap[product.identifier]) {
|
|
1035
|
+
await actions.cart.addProduct([{
|
|
1036
|
+
identifier: crossSellMap[product.identifier],
|
|
1037
|
+
fulfillmentType: 'shipping',
|
|
1038
|
+
quantity: 1
|
|
1039
|
+
}]);
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
**Step 3: Test & Iterate**
|
|
1045
|
+
1. Add one action at a time
|
|
1046
|
+
2. Test with real user flows
|
|
1047
|
+
3. Monitor conversion impact
|
|
1048
|
+
4. Adjust based on results
|
|
1049
|
+
|
|
1050
|
+
### 💡 Pro Implementation Tips
|
|
1051
|
+
|
|
1052
|
+
#### Combine Actions with Events
|
|
1053
|
+
```javascript
|
|
1054
|
+
// Listen for events, respond with actions
|
|
1055
|
+
window.addEventListener('lce:actions.cart_opened', function() {
|
|
1056
|
+
const cart = actions.cart.getDetails();
|
|
1057
|
+
|
|
1058
|
+
// If cart value is close to free shipping threshold
|
|
1059
|
+
if (cart.amounts.total > 45 && cart.amounts.total < 50) {
|
|
1060
|
+
showFreeShippingUpsell();
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
// Pre-fill checkout for VIP customers
|
|
1065
|
+
window.addEventListener('lce:actions.checkout_opened', async function() {
|
|
1066
|
+
const customer = getCurrentCustomer();
|
|
1067
|
+
|
|
1068
|
+
if (customer.isVIP) {
|
|
1069
|
+
actions.checkout.updateCustomerInfo(customer.profile);
|
|
1070
|
+
await actions.checkout.applyPromoCode('VIP20');
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
#### Chain Actions for Workflows
|
|
1076
|
+
```javascript
|
|
1077
|
+
// Create complete automated flows
|
|
1078
|
+
async function expressCheckoutFlow(productId, customerId) {
|
|
1079
|
+
// 1. Add product to cart
|
|
1080
|
+
await actions.cart.addProduct([{
|
|
1081
|
+
identifier: productId,
|
|
1082
|
+
fulfillmentType: 'shipping',
|
|
1083
|
+
quantity: 1
|
|
1084
|
+
}]);
|
|
1085
|
+
|
|
1086
|
+
// 2. Pre-fill customer data
|
|
1087
|
+
const customer = getCustomerData(customerId);
|
|
1088
|
+
actions.checkout.updateCustomerInfo(customer);
|
|
1089
|
+
|
|
1090
|
+
// 3. Apply best discount
|
|
1091
|
+
await actions.checkout.applyPromoCode(getBestDiscountFor(customer));
|
|
1092
|
+
|
|
1093
|
+
// 4. Open checkout
|
|
1094
|
+
actions.checkout.openCheckout();
|
|
1095
|
+
|
|
1096
|
+
showMessage("Express checkout ready! Review and complete your order.");
|
|
1097
|
+
}
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
#### Smart Error Handling
|
|
1101
|
+
```javascript
|
|
1102
|
+
// Graceful degradation
|
|
1103
|
+
async function smartAddToCart(productId) {
|
|
1104
|
+
try {
|
|
1105
|
+
await actions.cart.addProduct([{
|
|
1106
|
+
identifier: productId,
|
|
1107
|
+
fulfillmentType: 'shipping',
|
|
1108
|
+
quantity: 1
|
|
1109
|
+
}]);
|
|
1110
|
+
|
|
1111
|
+
showSuccessMessage("Added to cart!");
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
// Fallback - maybe the product is out of stock
|
|
1114
|
+
showMessage("This item isn't available, but here are similar options:");
|
|
1115
|
+
showAlternativeProducts(productId);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
## Common Integration Patterns
|
|
1121
|
+
|
|
1122
|
+
### 🎯 **Pattern 1: Behavioral Triggers**
|
|
1123
|
+
```javascript
|
|
1124
|
+
// Trigger actions based on customer behavior
|
|
1125
|
+
let pageViewCount = 0;
|
|
1126
|
+
let timeOnPage = 0;
|
|
1127
|
+
|
|
1128
|
+
// Track engagement
|
|
1129
|
+
setInterval(() => timeOnPage++, 1000);
|
|
1130
|
+
|
|
1131
|
+
// Act based on engagement
|
|
1132
|
+
if (timeOnPage > 60 && pageViewCount > 3) {
|
|
1133
|
+
// Engaged user - show special offer
|
|
1134
|
+
await actions.cart.applyPromoCode('ENGAGED15');
|
|
1135
|
+
showSpecialOffer();
|
|
1136
|
+
}
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
### 🛍️ **Pattern 2: Cross-Sell Automation**
|
|
1140
|
+
```javascript
|
|
1141
|
+
// Automatic product recommendations based on identifier
|
|
1142
|
+
const crossSellRules = {
|
|
1143
|
+
'laptop-001': ['laptop-case', 'wireless-mouse', 'usb-hub'],
|
|
1144
|
+
'phone-001': ['phone-case', 'screen-protector', 'wireless-charger'],
|
|
1145
|
+
'camera-001': ['memory-card', 'camera-bag', 'tripod']
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
window.addEventListener('lce:actions.product_add_to_cart', async function(event) {
|
|
1149
|
+
const data = event.detail.data;
|
|
1150
|
+
const recommendations = crossSellRules[data.identifier];
|
|
1151
|
+
|
|
1152
|
+
if (recommendations) {
|
|
1153
|
+
// Add first recommendation automatically
|
|
1154
|
+
await actions.cart.addProduct([{
|
|
1155
|
+
identifier: recommendations[0],
|
|
1156
|
+
fulfillmentType: 'shipping',
|
|
1157
|
+
quantity: 1
|
|
1158
|
+
}]);
|
|
1159
|
+
|
|
1160
|
+
showMessage(`We've added ${recommendations[0]} - customers love this combo!`);
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
### 💰 **Pattern 3: Dynamic Pricing**
|
|
1166
|
+
```javascript
|
|
1167
|
+
// Apply best available discount automatically
|
|
1168
|
+
async function optimizePricing() {
|
|
1169
|
+
const cart = actions.cart.getDetails();
|
|
1170
|
+
const customer = getCurrentCustomer();
|
|
1171
|
+
const now = new Date();
|
|
1172
|
+
|
|
1173
|
+
// Time-based discounts
|
|
1174
|
+
if (now.getHours() < 10) {
|
|
1175
|
+
await actions.cart.applyPromoCode('EARLYBIRD10');
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// Volume discounts
|
|
1179
|
+
else if (cart.amounts.total > 200) {
|
|
1180
|
+
await actions.cart.applyPromoCode('BULK20');
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Customer-specific discounts
|
|
1184
|
+
else if (customer.orderCount === 0) {
|
|
1185
|
+
await actions.cart.applyPromoCode('WELCOME15');
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// Seasonal discounts
|
|
1189
|
+
else if (isBlackFriday()) {
|
|
1190
|
+
await actions.cart.applyPromoCode('BLACKFRIDAY25');
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
## Business ROI Tracking
|
|
1196
|
+
|
|
1197
|
+
Track the impact of your action implementations:
|
|
1198
|
+
|
|
1199
|
+
```javascript
|
|
1200
|
+
// Track business metrics
|
|
1201
|
+
const metrics = {
|
|
1202
|
+
automatedDiscountsApplied: 0,
|
|
1203
|
+
bundleSalesCreated: 0,
|
|
1204
|
+
expressCheckoutsCompleted: 0,
|
|
1205
|
+
cartAbandonmentsPrevented: 0
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
// Track when actions create business value
|
|
1209
|
+
window.addEventListener('lce:actions.cart_updated', function() {
|
|
1210
|
+
if (wasDiscountAppliedAutomatically()) {
|
|
1211
|
+
metrics.automatedDiscountsApplied++;
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
window.addEventListener('lce:actions.checkout_submit_completed', function() {
|
|
1216
|
+
if (wasExpressCheckout()) {
|
|
1217
|
+
metrics.expressCheckoutsCompleted++;
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
// Send metrics to your analytics
|
|
1222
|
+
setInterval(() => {
|
|
1223
|
+
sendBusinessMetrics(metrics);
|
|
1224
|
+
}, 60000); // Every minute
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
## Getting Started Today
|
|
1228
|
+
|
|
1229
|
+
1. **Pick ONE scenario** from the examples above that matches your biggest business need
|
|
1230
|
+
2. **Copy the code** and customize it for your products/customers
|
|
1231
|
+
3. **Test it** with a small group first
|
|
1232
|
+
4. **Measure the impact** on your conversion rates
|
|
1233
|
+
5. **Scale up** what works, iterate on what doesn't
|
|
1234
|
+
|
|
1235
|
+
**Remember**: The power is in combining the right actions with the right business logic for YOUR specific use case. Start simple, then build more sophisticated flows as you see results!
|
|
1236
|
+
|
|
1237
|
+
Need help implementing? The actions work automatically once LiquidCommerce Elements are installed - just add your business logic and watch conversions improve!
|