@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.
@@ -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!