omni-sync-sdk 0.21.7 → 0.21.8

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.
@@ -1,483 +1,485 @@
1
- # OmniSync Store Builder
2
-
3
- Build a **{store_type}** store called "{store_name}" | Style: **{style}** | Currency: **{currency}**
4
-
5
- ---
6
-
7
- ## ⛔ STOP! Read These 3 Rules First (Breaking = Store Won't Work)
8
-
9
- ### Rule 1: Guest vs Logged-In = Different Checkout Methods!
10
-
11
- ```typescript
12
- // ❌ THIS WILL FAIL - "Cart not found" error!
13
- const cart = await omni.smartGetCart(); // Guest cart has id: "__local__"
14
- await omni.createCheckout({ cartId: cart.id }); // 💥 "__local__" doesn't exist on server!
15
-
16
- // ✅ CORRECT - Check user type first!
17
- if (omni.isCustomerLoggedIn()) {
18
- // Logged-in user → server cart exists
19
- const checkout = await omni.createCheckout({ cartId: cart.id });
20
- const checkoutId = checkout.id;
21
- } else {
22
- // Guest user → use startGuestCheckout()
23
- const result = await omni.startGuestCheckout();
24
- const checkoutId = result.checkoutId;
25
- }
26
- ```
27
-
28
- | User Type | Cart Location | Checkout Method | Get Checkout ID |
29
- | ------------- | ------------- | ---------------------------- | ------------------- |
30
- | **Guest** | localStorage | `startGuestCheckout()` | `result.checkoutId` |
31
- | **Logged-in** | Server | `createCheckout({ cartId })` | `checkout.id` |
32
-
33
- ### Rule 2: Clear Cart After Successful Payment!
34
-
35
- ```typescript
36
- // On /checkout/success page - MUST DO THIS!
37
- export default function CheckoutSuccessPage() {
38
- const checkoutId = new URLSearchParams(window.location.search).get('checkout_id');
39
-
40
- useEffect(() => {
41
- // This clears the cart (full or partial based on what was purchased)
42
- omni.handlePaymentSuccess(checkoutId);
43
- }, []);
44
-
45
- return <div>Thank you for your order!</div>;
46
- }
47
- ```
48
-
49
- ### Rule 3: Never Hardcode Products!
50
-
51
- ```typescript
52
- // ❌ FORBIDDEN - Store will show fake data!
53
- const products = [{ id: '1', name: 'T-Shirt', price: 29.99 }];
54
-
55
- // ✅ CORRECT - Fetch from API
56
- const { data: products } = await omni.getProducts();
57
- ```
58
-
59
- ---
60
-
61
- ## Quick Setup
62
-
63
- ```bash
64
- npm install omni-sync-sdk
65
- ```
66
-
67
- ```typescript
68
- // lib/omni-sync.ts
69
- import { OmniSyncClient } from 'omni-sync-sdk';
70
-
71
- export const omni = new OmniSyncClient({
72
- connectionId: '{connection_id}',
73
- baseUrl: '{api_url}',
74
- });
75
-
76
- // Restore customer session on page load
77
- export function initOmniSync() {
78
- if (typeof window === 'undefined') return;
79
- const token = localStorage.getItem('customerToken');
80
- if (token) omni.setCustomerToken(token);
81
- }
82
-
83
- // Save/clear customer token
84
- export function setCustomerToken(token: string | null) {
85
- if (token) {
86
- localStorage.setItem('customerToken', token);
87
- omni.setCustomerToken(token);
88
- } else {
89
- localStorage.removeItem('customerToken');
90
- omni.clearCustomerToken();
91
- }
92
- }
93
- ```
94
-
95
- ---
96
-
97
- ## Cart (Works for Both Guest & Logged-in)
98
-
99
- ```typescript
100
- // Get or create cart - handles both guest (localStorage) and logged-in (server) automatically
101
- const cart = await omni.smartGetCart();
102
-
103
- // Add to cart
104
- await omni.smartAddToCart({
105
- productId: 'prod_xxx',
106
- variantId: 'var_xxx', // optional, for products with variants
107
- quantity: 1,
108
- });
109
-
110
- // Update quantity (by productId, not itemId!)
111
- await omni.smartUpdateCartItem('prod_xxx', 2); // productId, quantity
112
- await omni.smartUpdateCartItem('prod_xxx', 3, 'var_xxx'); // with variant
113
-
114
- // Remove item (by productId, not itemId!)
115
- await omni.smartRemoveFromCart('prod_xxx');
116
- await omni.smartRemoveFromCart('prod_xxx', 'var_xxx'); // with variant
117
-
118
- // Get cart totals (cart doesn't have .total field!)
119
- import { getCartTotals } from 'omni-sync-sdk';
120
- const totals = getCartTotals(cart);
121
- // { subtotal: 59.98, discount: 10, shipping: 0, total: 49.98 }
122
-
123
- // ⚠️ Cart item count:
124
- // Server Cart has: cart.itemCount
125
- // LocalCart does NOT - use: cart.items.length or cart.items.reduce((sum, i) => sum + i.quantity, 0)
126
- ```
127
-
128
- ---
129
-
130
- ## 🛒 Partial Checkout (AliExpress Style) - REQUIRED!
131
-
132
- Cart page MUST have checkboxes so users can select which items to buy:
133
-
134
- ```typescript
135
- // Cart page - track selected items
136
- const [selectedIndices, setSelectedIndices] = useState<number[]>(
137
- cart.items.map((_, i) => i) // All selected by default
138
- );
139
-
140
- const toggleItem = (index: number) => {
141
- setSelectedIndices(prev =>
142
- prev.includes(index)
143
- ? prev.filter(i => i !== index)
144
- : [...prev, index]
145
- );
146
- };
147
-
148
- const toggleAll = () => {
149
- if (selectedIndices.length === cart.items.length) {
150
- setSelectedIndices([]); // Deselect all
151
- } else {
152
- setSelectedIndices(cart.items.map((_, i) => i)); // Select all
153
- }
154
- };
155
-
156
- // In your cart UI:
157
- <div>
158
- <label>
159
- <input
160
- type="checkbox"
161
- checked={selectedIndices.length === cart.items.length}
162
- onChange={toggleAll}
163
- />
164
- Select All
165
- </label>
166
- </div>
167
-
168
- {cart.items.map((item, index) => (
169
- <div key={index}>
170
- <input
171
- type="checkbox"
172
- checked={selectedIndices.includes(index)}
173
- onChange={() => toggleItem(index)}
174
- />
175
- {/* ... item details ... */}
176
- </div>
177
- ))}
178
-
179
- // On checkout button - pass selected items!
180
- const handleCheckout = async () => {
181
- if (selectedIndices.length === 0) {
182
- alert('Please select items to checkout');
183
- return;
184
- }
185
-
186
- const result = await omni.startGuestCheckout({ selectedIndices });
187
- // Only selected items go to checkout, others stay in cart!
188
- };
189
- ```
190
-
191
- **Why this matters:**
192
-
193
- - Users can buy some items now, leave others for later
194
- - After payment, `handlePaymentSuccess()` only removes purchased items
195
- - Remaining items stay in cart for future purchase
196
-
197
- **⚠️ Order Summary on Checkout Page - Use checkout.lineItems!**
198
-
199
- ```typescript
200
- // ❌ WRONG - Shows ALL cart items (even unselected ones!)
201
- <div className="order-summary">
202
- {cart.items.map(item => (
203
- <div>{item.product.name} - ${item.price}</div>
204
- ))}
205
- </div>
206
-
207
- // ✅ CORRECT - Shows only items being purchased in this checkout
208
- <div className="order-summary">
209
- {checkout.lineItems.map(item => (
210
- <div>{item.product.name} - ${item.price}</div>
211
- ))}
212
- </div>
213
- ```
214
-
215
- The `checkout` object's `lineItems` array contains ONLY the items selected for this checkout!
216
-
217
- ---
218
-
219
- ## Complete Checkout Flow
220
-
221
- ### Step 1: Start Checkout (Different for Guest vs Logged-in!)
222
-
223
- ```typescript
224
- async function startCheckout() {
225
- const cart = await omni.smartGetCart();
226
-
227
- if (cart.items.length === 0) {
228
- alert('Cart is empty');
229
- return;
230
- }
231
-
232
- let checkoutId: string;
233
-
234
- if (omni.isCustomerLoggedIn()) {
235
- // Logged-in: create checkout from server cart
236
- const checkout = await omni.createCheckout({ cartId: cart.id });
237
- checkoutId = checkout.id;
238
- } else {
239
- // Guest: use startGuestCheckout (syncs local cart to server)
240
- const result = await omni.startGuestCheckout();
241
- if (!result.tracked || !result.checkoutId) {
242
- throw new Error('Failed to create checkout');
243
- }
244
- checkoutId = result.checkoutId;
245
- }
246
-
247
- // Save for payment page
248
- localStorage.setItem('checkoutId', checkoutId);
249
-
250
- // Navigate to checkout
251
- window.location.href = '/checkout';
252
- }
253
- ```
254
-
255
- ### Step 2: Shipping Address
256
-
257
- ```typescript
258
- const checkoutId = localStorage.getItem('checkoutId')!;
259
-
260
- // Set shipping address (email is required!)
261
- const { checkout, rates } = await omni.setShippingAddress(checkoutId, {
262
- email: 'customer@example.com',
263
- firstName: 'John',
264
- lastName: 'Doe',
265
- line1: '123 Main St',
266
- city: 'New York',
267
- region: 'NY', // ⚠️ Use 'region', NOT 'state'!
268
- postalCode: '10001',
269
- country: 'US',
270
- });
271
-
272
- // Show available shipping rates
273
- rates.forEach((rate) => {
274
- console.log(`${rate.name}: $${rate.price}`);
275
- });
276
- ```
277
-
278
- ### Step 3: Select Shipping Method
279
-
280
- ```typescript
281
- await omni.selectShippingMethod(checkoutId, selectedRateId);
282
- ```
283
-
284
- ### Step 4: Payment with Stripe
285
-
286
- ```typescript
287
- // Install: npm install @stripe/stripe-js @stripe/react-stripe-js
288
-
289
- // 1. Check if payment is configured
290
- const { hasPayments, providers } = await omni.getPaymentProviders();
291
- if (!hasPayments) {
292
- return <div>Payment not configured for this store</div>;
293
- }
294
-
295
- // 2. Get Stripe config
296
- const stripeProvider = providers.find(p => p.provider === 'stripe');
297
- if (!stripeProvider) {
298
- return <div>Stripe not configured</div>;
299
- }
300
-
301
- // 3. Create payment intent
302
- const intent = await omni.createPaymentIntent(checkoutId);
303
-
304
- // 4. Initialize Stripe
305
- import { loadStripe } from '@stripe/stripe-js';
306
- const stripe = await loadStripe(stripeProvider.publicKey, {
307
- stripeAccount: stripeProvider.stripeAccountId,
308
- });
309
-
310
- // 5. Confirm payment (in your payment form)
311
- const { error } = await stripe.confirmPayment({
312
- elements,
313
- confirmParams: {
314
- return_url: `${window.location.origin}/checkout/success?checkout_id=${checkoutId}`,
315
- },
316
- });
317
-
318
- if (error) {
319
- setError(error.message);
320
- }
321
- // If no error, Stripe redirects to success page
322
- ```
323
-
324
- ### Step 5: Success Page (Clear Cart!)
325
-
326
- ```typescript
327
- // /checkout/success/page.tsx
328
- 'use client';
329
- import { useEffect, useState } from 'react';
330
- import { omni } from '@/lib/omni-sync';
331
-
332
- export default function CheckoutSuccessPage() {
333
- const [orderNumber, setOrderNumber] = useState<string>();
334
-
335
- useEffect(() => {
336
- const checkoutId = new URLSearchParams(window.location.search).get('checkout_id');
337
-
338
- // ⚠️ CRITICAL: Clear the cart!
339
- omni.handlePaymentSuccess(checkoutId);
340
-
341
- // Optional: Get order details
342
- if (checkoutId) {
343
- omni.getPaymentStatus(checkoutId).then(status => {
344
- if (status.orderNumber) {
345
- setOrderNumber(status.orderNumber);
346
- }
347
- });
348
- }
349
- }, []);
350
-
351
- return (
352
- <div className="text-center py-12">
353
- <h1 className="text-2xl font-bold text-green-600">Thank you for your order!</h1>
354
- {orderNumber && <p className="mt-2">Order #{orderNumber}</p>}
355
- <p className="mt-4">A confirmation email will be sent shortly.</p>
356
- </div>
357
- );
358
- }
359
- ```
360
-
361
- ---
362
-
363
- ## Partial Checkout (AliExpress Style)
364
-
365
- Allow customers to buy only some items from their cart:
366
-
367
- ```typescript
368
- // Start checkout with only selected items (by index)
369
- const result = await omni.startGuestCheckout({
370
- selectedIndices: [0, 2], // Buy items at index 0 and 2 only
371
- });
372
-
373
- // After payment, handlePaymentSuccess() will only remove those items!
374
- // Other items stay in cart.
375
- ```
376
-
377
- ---
378
-
379
- ## Products API
380
-
381
- ```typescript
382
- // List products with pagination
383
- const { data: products, meta } = await omni.getProducts({
384
- page: 1,
385
- limit: 20,
386
- search: 'blue shirt', // Searches name, description, SKU, categories, tags
387
- });
388
- // meta = { page: 1, limit: 20, total: 150, totalPages: 8 }
389
-
390
- // Get single product by slug (for product detail page)
391
- const product = await omni.getProductBySlug('blue-cotton-shirt');
392
-
393
- // Search suggestions (for autocomplete)
394
- const suggestions = await omni.getSearchSuggestions('blue', 5);
395
- // { products: [...], categories: [...] }
396
- ```
397
-
398
- ---
399
-
400
- ## Customer Authentication
401
-
402
- ```typescript
403
- // Register
404
- const { customer, token } = await omni.registerCustomer({
405
- email: 'john@example.com',
406
- password: 'securepass123',
407
- firstName: 'John',
408
- lastName: 'Doe',
409
- });
410
- setCustomerToken(token);
411
-
412
- // Login
413
- const { customer, token } = await omni.loginCustomer('john@example.com', 'password');
414
- setCustomerToken(token);
415
-
416
- // Logout
417
- setCustomerToken(null);
418
-
419
- // Get profile (requires token)
420
- const profile = await omni.getMyProfile();
421
-
422
- // Get order history
423
- const { data: orders, meta } = await omni.getMyOrders({ page: 1, limit: 10 });
424
- ```
425
-
426
- ---
427
-
428
- ## OAuth / Social Login
429
-
430
- ```typescript
431
- // Get available providers for this store
432
- const { providers } = await omni.getAvailableOAuthProviders();
433
- // providers = ['GOOGLE', 'FACEBOOK', 'GITHUB']
434
-
435
- // Redirect to OAuth provider
436
- const { authorizationUrl } = await omni.getOAuthAuthorizeUrl('GOOGLE', {
437
- redirectUrl: `${window.location.origin}/auth/callback`,
438
- });
439
- window.location.href = authorizationUrl;
440
-
441
- // Handle callback (on /auth/callback page)
442
- const code = new URLSearchParams(window.location.search).get('code');
443
- const state = new URLSearchParams(window.location.search).get('state');
444
- const { customer, token } = await omni.handleOAuthCallback('GOOGLE', code!, state!);
445
- setCustomerToken(token);
446
- ```
447
-
448
- ---
449
-
450
- ## Required Pages Checklist
451
-
452
- - [ ] **Home** (`/`) - Featured products grid
453
- - [ ] **Products** (`/products`) - Product list with infinite scroll
454
- - [ ] **Product Detail** (`/products/[slug]`) - Use `getProductBySlug(slug)`
455
- - [ ] **Cart** (`/cart`) - Show items, quantities, totals
456
- - [ ] **Checkout** (`/checkout`) - Address → Shipping → Payment
457
- - [ ] **Success** (`/checkout/success`) - **Must call `handlePaymentSuccess()`!**
458
- - [ ] **Login** (`/login`) - Email/password + social buttons
459
- - [ ] **Register** (`/register`) - Registration form
460
- - [ ] **Account** (`/account`) - Profile + order history
461
- - [ ] **Header** - Logo, nav, cart icon with count, search
462
-
463
- ---
464
-
465
- ## Common Type Gotchas
466
-
467
- ```typescript
468
- // ❌ WRONG // ✅ CORRECT
469
- address.state address.region
470
- cart.total getCartTotals(cart).total
471
- cart.discount cart.discountAmount
472
- item.name (in cart) item.product.name
473
- response.url (OAuth) response.authorizationUrl
474
- providers.forEach (OAuth) response.providers.forEach
475
- status === 'completed' status === 'succeeded'
476
- ```
477
-
478
- ---
479
-
480
- ## Full SDK Documentation
481
-
482
- For complete API reference and working code examples:
483
- **https://brainerce.com/docs/sdk**
1
+ # OmniSync Store Builder
2
+
3
+ Build a **{store_type}** store called "{store_name}" | Style: **{style}** | Currency: **{currency}**
4
+
5
+ ---
6
+
7
+ ## ⛔ STOP! Read These 3 Rules First (Breaking = Store Won't Work)
8
+
9
+ ### Rule 1: Guest vs Logged-In = Different Checkout Methods!
10
+
11
+ ```typescript
12
+ // ❌ THIS WILL FAIL - "Cart not found" error!
13
+ const cart = await omni.smartGetCart(); // Guest cart has id: "__local__"
14
+ await omni.createCheckout({ cartId: cart.id }); // 💥 "__local__" doesn't exist on server!
15
+
16
+ // ✅ CORRECT - Check user type first!
17
+ if (omni.isCustomerLoggedIn()) {
18
+ // Logged-in user → server cart exists
19
+ const checkout = await omni.createCheckout({ cartId: cart.id });
20
+ const checkoutId = checkout.id;
21
+ } else {
22
+ // Guest user → use startGuestCheckout()
23
+ const result = await omni.startGuestCheckout();
24
+ const checkoutId = result.checkoutId;
25
+ }
26
+ ```
27
+
28
+ | User Type | Cart Location | Checkout Method | Get Checkout ID |
29
+ | ------------- | ------------- | ---------------------------- | ------------------- |
30
+ | **Guest** | localStorage | `startGuestCheckout()` | `result.checkoutId` |
31
+ | **Logged-in** | Server | `createCheckout({ cartId })` | `checkout.id` |
32
+
33
+ ### Rule 2: Clear Cart After Successful Payment!
34
+
35
+ ```typescript
36
+ // On /checkout/success page - MUST DO THIS!
37
+ export default function CheckoutSuccessPage() {
38
+ const checkoutId = new URLSearchParams(window.location.search).get('checkout_id');
39
+
40
+ useEffect(() => {
41
+ // This clears the cart (full or partial based on what was purchased)
42
+ omni.handlePaymentSuccess(checkoutId);
43
+ }, []);
44
+
45
+ return <div>Thank you for your order!</div>;
46
+ }
47
+ ```
48
+
49
+ ### Rule 3: Never Hardcode Products!
50
+
51
+ ```typescript
52
+ // ❌ FORBIDDEN - Store will show fake data!
53
+ const products = [{ id: '1', name: 'T-Shirt', price: 29.99 }];
54
+
55
+ // ✅ CORRECT - Fetch from API
56
+ const { data: products } = await omni.getProducts();
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Quick Setup
62
+
63
+ ```bash
64
+ npm install omni-sync-sdk
65
+ ```
66
+
67
+ ```typescript
68
+ // lib/omni-sync.ts
69
+ import { OmniSyncClient } from 'omni-sync-sdk';
70
+
71
+ export const omni = new OmniSyncClient({
72
+ connectionId: '{connection_id}',
73
+ baseUrl: '{api_url}',
74
+ });
75
+
76
+ // Restore customer session on page load
77
+ export function initOmniSync() {
78
+ if (typeof window === 'undefined') return;
79
+ const token = localStorage.getItem('customerToken');
80
+ if (token) omni.setCustomerToken(token);
81
+ }
82
+
83
+ // Save/clear customer token
84
+ export function setCustomerToken(token: string | null) {
85
+ if (token) {
86
+ localStorage.setItem('customerToken', token);
87
+ omni.setCustomerToken(token);
88
+ } else {
89
+ localStorage.removeItem('customerToken');
90
+ omni.clearCustomerToken();
91
+ }
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Cart (Works for Both Guest & Logged-in)
98
+
99
+ ```typescript
100
+ // Get or create cart - handles both guest (localStorage) and logged-in (server) automatically
101
+ const cart = await omni.smartGetCart();
102
+
103
+ // Add to cart
104
+ await omni.smartAddToCart({
105
+ productId: 'prod_xxx',
106
+ variantId: 'var_xxx', // optional, for products with variants
107
+ quantity: 1,
108
+ });
109
+
110
+ // Update quantity (by productId, not itemId!)
111
+ await omni.smartUpdateCartItem('prod_xxx', 2); // productId, quantity
112
+ await omni.smartUpdateCartItem('prod_xxx', 3, 'var_xxx'); // with variant
113
+
114
+ // Remove item (by productId, not itemId!)
115
+ await omni.smartRemoveFromCart('prod_xxx');
116
+ await omni.smartRemoveFromCart('prod_xxx', 'var_xxx'); // with variant
117
+
118
+ // Get cart totals (cart doesn't have .total field!)
119
+ import { getCartTotals } from 'omni-sync-sdk';
120
+ const totals = getCartTotals(cart);
121
+ // { subtotal: 59.98, discount: 10, shipping: 0, total: 49.98 }
122
+
123
+ // ⚠️ LocalCart vs Cart - KEY DIFFERENCES:
124
+ // Server Cart has: id, itemCount, subtotal, discountAmount
125
+ // Guest LocalCart has NONE of these! Only: items, couponCode, customer
126
+ // To check type: if ('id' in cart) { /* server Cart */ } else { /* LocalCart */ }
127
+ // Item count for both: cart.items.length
128
+ ```
129
+
130
+ ---
131
+
132
+ ## 🛒 Partial Checkout (AliExpress Style) - REQUIRED!
133
+
134
+ Cart page MUST have checkboxes so users can select which items to buy:
135
+
136
+ ```typescript
137
+ // Cart page - track selected items
138
+ const [selectedIndices, setSelectedIndices] = useState<number[]>(
139
+ cart.items.map((_, i) => i) // All selected by default
140
+ );
141
+
142
+ const toggleItem = (index: number) => {
143
+ setSelectedIndices(prev =>
144
+ prev.includes(index)
145
+ ? prev.filter(i => i !== index)
146
+ : [...prev, index]
147
+ );
148
+ };
149
+
150
+ const toggleAll = () => {
151
+ if (selectedIndices.length === cart.items.length) {
152
+ setSelectedIndices([]); // Deselect all
153
+ } else {
154
+ setSelectedIndices(cart.items.map((_, i) => i)); // Select all
155
+ }
156
+ };
157
+
158
+ // In your cart UI:
159
+ <div>
160
+ <label>
161
+ <input
162
+ type="checkbox"
163
+ checked={selectedIndices.length === cart.items.length}
164
+ onChange={toggleAll}
165
+ />
166
+ Select All
167
+ </label>
168
+ </div>
169
+
170
+ {cart.items.map((item, index) => (
171
+ <div key={index}>
172
+ <input
173
+ type="checkbox"
174
+ checked={selectedIndices.includes(index)}
175
+ onChange={() => toggleItem(index)}
176
+ />
177
+ {/* ... item details ... */}
178
+ </div>
179
+ ))}
180
+
181
+ // On checkout button - pass selected items!
182
+ const handleCheckout = async () => {
183
+ if (selectedIndices.length === 0) {
184
+ alert('Please select items to checkout');
185
+ return;
186
+ }
187
+
188
+ const result = await omni.startGuestCheckout({ selectedIndices });
189
+ // Only selected items go to checkout, others stay in cart!
190
+ };
191
+ ```
192
+
193
+ **Why this matters:**
194
+
195
+ - Users can buy some items now, leave others for later
196
+ - After payment, `handlePaymentSuccess()` only removes purchased items
197
+ - Remaining items stay in cart for future purchase
198
+
199
+ **⚠️ Order Summary on Checkout Page - Use checkout.lineItems!**
200
+
201
+ ```typescript
202
+ // ❌ WRONG - Shows ALL cart items (even unselected ones!)
203
+ <div className="order-summary">
204
+ {cart.items.map(item => (
205
+ <div>{item.product.name} - ${item.price}</div>
206
+ ))}
207
+ </div>
208
+
209
+ // CORRECT - Shows only items being purchased in this checkout
210
+ <div className="order-summary">
211
+ {checkout.lineItems.map(item => (
212
+ <div>{item.product.name} - ${item.price}</div>
213
+ ))}
214
+ </div>
215
+ ```
216
+
217
+ The `checkout` object's `lineItems` array contains ONLY the items selected for this checkout!
218
+
219
+ ---
220
+
221
+ ## Complete Checkout Flow
222
+
223
+ ### Step 1: Start Checkout (Different for Guest vs Logged-in!)
224
+
225
+ ```typescript
226
+ async function startCheckout() {
227
+ const cart = await omni.smartGetCart();
228
+
229
+ if (cart.items.length === 0) {
230
+ alert('Cart is empty');
231
+ return;
232
+ }
233
+
234
+ let checkoutId: string;
235
+
236
+ if (omni.isCustomerLoggedIn()) {
237
+ // Logged-in: create checkout from server cart
238
+ const checkout = await omni.createCheckout({ cartId: cart.id });
239
+ checkoutId = checkout.id;
240
+ } else {
241
+ // Guest: use startGuestCheckout (syncs local cart to server)
242
+ const result = await omni.startGuestCheckout();
243
+ if (!result.tracked || !result.checkoutId) {
244
+ throw new Error('Failed to create checkout');
245
+ }
246
+ checkoutId = result.checkoutId;
247
+ }
248
+
249
+ // Save for payment page
250
+ localStorage.setItem('checkoutId', checkoutId);
251
+
252
+ // Navigate to checkout
253
+ window.location.href = '/checkout';
254
+ }
255
+ ```
256
+
257
+ ### Step 2: Shipping Address
258
+
259
+ ```typescript
260
+ const checkoutId = localStorage.getItem('checkoutId')!;
261
+
262
+ // Set shipping address (email is required!)
263
+ const { checkout, rates } = await omni.setShippingAddress(checkoutId, {
264
+ email: 'customer@example.com',
265
+ firstName: 'John',
266
+ lastName: 'Doe',
267
+ line1: '123 Main St',
268
+ city: 'New York',
269
+ region: 'NY', // ⚠️ Use 'region', NOT 'state'!
270
+ postalCode: '10001',
271
+ country: 'US',
272
+ });
273
+
274
+ // Show available shipping rates
275
+ rates.forEach((rate) => {
276
+ console.log(`${rate.name}: $${rate.price}`);
277
+ });
278
+ ```
279
+
280
+ ### Step 3: Select Shipping Method
281
+
282
+ ```typescript
283
+ await omni.selectShippingMethod(checkoutId, selectedRateId);
284
+ ```
285
+
286
+ ### Step 4: Payment with Stripe
287
+
288
+ ```typescript
289
+ // Install: npm install @stripe/stripe-js @stripe/react-stripe-js
290
+
291
+ // 1. Check if payment is configured
292
+ const { hasPayments, providers } = await omni.getPaymentProviders();
293
+ if (!hasPayments) {
294
+ return <div>Payment not configured for this store</div>;
295
+ }
296
+
297
+ // 2. Get Stripe config
298
+ const stripeProvider = providers.find(p => p.provider === 'stripe');
299
+ if (!stripeProvider) {
300
+ return <div>Stripe not configured</div>;
301
+ }
302
+
303
+ // 3. Create payment intent
304
+ const intent = await omni.createPaymentIntent(checkoutId);
305
+
306
+ // 4. Initialize Stripe
307
+ import { loadStripe } from '@stripe/stripe-js';
308
+ const stripe = await loadStripe(stripeProvider.publicKey, {
309
+ stripeAccount: stripeProvider.stripeAccountId,
310
+ });
311
+
312
+ // 5. Confirm payment (in your payment form)
313
+ const { error } = await stripe.confirmPayment({
314
+ elements,
315
+ confirmParams: {
316
+ return_url: `${window.location.origin}/checkout/success?checkout_id=${checkoutId}`,
317
+ },
318
+ });
319
+
320
+ if (error) {
321
+ setError(error.message);
322
+ }
323
+ // If no error, Stripe redirects to success page
324
+ ```
325
+
326
+ ### Step 5: Success Page (Clear Cart!)
327
+
328
+ ```typescript
329
+ // /checkout/success/page.tsx
330
+ 'use client';
331
+ import { useEffect, useState } from 'react';
332
+ import { omni } from '@/lib/omni-sync';
333
+
334
+ export default function CheckoutSuccessPage() {
335
+ const [orderNumber, setOrderNumber] = useState<string>();
336
+
337
+ useEffect(() => {
338
+ const checkoutId = new URLSearchParams(window.location.search).get('checkout_id');
339
+
340
+ // ⚠️ CRITICAL: Clear the cart!
341
+ omni.handlePaymentSuccess(checkoutId);
342
+
343
+ // Optional: Get order details
344
+ if (checkoutId) {
345
+ omni.getPaymentStatus(checkoutId).then(status => {
346
+ if (status.orderNumber) {
347
+ setOrderNumber(status.orderNumber);
348
+ }
349
+ });
350
+ }
351
+ }, []);
352
+
353
+ return (
354
+ <div className="text-center py-12">
355
+ <h1 className="text-2xl font-bold text-green-600">Thank you for your order!</h1>
356
+ {orderNumber && <p className="mt-2">Order #{orderNumber}</p>}
357
+ <p className="mt-4">A confirmation email will be sent shortly.</p>
358
+ </div>
359
+ );
360
+ }
361
+ ```
362
+
363
+ ---
364
+
365
+ ## Partial Checkout (AliExpress Style)
366
+
367
+ Allow customers to buy only some items from their cart:
368
+
369
+ ```typescript
370
+ // Start checkout with only selected items (by index)
371
+ const result = await omni.startGuestCheckout({
372
+ selectedIndices: [0, 2], // Buy items at index 0 and 2 only
373
+ });
374
+
375
+ // After payment, handlePaymentSuccess() will only remove those items!
376
+ // Other items stay in cart.
377
+ ```
378
+
379
+ ---
380
+
381
+ ## Products API
382
+
383
+ ```typescript
384
+ // List products with pagination
385
+ const { data: products, meta } = await omni.getProducts({
386
+ page: 1,
387
+ limit: 20,
388
+ search: 'blue shirt', // Searches name, description, SKU, categories, tags
389
+ });
390
+ // meta = { page: 1, limit: 20, total: 150, totalPages: 8 }
391
+
392
+ // Get single product by slug (for product detail page)
393
+ const product = await omni.getProductBySlug('blue-cotton-shirt');
394
+
395
+ // Search suggestions (for autocomplete)
396
+ const suggestions = await omni.getSearchSuggestions('blue', 5);
397
+ // { products: [...], categories: [...] }
398
+ ```
399
+
400
+ ---
401
+
402
+ ## Customer Authentication
403
+
404
+ ```typescript
405
+ // Register
406
+ const { customer, token } = await omni.registerCustomer({
407
+ email: 'john@example.com',
408
+ password: 'securepass123',
409
+ firstName: 'John',
410
+ lastName: 'Doe',
411
+ });
412
+ setCustomerToken(token);
413
+
414
+ // Login
415
+ const { customer, token } = await omni.loginCustomer('john@example.com', 'password');
416
+ setCustomerToken(token);
417
+
418
+ // Logout
419
+ setCustomerToken(null);
420
+
421
+ // Get profile (requires token)
422
+ const profile = await omni.getMyProfile();
423
+
424
+ // Get order history
425
+ const { data: orders, meta } = await omni.getMyOrders({ page: 1, limit: 10 });
426
+ ```
427
+
428
+ ---
429
+
430
+ ## OAuth / Social Login
431
+
432
+ ```typescript
433
+ // Get available providers for this store
434
+ const { providers } = await omni.getAvailableOAuthProviders();
435
+ // providers = ['GOOGLE', 'FACEBOOK', 'GITHUB']
436
+
437
+ // Redirect to OAuth provider
438
+ const { authorizationUrl } = await omni.getOAuthAuthorizeUrl('GOOGLE', {
439
+ redirectUrl: `${window.location.origin}/auth/callback`,
440
+ });
441
+ window.location.href = authorizationUrl;
442
+
443
+ // Handle callback (on /auth/callback page)
444
+ const code = new URLSearchParams(window.location.search).get('code');
445
+ const state = new URLSearchParams(window.location.search).get('state');
446
+ const { customer, token } = await omni.handleOAuthCallback('GOOGLE', code!, state!);
447
+ setCustomerToken(token);
448
+ ```
449
+
450
+ ---
451
+
452
+ ## Required Pages Checklist
453
+
454
+ - [ ] **Home** (`/`) - Featured products grid
455
+ - [ ] **Products** (`/products`) - Product list with infinite scroll
456
+ - [ ] **Product Detail** (`/products/[slug]`) - Use `getProductBySlug(slug)`
457
+ - [ ] **Cart** (`/cart`) - Show items, quantities, totals
458
+ - [ ] **Checkout** (`/checkout`) - Address Shipping → Payment
459
+ - [ ] **Success** (`/checkout/success`) - **Must call `handlePaymentSuccess()`!**
460
+ - [ ] **Login** (`/login`) - Email/password + social buttons
461
+ - [ ] **Register** (`/register`) - Registration form
462
+ - [ ] **Account** (`/account`) - Profile + order history
463
+ - [ ] **Header** - Logo, nav, cart icon with count, search
464
+
465
+ ---
466
+
467
+ ## Common Type Gotchas
468
+
469
+ ```typescript
470
+ // ❌ WRONG // ✅ CORRECT
471
+ address.state address.region
472
+ cart.total getCartTotals(cart).total
473
+ cart.discount cart.discountAmount
474
+ item.name (in cart) item.product.name
475
+ response.url (OAuth) response.authorizationUrl
476
+ providers.forEach (OAuth) response.providers.forEach
477
+ status === 'completed' status === 'succeeded'
478
+ ```
479
+
480
+ ---
481
+
482
+ ## Full SDK Documentation
483
+
484
+ For complete API reference and working code examples:
485
+ **https://brainerce.com/docs/sdk**