omni-sync-sdk 0.21.0 → 0.21.2

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 CHANGED
@@ -121,7 +121,32 @@ const { data: products } = await omni.getProducts();
121
121
 
122
122
  > **AI Agents / Vibe-Coders:** Read this section carefully! These are common misunderstandings.
123
123
 
124
- ### 1. All Types Are Exported - Don't Create Local Interfaces!
124
+ ### 1. Guest Checkout - Don't Use createCheckout with Local Cart!
125
+
126
+ **This is the #1 cause of "Cart not found" errors!**
127
+
128
+ ```typescript
129
+ // ❌ WRONG - Local cart ID "__local__" doesn't exist on server!
130
+ const cart = await omni.smartGetCart(); // Returns { id: "__local__", ... }
131
+ const checkout = await omni.createCheckout({ cartId: cart.id }); // 💥 ERROR: Cart not found
132
+
133
+ // ✅ CORRECT - Use startGuestCheckout() for guest users
134
+ const result = await omni.startGuestCheckout();
135
+ if (result.tracked) {
136
+ const checkout = await omni.getCheckout(result.checkoutId);
137
+ // Continue with payment flow...
138
+ }
139
+
140
+ // ✅ ALTERNATIVE - Use submitGuestOrder() for simple checkout without payment UI
141
+ const order = await omni.submitGuestOrder();
142
+ ```
143
+
144
+ **Rule of thumb:**
145
+
146
+ - Guest user + Local cart → `startGuestCheckout()` or `submitGuestOrder()`
147
+ - Logged-in user + Server cart → `createCheckout({ cartId })`
148
+
149
+ ### 2. All Types Are Exported - Don't Create Local Interfaces!
125
150
 
126
151
  ```typescript
127
152
  // ❌ WRONG - Don't create these locally
@@ -147,19 +172,32 @@ formatPrice(amount, 'USD');
147
172
  formatPrice(amount, { currency: 'USD' });
148
173
  ```
149
174
 
150
- ### 3. Order Fields - Use Correct Names
175
+ ### 3. Cart/Checkout vs Order - Different Item Structures!
176
+
177
+ **IMPORTANT:** Cart and Checkout items have NESTED product data. Order items are FLAT.
151
178
 
152
179
  ```typescript
153
- // WRONG
154
- order.total;
155
- item.product.name;
180
+ // CartItem and CheckoutLineItem - NESTED product
181
+ cart.items.forEach((item) => {
182
+ console.log(item.product.name); // ✅ Correct for Cart/Checkout
183
+ console.log(item.product.sku);
184
+ console.log(item.product.images);
185
+ });
156
186
 
157
- // CORRECT
158
- order.totalAmount; // or order.total (alias)
159
- item.name; // flat, not nested
160
- item.image; // flat, not nested
187
+ // OrderItem - FLAT structure
188
+ order.items.forEach((item) => {
189
+ console.log(item.name); // Correct for Orders
190
+ console.log(item.sku);
191
+ console.log(item.image); // singular, not images
192
+ });
161
193
  ```
162
194
 
195
+ | Type | Access Name | Access Image |
196
+ | ------------------ | ------------------- | --------------------- |
197
+ | `CartItem` | `item.product.name` | `item.product.images` |
198
+ | `CheckoutLineItem` | `item.product.name` | `item.product.images` |
199
+ | `OrderItem` | `item.name` | `item.image` |
200
+
163
201
  ### 4. Payment Status is 'succeeded', not 'completed'
164
202
 
165
203
  ```typescript
@@ -206,16 +244,186 @@ const color = variant.attributes?.['Color']; // string
206
244
  const size = variant.attributes?.['Size']; // string
207
245
  ```
208
246
 
