omni-sync-sdk 0.20.3 → 0.21.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/LICENSE ADDED
File without changes
package/README.md CHANGED
@@ -147,19 +147,32 @@ formatPrice(amount, 'USD');
147
147
  formatPrice(amount, { currency: 'USD' });
148
148
  ```
149
149
 
150
- ### 3. Order Fields - Use Correct Names
150
+ ### 3. Cart/Checkout vs Order - Different Item Structures!
151
+
152
+ **IMPORTANT:** Cart and Checkout items have NESTED product data. Order items are FLAT.
151
153
 
152
154
  ```typescript
153
- // WRONG
154
- order.total;
155
- item.product.name;
155
+ // CartItem and CheckoutLineItem - NESTED product
156
+ cart.items.forEach((item) => {
157
+ console.log(item.product.name); // ✅ Correct for Cart/Checkout
158
+ console.log(item.product.sku);
159
+ console.log(item.product.images);
160
+ });
156
161
 
157
- // CORRECT
158
- order.totalAmount; // or order.total (alias)
159
- item.name; // flat, not nested
160
- item.image; // flat, not nested
162
+ // OrderItem - FLAT structure
163
+ order.items.forEach((item) => {
164
+ console.log(item.name); // Correct for Orders
165
+ console.log(item.sku);
166
+ console.log(item.image); // singular, not images
167
+ });
161
168
  ```
162
169
 
170
+ | Type | Access Name | Access Image |
171
+ | ------------------ | ------------------- | --------------------- |
172
+ | `CartItem` | `item.product.name` | `item.product.images` |
173
+ | `CheckoutLineItem` | `item.product.name` | `item.product.images` |
174
+ | `OrderItem` | `item.name` | `item.image` |
175
+
163
176
  ### 4. Payment Status is 'succeeded', not 'completed'
164
177
 
165
178
  ```typescript
@@ -206,6 +219,118 @@ const color = variant.attributes?.['Color']; // string
206
219
  const size = variant.attributes?.['Size']; // string
207
220
  ```
208
221
 
222
+ ### 8. Address Uses `region`, NOT `state`
223
+
224
+ ```typescript
225
+ // ❌ WRONG
226
+ const address = {
227
+ state: 'NY', // This field doesn't exist!
228
+ };
229
+
230
+ // ✅ CORRECT
231
+ const address: SetShippingAddressDto = {
232
+ firstName: 'John',
233
+ lastName: 'Doe',
234
+ line1: '123 Main St',
235
+ city: 'New York',
236
+ region: 'NY', // Use 'region' for state/province
237
+ postalCode: '10001',
238
+ country: 'US',
239
+ };
240
+ ```
241
+
242
+ ### 9. OAuth - Use `authorizationUrl`, NOT `url`
243
+
244
+ ```typescript
245
+ // ❌ WRONG
246
+ const response = await omni.getOAuthAuthorizeUrl('GOOGLE', { redirectUrl });
247
+ window.location.href = response.url; // 'url' doesn't exist!
248
+
249
+ // ✅ CORRECT
250
+ const response = await omni.getOAuthAuthorizeUrl('GOOGLE', { redirectUrl });
251
+ window.location.href = response.authorizationUrl; // Correct property name
252
+ ```
253
+
254
+ ### 10. OAuth Provider Type is Exported
255
+
256
+ ```typescript
257
+ // ❌ WRONG - creating your own type
258
+ type Provider = 'google' | 'facebook'; // lowercase won't work!
259
+
260
+ // ✅ CORRECT - import from SDK
261
+ import { CustomerOAuthProvider } from 'omni-sync-sdk';
262
+ // CustomerOAuthProvider = 'GOOGLE' | 'FACEBOOK' | 'GITHUB' (UPPERCASE)
263
+
264
+ const provider: CustomerOAuthProvider = 'GOOGLE';
265
+ await omni.getOAuthAuthorizeUrl(provider, { redirectUrl });
266
+ ```
267
+
268
+ ### 11. getAvailableOAuthProviders Returns Object, Not Array
269
+
270
+ ```typescript
271
+ // ❌ WRONG - expecting array directly
272
+ const providers = await omni.getAvailableOAuthProviders();
273
+ providers.forEach(p => ...); // Error! providers is not an array
274
+
275
+ // ✅ CORRECT - access the providers property
276
+ const response = await omni.getAvailableOAuthProviders();
277
+ response.providers.forEach(p => ...); // response.providers is the array
278
+ ```
279
+
280
+ ### 12. SDK Uses `null`, Not `undefined`
281
+
282
+ Optional fields in SDK types use `null`, not `undefined`:
283
+
284
+ ```typescript
285
+ // SDK types use:
286
+ slug: string | null;
287
+ salePrice: string | null;
288
+
289
+ // So when checking:
290
+ if (product.slug !== null) {
291
+ // ✅ Check for null
292
+ // ...
293
+ }
294
+ ```
295
+
296
+ ### 13. Cart Has No `total` Field - Use `getCartTotals()` Helper
297
+
298
+ ```typescript
299
+ // ❌ WRONG - these fields don't exist on Cart
300
+ const total = cart.total; // ← 'total' doesn't exist!
301
+ const discount = cart.discount; // ← 'discount' doesn't exist! It's 'discountAmount'
302
+
303
+ // ✅ CORRECT - use the helper function (RECOMMENDED)
304
+ import { getCartTotals } from 'omni-sync-sdk';
305
+ const totals = getCartTotals(cart, shippingPrice);
306
+ // Returns: { subtotal: 59.98, discount: 10, shipping: 5.99, total: 55.97 }
307
+
308
+ // ✅ CORRECT - or calculate manually
309
+ const subtotal = parseFloat(cart.subtotal);
310
+ const discount = parseFloat(cart.discountAmount); // ← Note: 'discountAmount', NOT 'discount'
311
+ const total = subtotal - discount;
312
+ ```
313
+
314
+ **Important Notes:**
315
+
316
+ - Cart field is `discountAmount`, NOT `discount`
317
+ - Cart has NO `total` field - use `getCartTotals()` or calculate
318
+ - Checkout DOES have a `total` field, but Cart does not
319
+
320
+ ### 14. SearchSuggestions - Products Have `price`, Not `basePrice`
321
+
322
+ ```typescript
323
+ // In SearchSuggestions, ProductSuggestion has:
324
+ // - price: effective price (sale price if on sale, otherwise base price)
325
+ // - basePrice: original price
326
+ // - salePrice: sale price if on sale
327
+
328
+ // ✅ Use 'price' for display (it's already the correct price)
329
+ suggestions.products.map(p => (
330
+ <div>{p.name} - {formatPrice(p.price, { currency })}</div>
331
+ ));
332
+ ```
333
+
209
334
  ---
210
335
 
211
336
  ## Checkout: Guest vs Logged-In Customer
package/dist/index.d.mts CHANGED
@@ -1692,15 +1692,27 @@ interface ShippingRate {
1692
1692
  * **Note:** Order is created automatically via webhook when payment succeeds.
1693
1693
  * No need to call `completeCheckout()` - it happens automatically.
1694
1694
  *
1695
+ * **IMPORTANT: Order Summary Display**
1696
+ * Always use `checkout.lineItems` for displaying the Order Summary during checkout!
1697
+ * This is especially important for **partial checkout** (AliExpress-style) where the user
1698
+ * selects only some items from their cart. The `lineItems` array contains ONLY the items
1699
+ * being purchased in this checkout, NOT the entire cart.
1700
+ *
1695
1701
  * **IMPORTANT: Price fields are strings**
1696
1702
  * All monetary fields (subtotal, discountAmount, shippingAmount, taxAmount, total)
1697
1703
  * are strings to preserve decimal precision. Use parseFloat() for calculations.
1698
1704
  *
1699
1705
  * @example
1700
1706
  * ```typescript
