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 +295 -15
- package/dist/index.js +6 -0
- package/dist/index.mjs +6 -0
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
-
//
|
|
154
|
-
|
|
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
|
-
//
|
|
158
|
-
order.
|
|
159
|
-
item.name; //
|
|
160
|
-
item.
|
|
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
|
-
>
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
//
|
|
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.
|
|
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",
|