247
+ ### 8. Address Uses `region`, NOT `state`
248
+
249
+ ```typescript
250
+ // ❌ WRONG
251
+ const address = {
252
+ state: 'NY', // This field doesn't exist!
253
+ };
254
+
255
+ // ✅ CORRECT
256
+ const address: SetShippingAddressDto = {
257
+ firstName: 'John',
258
+ lastName: 'Doe',
259
+ line1: '123 Main St',
260
+ city: 'New York',
261
+ region: 'NY', // Use 'region' for state/province
262
+ postalCode: '10001',
263
+ country: 'US',
264
+ };
265
+ ```
266
+
267
+ ### 9. OAuth - Use `authorizationUrl`, NOT `url`
268
+
269
+ ```typescript
270
+ // ❌ WRONG
271
+ const response = await omni.getOAuthAuthorizeUrl('GOOGLE', { redirectUrl });
272
+ window.location.href = response.url; // 'url' doesn't exist!
273
+
274
+ // ✅ CORRECT
275
+ const response = await omni.getOAuthAuthorizeUrl('GOOGLE', { redirectUrl });
276
+ window.location.href = response.authorizationUrl; // Correct property name
277
+ ```
278
+
279
+ ### 10. OAuth Provider Type is Exported
280
+
281
+ ```typescript
282
+ // ❌ WRONG - creating your own type
283
+ type Provider = 'google' | 'facebook'; // lowercase won't work!
284
+
285
+ // ✅ CORRECT - import from SDK
286
+ import { CustomerOAuthProvider } from 'omni-sync-sdk';
287
+ // CustomerOAuthProvider = 'GOOGLE' | 'FACEBOOK' | 'GITHUB' (UPPERCASE)
288
+
289
+ const provider: CustomerOAuthProvider = 'GOOGLE';
290
+ await omni.getOAuthAuthorizeUrl(provider, { redirectUrl });
291
+ ```
292
+
293
+ ### 11. getAvailableOAuthProviders Returns Object, Not Array
294
+
295
+ ```typescript
296
+ // ❌ WRONG - expecting array directly
297
+ const providers = await omni.getAvailableOAuthProviders();
298
+ providers.forEach(p => ...); // Error! providers is not an array
299
+
300
+ // ✅ CORRECT - access the providers property
301
+ const response = await omni.getAvailableOAuthProviders();
302
+ response.providers.forEach(p => ...); // response.providers is the array
303
+ ```
304
+
305
+ ### 12. SDK Uses `null`, Not `undefined`
306
+
307
+ Optional fields in SDK types use `null`, not `undefined`:
308
+
309
+ ```typescript
310
+ // SDK types use:
311
+ slug: string | null;
312
+ salePrice: string | null;
313
+
314
+ // So when checking:
315
+ if (product.slug !== null) {
316
+ // ✅ Check for null
317
+ // ...
318
+ }
319
+ ```
320
+
321
+ ### 13. Cart Has No `total` Field - Use `getCartTotals()` Helper
322
+
323
+ ```typescript
324
+ // ❌ WRONG - these fields don't exist on Cart
325
+ const total = cart.total; // ← 'total' doesn't exist!
326
+ const discount = cart.discount; // ← 'discount' doesn't exist! It's 'discountAmount'
327
+
328
+ // ✅ CORRECT - use the helper function (RECOMMENDED)
329
+ import { getCartTotals } from 'omni-sync-sdk';
330
+ const totals = getCartTotals(cart, shippingPrice);
331
+ // Returns: { subtotal: 59.98, discount: 10, shipping: 5.99, total: 55.97 }
332
+
333
+ // ✅ CORRECT - or calculate manually
334
+ const subtotal = parseFloat(cart.subtotal);
335
+ const discount = parseFloat(cart.discountAmount); // ← Note: 'discountAmount', NOT 'discount'
336
+ const total = subtotal - discount;
337
+ ```
338
+
339
+ **Important Notes:**
340
+
341
+ - Cart field is `discountAmount`, NOT `discount`
342
+ - Cart has NO `total` field - use `getCartTotals()` or calculate
343
+ - Checkout DOES have a `total` field, but Cart does not
344
+
345
+ ### 14. SearchSuggestions - Products Have `price`, Not `basePrice`
346
+
347
+ ```typescript
348
+ // In SearchSuggestions, ProductSuggestion has:
349
+ // - price: effective price (sale price if on sale, otherwise base price)
350
+ // - basePrice: original price
351
+ // - salePrice: sale price if on sale
352
+
353
+ // ✅ Use 'price' for display (it's already the correct price)
354
+ suggestions.products.map(p => (
355
+ <div>{p.name} - {formatPrice(p.price, { currency })}</div>
356
+ ));
357
+ ```
358
+
359
+ ### 15. Forgetting to Clear Cart After Payment
360
+
361
+ **This causes "ghost items" in the cart after successful payment!**
362
+
363
+ ```typescript
364
+ // ❌ WRONG - Cart items remain after payment!
365
+ // In your success page:
366
+ export default function SuccessPage() {
367
+ return <div>Thank you for your order!</div>;
368
+ // User goes back to shop → still sees purchased items in cart!
369
+ }
370
+
371
+ // ✅ CORRECT - Call handlePaymentSuccess() on success page
372
+ export default function SuccessPage() {
373
+ const checkoutId = new URLSearchParams(window.location.search).get('checkout_id');
374
+
375
+ useEffect(() => {
376
+ // Clear cart items that were purchased
377
+ omni.handlePaymentSuccess(checkoutId);
378
+ }, []);
379
+
380
+ return <div>Thank you for your order!</div>;
381
+ }
382
+ ```
383
+
384
+ **Why is this needed?**
385
+
386
+ - Guest users: Local cart persists in localStorage across page refreshes and Stripe redirects
387
+ - Without cleanup, purchased items remain visible in cart
388
+ - `handlePaymentSuccess()` handles both full and partial checkout cleanup
389
+
209
390
  ---