1701
- * // Display checkout summary
1707
+ * // Display checkout summary - use checkout.lineItems, NOT localCart!
1702
1708
  * const checkout = await omni.getCheckout(checkoutId);
1703
1709
  *
1710
+ * // Order Summary - ALWAYS use checkout.lineItems
1711
+ * checkout.lineItems.forEach(item => {
1712
+ * console.log(item.product.name, item.quantity, item.unitPrice);
1713
+ * });
1714
+ *
1715
+ * // Totals
1704
1716
  * const subtotal = parseFloat(checkout.subtotal);
1705
1717
  * const discount = parseFloat(checkout.discountAmount);
1706
1718
  * const shipping = parseFloat(checkout.shippingAmount);
@@ -1712,11 +1724,6 @@ interface ShippingRate {
1712
1724
  * console.log(`Shipping: $${shipping.toFixed(2)}`);
1713
1725
  * console.log(`Tax: $${tax.toFixed(2)}`);
1714
1726
  * console.log(`Total: $${total.toFixed(2)}`);
1715
- *
1716
- * // Access line items (nested structure)
1717
- * checkout.lineItems.forEach(item => {
1718
- * console.log(item.product.name, item.quantity);
1719
- * });
1720
1727
  * ```
1721
1728
  *
1722
1729
  * @see CheckoutLineItem for item structure
@@ -3057,6 +3064,11 @@ declare class OmniSyncClient {
3057
3064
  * When a cart has this ID, operations use localStorage instead of server API.
3058
3065
  */
3059
3066
  private readonly VIRTUAL_LOCAL_CART_ID;
3067
+ /**
3068
+ * localStorage key for persisting active checkout across page redirects.
3069
+ * This is needed because Stripe redirects lose in-memory state.
3070
+ */
3071
+ private readonly ACTIVE_CHECKOUT_KEY;
3060
3072
  private readonly onAuthError?;
3061
3073
  constructor(options: OmniSyncClientOptions);
3062
3074
  /**
@@ -4748,6 +4760,11 @@ declare class OmniSyncClient {
4748
4760
  * Pass `selectedIndices` to checkout only specific items from the local cart.
4749
4761
  * Use the index of each item in the cart.items array.
4750
4762
  *
4763
+ * **IMPORTANT - Order Summary Display:**
4764
+ * After calling this function, use `getCheckout(checkoutId)` to get the checkout
4765
+ * and display `checkout.lineItems` in your Order Summary - NOT the local cart items!
4766
+ * The checkout.lineItems contains ONLY the selected items for this checkout.
4767
+ *
4751
4768
  * @param options.selectedIndices - Optional array of item indices for partial checkout
4752
4769
  * @returns Tracking result with checkoutId if enabled
4753
4770
  *
@@ -4760,19 +4777,22 @@ declare class OmniSyncClient {
4760
4777
  * const result = await omni.startGuestCheckout({ selectedIndices: [0, 2] });
4761
4778
  *
4762
4779
  * if (result.tracked) {
4763
- * // Store checkoutId for later use
4764
- * console.log('Checkout tracked:', result.checkoutId);
4780
+ * // IMPORTANT: Fetch checkout and use checkout.lineItems for Order Summary!
4781
+ * const checkout = await omni.getCheckout(result.checkoutId);
4782
+ *
4783
+ * // Display Order Summary using checkout.lineItems (NOT localCart.items!)
4784
+ * // checkout.lineItems contains ONLY the selected items
4785
+ * checkout.lineItems.forEach(item => {
4786
+ * console.log(item.product.name, item.quantity, item.unitPrice);
4787
+ * });
4765
4788
  *
4766
4789
  * // Update checkout with address
4767
- * await omni.updateGuestCheckout(result.checkoutId, {
4790
+ * await omni.updateGuestCheckoutAddress(result.checkoutId, {
4768
4791
  * shippingAddress: { ... },
4769
4792
  * });
4770
4793
  *
4771
- * // Complete checkout
4772
- * const order = await omni.completeGuestCheckout(result.checkoutId);
4773
- *
4774
- * // For partial checkout: remove only purchased items
4775
- * omni.removeLocalCartItemsByIndex([0, 2]);
4794
+ * // After payment success, call handlePaymentSuccess() to clear cart
4795
+ * omni.handlePaymentSuccess(result.checkoutId);
4776
4796
  * } else {
4777
4797
  * // Tracking not enabled, use regular submitGuestOrder
4778
4798
  * const order = await omni.submitGuestOrder();
@@ -4782,6 +4802,21 @@ declare class OmniSyncClient {
4782
4802
  startGuestCheckout(options?: {
4783
4803
  selectedIndices?: number[];
4784
4804
  }): Promise<GuestCheckoutStartResponse>;
4805
+ /**
4806
+ * Save active checkout to localStorage (persists across page redirects)
4807
+ * @internal
4808
+ */
4809
+ private saveActiveCheckout;
4810
+ /**
4811
+ * Load active checkout from localStorage
4812
+ * @internal
4813
+ */
4814
+ private loadActiveCheckout;
4815
+ /**
4816
+ * Clear active checkout from localStorage
4817
+ * @internal
4818
+ */
4819
+ private clearActiveCheckoutStorage;
4785
4820
  /**
4786
4821
  * Remove specific items from local cart by their indices.
4787
4822
  * Use after partial checkout to remove only the purchased items.
@@ -4829,6 +4864,83 @@ declare class OmniSyncClient {
4829
4864
  clearCartOnSuccess?: boolean;
4830
4865
  selectedIndices?: number[];
4831
4866
  }): Promise<GuestOrderResponse>;
4867
+ /**
4868
+ * Get the active guest checkout session info.
4869
+ * Use this to check if there's an active checkout before handling payment success.
4870
+ *
4871
+ * @returns The active checkout info or null if no checkout is active
4872
+ *
4873
+ * @example
4874
+ * ```typescript
4875
+ * const activeCheckout = omni.getActiveGuestCheckout();
4876
+ * if (activeCheckout) {
4877
+ * console.log('Active checkout:', activeCheckout.checkoutId);
4878
+ * console.log('Partial checkout:', activeCheckout.selectedIndices?.length);
4879
+ * }
4880
+ * ```
4881
+ */
4882
+ getActiveGuestCheckout(): {
4883
+ checkoutId: string;
4884
+ cartId: string;
4885
+ selectedIndices?: number[];
4886
+ } | null;
4887
+ /**
4888
+ * Handle payment success - automatically clears the cart (both local and server).
4889
+ *
4890
+ * Call this after Stripe payment succeeds (payment_intent.succeeded or confirmPayment success).
4891
+ * This handles ALL checkout scenarios:
4892
+ *
4893
+ * **For guest users (local cart):**
4894
+ * - Full checkout: clears entire localStorage cart
4895
+ * - Partial checkout: removes only the purchased items
4896
+ *
4897
+ * **For logged-in users (server cart):**
4898
+ * - Clears the cached cart ID so next `smartGetCart()` fetches fresh data
4899
+ * - The server cart is already marked as CONVERTED by the webhook
4900
+ *
4901
+ * **IMPORTANT:** Call this from your payment success handler (e.g., after stripe.confirmPayment succeeds,
4902
+ * or on your success page after redirect). This works for both guests AND logged-in users!
4903
+ *
4904
+ * @param checkoutId - Optional checkout ID for validation
4905
+ * @returns Object indicating whether cart was cleared and how
4906
+ *
4907
+ * @example
4908
+ * ```typescript
4909
+ * // After stripe.confirmPayment() succeeds (works for guests AND logged-in users)
4910
+ * const { error, paymentIntent } = await stripe.confirmPayment({
4911
+ * elements,
4912
+ * confirmParams: { return_url: '/checkout/success' },
4913
+ * redirect: 'if_required',
4914
+ * });
4915
+ *
4916
+ * if (!error && paymentIntent?.status === 'succeeded') {
4917
+ * // Clear the cart automatically - handles all scenarios!
4918
+ * const result = omni.handlePaymentSuccess(checkoutId);
4919
+ * console.log('Cart cleared:', result.cleared);
4920
+ * console.log('User type:', result.userType); // 'guest' or 'customer'
4921
+ * }
4922
+ * ```
4923
+ *
4924
+ * @example
4925
+ * ```typescript
4926
+ * // On success page (after redirect)
4927
+ * const checkoutId = new URLSearchParams(location.search).get('checkout_id');
4928
+ * if (checkoutId) {
4929
+ * omni.handlePaymentSuccess(checkoutId);
4930
+ * }
4931
+ * ```
4932
+ */
4933
+ handlePaymentSuccess(checkoutId?: string): {
4934
+ cleared: boolean;
4935
+ mode: 'full' | 'partial' | 'none';
4936
+ userType: 'guest' | 'customer';
4937
+ itemsRemoved?: number;
4938
+ };
4939
+ /**
4940
+ * Clear the active guest checkout tracking without clearing the cart.
4941
+ * Use this if the user abandons checkout or navigates away.
4942
+ */
4943
+ clearActiveGuestCheckout(): void;
4832
4944
  /**
4833
4945
  * Create order from custom data (not from local cart)
4834
4946
  * Use this if you manage cart state yourself
package/dist/index.d.ts CHANGED
@@ -1692,15 +1692,27 @@ interface ShippingRate {
1692
1692
  * **Note:** Order is created automatically via webhook when payment succeeds.
1693
1693
  * No need to call `completeCheckout()` - it happens automatically.
1694
1694
  *
1695
+ * **IMPORTANT: Order Summary Display**
1696
+ * Always use `checkout.lineItems` for displaying the Order Summary during checkout!
1697
+ * This is especially important for **partial checkout** (AliExpress-style) where the user
1698
+ * selects only some items from their cart. The `lineItems` array contains ONLY the items
1699
+ * being purchased in this checkout, NOT the entire cart.
1700
+ *
1695
1701
  * **IMPORTANT: Price fields are strings**
1696
1702
  * All monetary fields (subtotal, discountAmount, shippingAmount, taxAmount, total)
1697
1703
  * are strings to preserve decimal precision. Use parseFloat() for calculations.
1698
1704
  *
1699
1705
  * @example
1700
1706
  * ```typescript
1701
- * // Display checkout summary
1707
+ * // Display checkout summary - use checkout.lineItems, NOT localCart!
1702
1708
  * const checkout = await omni.getCheckout(checkoutId);
1703
1709
  *
1710
+ * // Order Summary - ALWAYS use checkout.lineItems
1711
+ * checkout.lineItems.forEach(item => {
1712
+ * console.log(item.product.name, item.quantity, item.unitPrice);
1713
+ * });
1714
+ *
1715
+ * // Totals
1704
1716
  * const subtotal = parseFloat(checkout.subtotal);
1705
1717
  * const discount = parseFloat(checkout.discountAmount);
1706
1718
  * const shipping = parseFloat(checkout.shippingAmount);
@@ -1712,11 +1724,6 @@ interface ShippingRate {
1712
1724
  * console.log(`Shipping: $${shipping.toFixed(2)}`);
1713
1725
  * console.log(`Tax: $${tax.toFixed(2)}`);
1714
1726
  * console.log(`Total: $${total.toFixed(2)}`);
1715
- *
1716
- * // Access line items (nested structure)
1717
- * checkout.lineItems.forEach(item => {
1718
- * console.log(item.product.name, item.quantity);
1719
- * });
1720
1727
  * ```
1721
1728
  *
1722
1729
  * @see CheckoutLineItem for item structure
@@ -3057,6 +3064,11 @@ declare class OmniSyncClient {
3057
3064
  * When a cart has this ID, operations use localStorage instead of server API.
3058
3065
  */
3059
3066
  private readonly VIRTUAL_LOCAL_CART_ID;
3067
+ /**
3068
+ * localStorage key for persisting active checkout across page redirects.
3069
+ * This is needed because Stripe redirects lose in-memory state.
3070
+ */
3071
+ private readonly ACTIVE_CHECKOUT_KEY;
3060
3072
  private readonly onAuthError?;
3061
3073
  constructor(options: OmniSyncClientOptions);
3062
3074
  /**
@@ -4748,6 +4760,11 @@ declare class OmniSyncClient {
4748
4760
  * Pass `selectedIndices` to checkout only specific items from the local cart.
4749
4761
  * Use the index of each item in the cart.items array.
4750
4762
  *
4763
+ * **IMPORTANT - Order Summary Display:**
4764
+ * After calling this function, use `getCheckout(checkoutId)` to get the checkout
4765
+ * and display `checkout.lineItems` in your Order Summary - NOT the local cart items!
4766
+ * The checkout.lineItems contains ONLY the selected items for this checkout.
4767
+ *
4751
4768
  * @param options.selectedIndices - Optional array of item indices for partial checkout
4752
4769
  * @returns Tracking result with checkoutId if enabled
4753
4770
  *
@@ -4760,19 +4777,22 @@ declare class OmniSyncClient {
4760
4777
  * const result = await omni.startGuestCheckout({ selectedIndices: [0, 2] });
4761
4778
  *
4762
4779
  * if (result.tracked) {
4763
- * // Store checkoutId for later use
4764
- * console.log('Checkout tracked:', result.checkoutId);
4780
+ * // IMPORTANT: Fetch checkout and use checkout.lineItems for Order Summary!
4781
+ * const checkout = await omni.getCheckout(result.checkoutId);
4782
+ *
4783
+ * // Display Order Summary using checkout.lineItems (NOT localCart.items!)
4784
+ * // checkout.lineItems contains ONLY the selected items
4785
+ * checkout.lineItems.forEach(item => {
4786
+ * console.log(item.product.name, item.quantity, item.unitPrice);
4787
+ * });
4765
4788
  *
4766
4789
  * // Update checkout with address
4767
- * await omni.updateGuestCheckout(result.checkoutId, {
4790
+ * await omni.updateGuestCheckoutAddress(result.checkoutId, {
4768
4791
  * shippingAddress: { ... },
4769
4792
  * });
4770
4793
  *
4771
- * // Complete checkout
4772
- * const order = await omni.completeGuestCheckout(result.checkoutId);
4773
- *
4774
- * // For partial checkout: remove only purchased items
4775
- * omni.removeLocalCartItemsByIndex([0, 2]);
4794
+ * // After payment success, call handlePaymentSuccess() to clear cart
4795
+ * omni.handlePaymentSuccess(result.checkoutId);
4776
4796
  * } else {
4777
4797
  * // Tracking not enabled, use regular submitGuestOrder
4778
4798
  * const order = await omni.submitGuestOrder();
@@ -4782,6 +4802,21 @@ declare class OmniSyncClient {
4782
4802
  startGuestCheckout(options?: {
4783
4803
  selectedIndices?: number[];
4784
4804
  }): Promise<GuestCheckoutStartResponse>;
4805
+ /**
4806
+ * Save active checkout to localStorage (persists across page redirects)
4807
+ * @internal
4808
+ */
4809
+ private saveActiveCheckout;
4810
+ /**
4811
+ * Load active checkout from localStorage
4812
+ * @internal
4813
+ */
4814
+ private loadActiveCheckout;
4815
+ /**
4816
+ * Clear active checkout from localStorage
4817
+ * @internal
4818
+ */
4819
+ private clearActiveCheckoutStorage;
4785
4820
  /**
4786
4821
  * Remove specific items from local cart by their indices.
4787
4822
  * Use after partial checkout to remove only the purchased items.
@@ -4829,6 +4864,83 @@ declare class OmniSyncClient {
4829
4864
  clearCartOnSuccess?: boolean;
4830
4865
  selectedIndices?: number[];
4831
4866
  }): Promise<GuestOrderResponse>;
4867
+ /**
4868
+ * Get the active guest checkout session info.
4869
+ * Use this to check if there's an active checkout before handling payment success.
4870
+ *
4871
+ * @returns The active checkout info or null if no checkout is active
4872
+ *
4873
+ * @example
4874
+ * ```typescript
4875
+ * const activeCheckout = omni.getActiveGuestCheckout();
4876
+ * if (activeCheckout) {
4877
+ * console.log('Active checkout:', activeCheckout.checkoutId);
4878
+ * console.log('Partial checkout:', activeCheckout.selectedIndices?.length);
4879
+ * }
4880
+ * ```
4881
+ */
4882
+ getActiveGuestCheckout(): {
4883
+ checkoutId: string;
4884
+ cartId: string;
4885
+ selectedIndices?: number[];
4886
+ } | null;
4887
+ /**
4888
+ * Handle payment success - automatically clears the cart (both local and server).
4889
+ *
4890
+ * Call this after Stripe payment succeeds (payment_intent.succeeded or confirmPayment success).
4891
+ * This handles ALL checkout scenarios:
4892
+ *
4893
+ * **For guest users (local cart):**
4894
+ * - Full checkout: clears entire localStorage cart
4895
+ * - Partial checkout: removes only the purchased items
4896
+ *
4897
+ * **For logged-in users (server cart):**
4898
+ * - Clears the cached cart ID so next `smartGetCart()` fetches fresh data
4899
+ * - The server cart is already marked as CONVERTED by the webhook
4900
+ *
4901
+ * **IMPORTANT:** Call this from your payment success handler (e.g., after stripe.confirmPayment succeeds,
4902
+ * or on your success page after redirect). This works for both guests AND logged-in users!
4903
+ *
4904
+ * @param checkoutId - Optional checkout ID for validation
4905
+ * @returns Object indicating whether cart was cleared and how
4906
+ *
4907
+ * @example
4908
+ * ```typescript
4909
+ * // After stripe.confirmPayment() succeeds (works for guests AND logged-in users)
4910
+ * const { error, paymentIntent } = await stripe.confirmPayment({
4911
+ * elements,
4912
+ * confirmParams: { return_url: '/checkout/success' },
4913
+ * redirect: 'if_required',
4914
+ * });
4915
+ *
4916
+ * if (!error && paymentIntent?.status === 'succeeded') {
4917
+ * // Clear the cart automatically - handles all scenarios!
4918
+ * const result = omni.handlePaymentSuccess(checkoutId);
4919
+ * console.log('Cart cleared:', result.cleared);
4920
+ * console.log('User type:', result.userType); // 'guest' or 'customer'
4921
+ * }
4922
+ * ```
4923
+ *
4924
+ * @example
4925
+ * ```typescript
4926
+ * // On success page (after redirect)
4927
+ * const checkoutId = new URLSearchParams(location.search).get('checkout_id');
4928
+ * if (checkoutId) {
4929
+ * omni.handlePaymentSuccess(checkoutId);
4930
+ * }
4931
+ * ```
4932
+ */
4933
+ handlePaymentSuccess(checkoutId?: string): {
4934
+ cleared: boolean;
4935
+ mode: 'full' | 'partial' | 'none';
4936
+ userType: 'guest' | 'customer';
4937
+ itemsRemoved?: number;
4938
+ };
4939
+ /**
4940
+ * Clear the active guest checkout tracking without clearing the cart.
4941
+ * Use this if the user abandons checkout or navigates away.
4942
+ */
4943
+ clearActiveGuestCheckout(): void;
4832
4944
  /**
4833
4945
  * Create order from custom data (not from local cart)
4834
4946
  * Use this if you manage cart state yourself
package/dist/index.js CHANGED
@@ -54,6 +54,11 @@ var OmniSyncClient = class {
54
54
  * When a cart has this ID, operations use localStorage instead of server API.
55
55
  */
56
56
  this.VIRTUAL_LOCAL_CART_ID = "__local__";
57
+ /**
58
+ * localStorage key for persisting active checkout across page redirects.
59
+ * This is needed because Stripe redirects lose in-memory state.
60
+ */
61
+ this.ACTIVE_CHECKOUT_KEY = "omni_active_checkout";
57
62
  // -------------------- Local Cart (Client-Side for Guests) --------------------
58
63
  // These methods store cart data in localStorage - NO API calls!
59
64
  // Use for guest users in vibe-coded sites
@@ -2255,6 +2260,12 @@ var OmniSyncClient = class {
2255
2260
  * ```
2256
2261
  */
2257
2262
  async createCheckout(data) {
2263
+ if (data.cartId === this.VIRTUAL_LOCAL_CART_ID) {
2264
+ throw new OmniSyncError(
2265
+ "Cannot create checkout from local cart directly. Use startGuestCheckout() for guest checkout with localStorage cart, or sync cart to server first with syncCartOnLogin().",
2266
+ 400
2267
+ );
2268
+ }
2258
2269
  if (this.isVibeCodedMode()) {
2259
2270
  return this.vibeCodedRequest("POST", "/checkout", data);
2260
2271
  }
@@ -3069,6 +3080,11 @@ var OmniSyncClient = class {
3069
3080
  * Pass `selectedIndices` to checkout only specific items from the local cart.
3070
3081
  * Use the index of each item in the cart.items array.
3071
3082
  *
3083
+ * **IMPORTANT - Order Summary Display:**
3084
+ * After calling this function, use `getCheckout(checkoutId)` to get the checkout
3085
+ * and display `checkout.lineItems` in your Order Summary - NOT the local cart items!
3086
+ * The checkout.lineItems contains ONLY the selected items for this checkout.
3087
+ *
3072
3088
  * @param options.selectedIndices - Optional array of item indices for partial checkout
3073
3089
  * @returns Tracking result with checkoutId if enabled
3074
3090
  *
@@ -3081,19 +3097,22 @@ var OmniSyncClient = class {
3081
3097
  * const result = await omni.startGuestCheckout({ selectedIndices: [0, 2] });
3082
3098
  *
3083
3099
  * if (result.tracked) {
3084
- * // Store checkoutId for later use
3085
- * console.log('Checkout tracked:', result.checkoutId);
3100
+ * // IMPORTANT: Fetch checkout and use checkout.lineItems for Order Summary!
3101
+ * const checkout = await omni.getCheckout(result.checkoutId);
3102
+ *
3103
+ * // Display Order Summary using checkout.lineItems (NOT localCart.items!)
3104
+ * // checkout.lineItems contains ONLY the selected items
3105
+ * checkout.lineItems.forEach(item => {
3106
+ * console.log(item.product.name, item.quantity, item.unitPrice);
3107
+ * });
3086
3108
  *
3087
3109
  * // Update checkout with address
3088
- * await omni.updateGuestCheckout(result.checkoutId, {
3110
+ * await omni.updateGuestCheckoutAddress(result.checkoutId, {
3089
3111
  * shippingAddress: { ... },
3090
3112
  * });
3091
3113
  *
3092
- * // Complete checkout
3093
- * const order = await omni.completeGuestCheckout(result.checkoutId);
3094
- *
3095
- * // For partial checkout: remove only purchased items
3096
- * omni.removeLocalCartItemsByIndex([0, 2]);
3114
+ * // After payment success, call handlePaymentSuccess() to clear cart
3115
+ * omni.handlePaymentSuccess(result.checkoutId);
3097
3116
  * } else {
3098
3117
  * // Tracking not enabled, use regular submitGuestOrder
3099
3118
  * const order = await omni.submitGuestOrder();
@@ -3121,8 +3140,48 @@ var OmniSyncClient = class {
3121
3140
  customer: cart.customer
3122
3141
  }
3123
3142
  );
3143
+ if (response.tracked && response.checkoutId && response.cartId) {
3144
+ this.saveActiveCheckout({
3145
+ checkoutId: response.checkoutId,
3146
+ cartId: response.cartId,
3147
+ selectedIndices: options?.selectedIndices
3148
+ });
3149
+ }
3124
3150
  return response;
3125
3151
  }
3152
+ /**
3153
+ * Save active checkout to localStorage (persists across page redirects)
3154
+ * @internal
3155
+ */
3156
+ saveActiveCheckout(checkout) {
3157
+ if (typeof window !== "undefined" && window.localStorage) {
3158
+ localStorage.setItem(this.ACTIVE_CHECKOUT_KEY, JSON.stringify(checkout));
3159
+ }
3160
+ }
3161
+ /**
3162
+ * Load active checkout from localStorage
3163
+ * @internal
3164
+ */
3165
+ loadActiveCheckout() {
3166
+ if (typeof window === "undefined" || !window.localStorage) {
3167
+ return null;
3168
+ }
3169
+ try {
3170
+ const data = localStorage.getItem(this.ACTIVE_CHECKOUT_KEY);
3171
+ return data ? JSON.parse(data) : null;
3172
+ } catch {
3173
+ return null;
3174
+ }
3175
+ }
3176
+ /**
3177
+ * Clear active checkout from localStorage
3178
+ * @internal
3179
+ */
3180
+ clearActiveCheckoutStorage() {
3181
+ if (typeof window !== "undefined" && window.localStorage) {
3182
+ localStorage.removeItem(this.ACTIVE_CHECKOUT_KEY);
3183
+ }
3184
+ }
3126
3185
  /**
3127
3186
  * Remove specific items from local cart by their indices.
3128
3187
  * Use after partial checkout to remove only the purchased items.
@@ -3213,6 +3272,113 @@ var OmniSyncClient = class {
3213
3272
  }
3214
3273
  return result;
3215
3274
  }
3275
+ /**
3276
+ * Get the active guest checkout session info.
3277
+ * Use this to check if there's an active checkout before handling payment success.
3278
+ *
3279
+ * @returns The active checkout info or null if no checkout is active
3280
+ *
3281
+ * @example
3282
+ * ```typescript
3283
+ * const activeCheckout = omni.getActiveGuestCheckout();
3284
+ * if (activeCheckout) {
3285
+ * console.log('Active checkout:', activeCheckout.checkoutId);
3286
+ * console.log('Partial checkout:', activeCheckout.selectedIndices?.length);
3287
+ * }
3288
+ * ```
3289
+ */
3290
+ getActiveGuestCheckout() {
3291
+ return this.loadActiveCheckout();
3292
+ }
3293
+ /**
3294
+ * Handle payment success - automatically clears the cart (both local and server).
3295
+ *
3296
+ * Call this after Stripe payment succeeds (payment_intent.succeeded or confirmPayment success).
3297
+ * This handles ALL checkout scenarios:
3298
+ *
3299
+ * **For guest users (local cart):**
3300
+ * - Full checkout: clears entire localStorage cart
3301
+ * - Partial checkout: removes only the purchased items
3302
+ *
3303
+ * **For logged-in users (server cart):**
3304
+ * - Clears the cached cart ID so next `smartGetCart()` fetches fresh data
3305
+ * - The server cart is already marked as CONVERTED by the webhook
3306
+ *
3307
+ * **IMPORTANT:** Call this from your payment success handler (e.g., after stripe.confirmPayment succeeds,
3308
+ * or on your success page after redirect). This works for both guests AND logged-in users!
3309
+ *
3310
+ * @param checkoutId - Optional checkout ID for validation
3311
+ * @returns Object indicating whether cart was cleared and how
3312
+ *
3313
+ * @example
3314
+ * ```typescript
3315
+ * // After stripe.confirmPayment() succeeds (works for guests AND logged-in users)
3316
+ * const { error, paymentIntent } = await stripe.confirmPayment({
3317
+ * elements,
3318
+ * confirmParams: { return_url: '/checkout/success' },
3319
+ * redirect: 'if_required',
3320
+ * });
3321
+ *
3322
+ * if (!error && paymentIntent?.status === 'succeeded') {
3323
+ * // Clear the cart automatically - handles all scenarios!
3324
+ * const result = omni.handlePaymentSuccess(checkoutId);
3325
+ * console.log('Cart cleared:', result.cleared);
3326
+ * console.log('User type:', result.userType); // 'guest' or 'customer'
3327
+ * }
3328
+ * ```
3329
+ *
3330
+ * @example
3331
+ * ```typescript
3332
+ * // On success page (after redirect)
3333
+ * const checkoutId = new URLSearchParams(location.search).get('checkout_id');
3334
+ * if (checkoutId) {
3335
+ * omni.handlePaymentSuccess(checkoutId);
3336
+ * }
3337
+ * ```
3338
+ */
3339
+ handlePaymentSuccess(checkoutId) {
3340
+ const isLoggedIn = this.isCustomerLoggedIn();
3341
+ if (isLoggedIn) {
3342
+ this.customerCartId = null;
3343
+ }
3344
+ const activeCheckout = this.loadActiveCheckout();
3345
+ if (checkoutId && activeCheckout && activeCheckout.checkoutId !== checkoutId) {
3346
+ console.warn(
3347
+ `handlePaymentSuccess: checkoutId mismatch. Expected ${activeCheckout.checkoutId}, got ${checkoutId}. Proceeding with cleanup.`
3348
+ );
3349
+ }
3350
+ this.clearActiveCheckoutStorage();
3351
+ if (isLoggedIn) {
3352
+ this.clearLocalCart();
3353
+ return {
3354
+ cleared: true,
3355
+ mode: "full",
3356
+ userType: "customer"
3357
+ };
3358
+ }
3359
+ if (activeCheckout?.selectedIndices && activeCheckout.selectedIndices.length > 0) {
3360
+ this.removeLocalCartItemsByIndex(activeCheckout.selectedIndices);
3361
+ return {
3362
+ cleared: true,
3363
+ mode: "partial",
3364
+ userType: "guest",
3365
+ itemsRemoved: activeCheckout.selectedIndices.length
3366
+ };
3367
+ }
3368
+ this.clearLocalCart();
3369
+ return {
3370
+ cleared: true,
3371
+ mode: "full",
3372
+ userType: "guest"
3373
+ };
3374
+ }
3375
+ /**
3376
+ * Clear the active guest checkout tracking without clearing the cart.
3377
+ * Use this if the user abandons checkout or navigates away.
3378
+ */
3379
+ clearActiveGuestCheckout() {
3380
+ this.clearActiveCheckoutStorage();
3381
+ }
3216
3382
  /**
3217
3383
  * Create order from custom data (not from local cart)
3218
3384
  * Use this if you manage cart state yourself
package/dist/index.mjs CHANGED
@@ -17,6 +17,11 @@ var OmniSyncClient = class {
17
17
  * When a cart has this ID, operations use localStorage instead of server API.
18
18
  */
19
19
  this.VIRTUAL_LOCAL_CART_ID = "__local__";
20
+ /**
21
+ * localStorage key for persisting active checkout across page redirects.
22
+ * This is needed because Stripe redirects lose in-memory state.
23
+ */
24
+ this.ACTIVE_CHECKOUT_KEY = "omni_active_checkout";
20
25
  // -------------------- Local Cart (Client-Side for Guests) --------------------
21
26
  // These methods store cart data in localStorage - NO API calls!
22
27
  // Use for guest users in vibe-coded sites
@@ -2218,6 +2223,12 @@ var OmniSyncClient = class {
2218
2223
  * ```
2219
2224
  */
2220
2225
  async createCheckout(data) {
2226
+ if (data.cartId === this.VIRTUAL_LOCAL_CART_ID) {
2227
+ throw new OmniSyncError(
2228
+ "Cannot create checkout from local cart directly. Use startGuestCheckout() for guest checkout with localStorage cart, or sync cart to server first with syncCartOnLogin().",
2229
+ 400
2230
+ );
2231
+ }
2221
2232
  if (this.isVibeCodedMode()) {
2222
2233
  return this.vibeCodedRequest("POST", "/checkout", data);
2223
2234
  }
@@ -3032,6 +3043,11 @@ var OmniSyncClient = class {
3032
3043
  * Pass `selectedIndices` to checkout only specific items from the local cart.
3033
3044
  * Use the index of each item in the cart.items array.
3034
3045
  *
3046
+ * **IMPORTANT - Order Summary Display:**
3047
+ * After calling this function, use `getCheckout(checkoutId)` to get the checkout
3048
+ * and display `checkout.lineItems` in your Order Summary - NOT the local cart items!
3049
+ * The checkout.lineItems contains ONLY the selected items for this checkout.
3050
+ *
3035
3051
  * @param options.selectedIndices - Optional array of item indices for partial checkout
3036
3052
  * @returns Tracking result with checkoutId if enabled
3037
3053
  *
@@ -3044,19 +3060,22 @@ var OmniSyncClient = class {
3044
3060
  * const result = await omni.startGuestCheckout({ selectedIndices: [0, 2] });
3045
3061
  *
3046
3062
  * if (result.tracked) {
3047
- * // Store checkoutId for later use
3048
- * console.log('Checkout tracked:', result.checkoutId);
3063
+ * // IMPORTANT: Fetch checkout and use checkout.lineItems for Order Summary!
3064
+ * const checkout = await omni.getCheckout(result.checkoutId);
3065
+ *
3066
+ * // Display Order Summary using checkout.lineItems (NOT localCart.items!)
3067
+ * // checkout.lineItems contains ONLY the selected items
3068
+ * checkout.lineItems.forEach(item => {
3069
+ * console.log(item.product.name, item.quantity, item.unitPrice);
3070
+ * });
3049
3071
  *
3050
3072
  * // Update checkout with address
3051
- * await omni.updateGuestCheckout(result.checkoutId, {
3073
+ * await omni.updateGuestCheckoutAddress(result.checkoutId, {
3052
3074
  * shippingAddress: { ... },
3053
3075
  * });
3054
3076
  *
3055
- * // Complete checkout
3056
- * const order = await omni.completeGuestCheckout(result.checkoutId);
3057
- *
3058
- * // For partial checkout: remove only purchased items
3059
- * omni.removeLocalCartItemsByIndex([0, 2]);
3077
+ * // After payment success, call handlePaymentSuccess() to clear cart
3078
+ * omni.handlePaymentSuccess(result.checkoutId);
3060
3079
  * } else {
3061
3080
  * // Tracking not enabled, use regular submitGuestOrder
3062
3081
  * const order = await omni.submitGuestOrder();
@@ -3084,8 +3103,48 @@ var OmniSyncClient = class {
3084
3103
  customer: cart.customer
3085
3104
  }
3086
3105
  );
3106
+ if (response.tracked && response.checkoutId && response.cartId) {
3107
+ this.saveActiveCheckout({
3108
+ checkoutId: response.checkoutId,
3109
+ cartId: response.cartId,
3110
+ selectedIndices: options?.selectedIndices
3111
+ });
3112
+ }
3087
3113
  return response;
3088
3114
  }
3115
+ /**
3116
+ * Save active checkout to localStorage (persists across page redirects)
3117
+ * @internal
3118
+ */
3119
+ saveActiveCheckout(checkout) {
3120
+ if (typeof window !== "undefined" && window.localStorage) {
3121
+ localStorage.setItem(this.ACTIVE_CHECKOUT_KEY, JSON.stringify(checkout));
3122
+ }
3123
+ }
3124
+ /**
3125
+ * Load active checkout from localStorage
3126
+ * @internal
3127
+ */
3128
+ loadActiveCheckout() {
3129
+ if (typeof window === "undefined" || !window.localStorage) {
3130
+ return null;
3131
+ }
3132
+ try {
3133
+ const data = localStorage.getItem(this.ACTIVE_CHECKOUT_KEY);
3134
+ return data ? JSON.parse(data) : null;
3135
+ } catch {
3136
+ return null;
3137
+ }
3138
+ }
3139
+ /**
3140
+ * Clear active checkout from localStorage
3141
+ * @internal
3142
+ */
3143
+ clearActiveCheckoutStorage() {
3144
+ if (typeof window !== "undefined" && window.localStorage) {
3145
+ localStorage.removeItem(this.ACTIVE_CHECKOUT_KEY);
3146
+ }
3147
+ }
3089
3148
  /**
3090
3149
  * Remove specific items from local cart by their indices.
3091
3150
  * Use after partial checkout to remove only the purchased items.
@@ -3176,6 +3235,113 @@ var OmniSyncClient = class {
3176
3235
  }
3177
3236
  return result;
3178
3237
  }
3238
+ /**
3239
+ * Get the active guest checkout session info.
3240
+ * Use this to check if there's an active checkout before handling payment success.
3241
+ *
3242
+ * @returns The active checkout info or null if no checkout is active
3243
+ *
3244
+ * @example
3245
+ * ```typescript
3246
+ * const activeCheckout = omni.getActiveGuestCheckout();
3247
+ * if (activeCheckout) {
3248
+ * console.log('Active checkout:', activeCheckout.checkoutId);
3249
+ * console.log('Partial checkout:', activeCheckout.selectedIndices?.length);
3250
+ * }
3251
+ * ```
3252
+ */
3253
+ getActiveGuestCheckout() {
3254
+ return this.loadActiveCheckout();
3255
+ }
3256
+ /**
3257
+ * Handle payment success - automatically clears the cart (both local and server).
3258
+ *
3259
+ * Call this after Stripe payment succeeds (payment_intent.succeeded or confirmPayment success).
3260
+ * This handles ALL checkout scenarios:
3261
+ *
3262
+ * **For guest users (local cart):**
3263
+ * - Full checkout: clears entire localStorage cart
3264
+ * - Partial checkout: removes only the purchased items
3265
+ *
3266
+ * **For logged-in users (server cart):**
3267
+ * - Clears the cached cart ID so next `smartGetCart()` fetches fresh data
3268
+ * - The server cart is already marked as CONVERTED by the webhook
3269
+ *
3270
+ * **IMPORTANT:** Call this from your payment success handler (e.g., after stripe.confirmPayment succeeds,
3271
+ * or on your success page after redirect). This works for both guests AND logged-in users!
3272
+ *
3273
+ * @param checkoutId - Optional checkout ID for validation
3274
+ * @returns Object indicating whether cart was cleared and how
3275
+ *
3276
+ * @example
3277
+ * ```typescript
3278
+ * // After stripe.confirmPayment() succeeds (works for guests AND logged-in users)
3279
+ * const { error, paymentIntent } = await stripe.confirmPayment({
3280
+ * elements,
3281
+ * confirmParams: { return_url: '/checkout/success' },
3282
+ * redirect: 'if_required',
3283
+ * });
3284
+ *
3285
+ * if (!error && paymentIntent?.status === 'succeeded') {
3286
+ * // Clear the cart automatically - handles all scenarios!
3287
+ * const result = omni.handlePaymentSuccess(checkoutId);
3288
+ * console.log('Cart cleared:', result.cleared);
3289
+ * console.log('User type:', result.userType); // 'guest' or 'customer'
3290
+ * }
3291
+ * ```
3292
+ *
3293
+ * @example
3294
+ * ```typescript
3295
+ * // On success page (after redirect)
3296
+ * const checkoutId = new URLSearchParams(location.search).get('checkout_id');
3297
+ * if (checkoutId) {
3298
+ * omni.handlePaymentSuccess(checkoutId);
3299
+ * }
3300
+ * ```
3301
+ */
3302
+ handlePaymentSuccess(checkoutId) {
3303
+ const isLoggedIn = this.isCustomerLoggedIn();
3304
+ if (isLoggedIn) {
3305
+ this.customerCartId = null;
3306
+ }
3307
+ const activeCheckout = this.loadActiveCheckout();
3308
+ if (checkoutId && activeCheckout && activeCheckout.checkoutId !== checkoutId) {
3309
+ console.warn(
3310
+ `handlePaymentSuccess: checkoutId mismatch. Expected ${activeCheckout.checkoutId}, got ${checkoutId}. Proceeding with cleanup.`
3311
+ );
3312
+ }
3313
+ this.clearActiveCheckoutStorage();
3314
+ if (isLoggedIn) {
3315
+ this.clearLocalCart();
3316
+ return {
3317
+ cleared: true,
3318
+ mode: "full",
3319
+ userType: "customer"
3320
+ };
3321
+ }
3322
+ if (activeCheckout?.selectedIndices && activeCheckout.selectedIndices.length > 0) {
3323
+ this.removeLocalCartItemsByIndex(activeCheckout.selectedIndices);
3324
+ return {
3325
+ cleared: true,
3326
+ mode: "partial",
3327
+ userType: "guest",
3328
+ itemsRemoved: activeCheckout.selectedIndices.length
3329
+ };
3330
+ }
3331
+ this.clearLocalCart();
3332
+ return {
3333
+ cleared: true,
3334
+ mode: "full",
3335
+ userType: "guest"
3336
+ };
3337
+ }
3338
+ /**
3339
+ * Clear the active guest checkout tracking without clearing the cart.
3340
+ * Use this if the user abandons checkout or navigates away.
3341
+ */
3342
+ clearActiveGuestCheckout() {
3343
+ this.clearActiveCheckoutStorage();
3344
+ }
3179
3345
  /**
3180
3346
  * Create order from custom data (not from local cart)
3181
3347
  * Use this if you manage cart state yourself
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omni-sync-sdk",
3
- "version": "0.20.3",
3
+ "version": "0.21.1",
4
4
  "description": "Official SDK for building e-commerce storefronts with OmniSync Platform. Perfect for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -16,14 +16,6 @@
16
16
  "dist",
17
17
  "README.md"
18
18
  ],
19
- "scripts": {
20
- "build": "tsup src/index.ts --format cjs,esm --dts",
21
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
22
- "lint": "eslint \"src/**/*.ts\"",
23
- "test": "vitest run",
24
- "test:watch": "vitest",
25
- "prepublishOnly": "pnpm build"
26
- },
27
19
  "keywords": [
28
20
  "omni-sync",
29
21
  "e-commerce",
@@ -72,5 +64,12 @@
72
64
  "typescript": {
73
65
  "optional": true
74
66
  }
67
+ },
68
+ "scripts": {
69
+ "build": "tsup src/index.ts --format cjs,esm --dts",
70
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
71
+ "lint": "eslint \"src/**/*.ts\"",
72
+ "test": "vitest run",
73
+ "test:watch": "vitest"
75
74
  }
76
- }
75
+ }