210
391
 
211
392
  ## Checkout: Guest vs Logged-In Customer
212
393
 
213
- > **IMPORTANT:** There are TWO different checkout flows. You MUST use the correct one based on whether the customer is logged in or not.
394
+ > **⚠️ CRITICAL:** There are TWO different checkout flows. Using the wrong one will cause errors!
395
+
396
+ | Customer Type | Cart Type | With Payment (Stripe) | Without Payment UI |
397
+ | ------------- | ------------------------- | ---------------------- | -------------------- |
398
+ | **Guest** | Local Cart (localStorage) | `startGuestCheckout()` | `submitGuestOrder()` |
399
+ | **Logged In** | Server Cart | `createCheckout()` | `completeCheckout()` |
400
+
401
+ ### ❌ COMMON MISTAKE - Don't Do This!
402
+
403
+ ```typescript
404
+ // ❌ WRONG - This will FAIL with "Cart not found" error!
405
+ const cart = omni.getLocalCart(); // Returns cart with id: "__local__"
406
+ const checkout = await omni.createCheckout({ cartId: cart.id }); // ERROR!
407
+
408
+ // The "__local__" ID is virtual - it doesn't exist on the server!
409
+ ```
410
+
411
+ ### ✅ Correct Flow for Guest Checkout with Payment
214
412
 
215
- | Customer Type | Cart Type | Checkout Method | Orders Linked to Account? |
216
- | ------------- | ------------------------- | -------------------- | ------------------------- |
217
- | **Guest** | Local Cart (localStorage) | `submitGuestOrder()` | No |
218
- | **Logged In** | Server Cart | `completeCheckout()` | Yes |
413
+ ```typescript
414
+ // CORRECT - Use startGuestCheckout() for guests with local cart
415
+ const result = await omni.startGuestCheckout();
416
+
417
+ if (result.tracked) {
418
+ // Now you have a REAL checkout on the server
419
+ const checkout = await omni.getCheckout(result.checkoutId);
420
+
421
+ // Continue with shipping, payment, etc.
422
+ await omni.setShippingAddress(result.checkoutId, { ... });
423
+ const intent = await omni.createPaymentIntent(result.checkoutId);
424
+ // ... Stripe payment ...
425
+ }
426
+ ```
219
427
 
220
428
  ### Decision Flow
221
429
 
@@ -1418,6 +1626,22 @@ function canProceedToPayment(checkout: Checkout, rates: ShippingRate[]): boolean
1418
1626
 
1419
1627
  For vibe-coded sites, the SDK provides payment integration with Stripe and PayPal. The store owner configures their payment provider(s) in the admin, and your site uses these methods to process payments.
1420
1628
 
1629
+ #### ⚠️ Important: Getting a Valid Checkout ID
1630
+
1631
+ Before creating a payment intent, you need a checkout ID. How you get it depends on the customer type:
1632
+
1633
+ ```typescript
1634
+ // For GUEST users (local cart in localStorage):
1635
+ const result = await omni.startGuestCheckout();
1636
+ const checkoutId = result.checkoutId;
1637
+
1638
+ // For LOGGED-IN users (server cart):
1639
+ const checkout = await omni.createCheckout({ cartId: serverCartId });
1640
+ const checkoutId = checkout.id;
1641
+
1642
+ // Then continue with shipping and payment...
1643
+ ```
1644
+
1421
1645
  #### Get All Payment Providers (Recommended)
1422
1646
 
1423
1647
  Use this method to get ALL enabled payment providers and build dynamic UI:
@@ -1628,6 +1852,20 @@ if (status.status === 'succeeded' && status.orderId) {
1628
1852
 
1629
1853
  #### Complete Checkout with Payment Example
1630
1854
 
1855
+ > **Note:** This example assumes you already have a `checkout_id`. See below for how to create one.
1856
+
1857
+ **How to get a checkout_id:**
1858
+
1859
+ ```typescript
1860
+ // For GUEST users (local cart):
1861
+ const result = await omni.startGuestCheckout();
1862
+ const checkoutId = result.checkoutId; // Use this!
1863
+
1864
+ // For LOGGED-IN users (server cart):
1865
+ const checkout = await omni.createCheckout({ cartId: cart.id });
1866
+ const checkoutId = checkout.id; // Use this!
1867
+ ```
1868
+
1631
1869
  ```typescript
1632
1870
  'use client';
1633
1871
  import { useState, useEffect } from 'react';
@@ -1647,7 +1885,7 @@ export default function CheckoutPaymentPage() {
1647
1885
  useEffect(() => {
1648
1886
  async function initPayment() {
1649
1887
  try {
1650
- // Assume checkout is already created and shipping is set
1888
+ // Get checkout_id from URL (set by previous step)
1651
1889
  const checkoutId = new URLSearchParams(window.location.search).get('checkout_id');
1652
1890
  if (!checkoutId) throw new Error('No checkout ID');
1653
1891
 
@@ -1732,6 +1970,48 @@ function PaymentForm({ checkoutId }: { checkoutId: string }) {
1732
1970
  }
1733
1971
  ```
1734
1972
 
1973
+ #### Cart Cleanup After Payment: `handlePaymentSuccess()`
1974
+
1975
+ **CRITICAL:** After payment succeeds, you MUST call `handlePaymentSuccess()` to clear the cart. This ensures:
1976
+
1977
+ - Guest users: Local cart items are removed (full or partial based on what was purchased)
1978
+ - Logged-in users: Server cart is cleared
1979
+
1980
+ ```typescript
1981
+ // On your /checkout/success page:
1982
+ export default function CheckoutSuccessPage() {
1983
+ const checkoutId = new URLSearchParams(window.location.search).get('checkout_id');
1984
+
1985
+ useEffect(() => {
1986
+ // IMPORTANT: Call this to clear the cart after successful payment
1987
+ const result = omni.handlePaymentSuccess(checkoutId);
1988
+
1989
+ console.log('Cart cleanup:', result);
1990
+ // { cleared: true, mode: 'full' | 'partial', userType: 'guest' | 'customer' }
1991
+ }, []);
1992
+
1993
+ return (
1994
+ <div className="text-center py-12">
1995
+ <h1 className="text-2xl font-bold text-green-600">Payment Received!</h1>
1996
+ {/* ... rest of success page */}
1997
+ </div>
1998
+ );
1999
+ }
2000
+ ```
2001
+
2002
+ **How it works:**
2003
+ | User Type | Cart Type | Behavior |
2004
+ |-----------|-----------|----------|
2005
+ | Guest (partial checkout) | Local cart | Only removes purchased items |
2006
+ | Guest (full checkout) | Local cart | Clears entire cart |
2007
+ | Logged-in | Server cart | Clears cart via SDK state |
2008
+
2009
+ **Why is this needed?**
2010
+
2011
+ - After Stripe redirects back to your success page, the cart is still in localStorage/memory
2012
+ - Without calling `handlePaymentSuccess()`, users will see their "purchased" items still in cart
2013
+ - For partial checkout (AliExpress-style), only the purchased items are removed
2014
+
1735
2015
  ---
1736
2016
 
1737
2017
  ### Customer Authentication
package/dist/index.js CHANGED
@@ -2260,6 +2260,12 @@ var OmniSyncClient = class {
2260
2260
  * ```
2261
2261
  */
2262
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
+ }
2263
2269
  if (this.isVibeCodedMode()) {
2264
2270
  return this.vibeCodedRequest("POST", "/checkout", data);
2265
2271
  }
package/dist/index.mjs CHANGED
@@ -2223,6 +2223,12 @@ var OmniSyncClient = class {
2223
2223
  * ```
2224
2224
  */
2225
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
+ }
2226
2232
  if (this.isVibeCodedMode()) {
2227
2233
  return this.vibeCodedRequest("POST", "/checkout", data);
2228
2234
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omni-sync-sdk",
3
- "version": "0.21.0",
3
+ "version": "0.21.2",
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",