epicmerch-mcp 1.0.0
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 +182 -0
- package/manifest.json +17 -0
- package/package.json +64 -0
- package/skills/epicmerch-auth.md +78 -0
- package/skills/epicmerch-merchant-agent.md +74 -0
- package/skills/epicmerch-merchant.md +46 -0
- package/skills/epicmerch-migrate.md +62 -0
- package/skills/epicmerch-orders.md +153 -0
- package/skills/epicmerch-products.md +95 -0
- package/skills/epicmerch-stripe.md +64 -0
- package/skills/epicmerch.md +389 -0
- package/smithery.yaml +6 -0
- package/src/adapters/mcp.js +76 -0
- package/src/adapters/openapi.js +97 -0
- package/src/auth.js +118 -0
- package/src/client.js +81 -0
- package/src/index.js +67 -0
- package/src/install.js +269 -0
- package/src/login.js +149 -0
- package/src/logout.js +30 -0
- package/src/prompts/index.js +59 -0
- package/src/refresh.js +73 -0
- package/src/resources/examples/auth.md +31 -0
- package/src/resources/examples/orders.md +27 -0
- package/src/resources/examples/payments.md +23 -0
- package/src/resources/examples/products.md +22 -0
- package/src/resources/overview.md +26 -0
- package/src/resources/sdk-guide.md +341 -0
- package/src/status.js +22 -0
- package/src/token-store.js +52 -0
- package/src/tools/_examples.js +352 -0
- package/src/tools/developer.js +65 -0
- package/src/tools/merchant.js +618 -0
- package/src/tools/scaffold.js +278 -0
- package/src/tools/session.js +44 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# EpicMerch SDK Integration Guide for Epic Threadz
|
|
2
|
+
|
|
3
|
+
This document details how the **EpicMerch SDK** is integrated into the **Epic Threadz** storefront. It serves as a reference for developers to understand the interaction between the frontend application and the backend services.
|
|
4
|
+
|
|
5
|
+
## 1. Installation & Initialization
|
|
6
|
+
|
|
7
|
+
The SDK is imported and initialized in the main application entry point (`src/App.jsx`).
|
|
8
|
+
|
|
9
|
+
### Initialization
|
|
10
|
+
The `EpicMerch` class is instantiated with the API Key. For local development, the `baseUrl` can be overridden via environment variables.
|
|
11
|
+
|
|
12
|
+
```javascript
|
|
13
|
+
// src/App.jsx
|
|
14
|
+
import EpicMerch from 'epicmerch';
|
|
15
|
+
|
|
16
|
+
export const store = new EpicMerch({
|
|
17
|
+
apiKey: import.meta.env.VITE_API_KEY || 'your_public_key',
|
|
18
|
+
// Optional: Override for local development
|
|
19
|
+
...(import.meta.env.VITE_API_URL && { baseUrl: import.meta.env.VITE_API_URL })
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The `store` instance is then exported and passed as a prop to child components or imported directly where needed.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Authentication
|
|
28
|
+
|
|
29
|
+
The authentication flow uses OTP (One-Time Password) or Google OAuth. This is handled primarily in `src/pages/Login.jsx`.
|
|
30
|
+
|
|
31
|
+
### Sending OTP
|
|
32
|
+
```javascript
|
|
33
|
+
// Send OTP to phone or email
|
|
34
|
+
const response = await store.auth.sendOtp(identifier, 'phone'); // or 'email'
|
|
35
|
+
// identifier: e.g., '+919876543210'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Verifying OTP
|
|
39
|
+
```javascript
|
|
40
|
+
// Verify OTP and complete login/registration
|
|
41
|
+
const result = await store.auth.verifyOtp(identifier, otpValue, {
|
|
42
|
+
name: 'User Name',
|
|
43
|
+
email: 'user@example.com', // optional profile data for new users
|
|
44
|
+
gender: 'Male'
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Result contains the authentication token
|
|
48
|
+
if (result.token) {
|
|
49
|
+
store.setCustomerToken(result.token); // Store token in SDK for future requests
|
|
50
|
+
localStorage.setItem('customerInfo', JSON.stringify(result)); // Persist session
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Google OAuth
|
|
55
|
+
```javascript
|
|
56
|
+
// Generate OAuth URL
|
|
57
|
+
const authUrl = store.auth.getGoogleAuthUrl(window.location.origin + '/login');
|
|
58
|
+
window.location.href = authUrl;
|
|
59
|
+
|
|
60
|
+
// Handle Callback (on redirect page)
|
|
61
|
+
const result = await store.auth.handleOAuthCallback();
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 3. Product Catalog
|
|
67
|
+
|
|
68
|
+
Product listing and searching are implemented in `src/App.jsx`.
|
|
69
|
+
|
|
70
|
+
### Fetching Products
|
|
71
|
+
```javascript
|
|
72
|
+
const params = {
|
|
73
|
+
type: activeCategory, // e.g., 'Apparel' or undefined for all
|
|
74
|
+
page: 1,
|
|
75
|
+
limit: 12,
|
|
76
|
+
sort: 'newest', // 'popularity', 'price_asc', 'price_desc'
|
|
77
|
+
keyword: searchQuery // Optional search term
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const data = await store.products.list(params);
|
|
81
|
+
// Returns: { products: [...], total, pages }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 4. Shopping Cart
|
|
87
|
+
|
|
88
|
+
Cart operations are managed in `src/App.jsx` and `src/components/CartSlider.jsx`.
|
|
89
|
+
|
|
90
|
+
### Core Cart Operations
|
|
91
|
+
```javascript
|
|
92
|
+
// Get current cart
|
|
93
|
+
const cartData = await store.cart.get();
|
|
94
|
+
|
|
95
|
+
// Add item to cart
|
|
96
|
+
await store.cart.add(productId, quantity, { type: 'Size', value: 'M' });
|
|
97
|
+
|
|
98
|
+
// Update quantity
|
|
99
|
+
await store.cart.update(productId, newQuantity);
|
|
100
|
+
|
|
101
|
+
// Remove item
|
|
102
|
+
await store.cart.remove(productId);
|
|
103
|
+
|
|
104
|
+
// Clear cart
|
|
105
|
+
await store.cart.clear();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 5. Checkout & Payments
|
|
111
|
+
|
|
112
|
+
The checkout process (`src/pages/Checkout.jsx`) involves address management, order creation, and payment processing via Razorpay.
|
|
113
|
+
|
|
114
|
+
### Address Management
|
|
115
|
+
```javascript
|
|
116
|
+
// List saved addresses
|
|
117
|
+
const { addresses } = await store.addresses.list();
|
|
118
|
+
|
|
119
|
+
// Add new address
|
|
120
|
+
await store.addresses.add({
|
|
121
|
+
label: 'Home',
|
|
122
|
+
street: '123 Main St',
|
|
123
|
+
city: 'Mumbai',
|
|
124
|
+
state: 'MH',
|
|
125
|
+
postalCode: '400001',
|
|
126
|
+
country: 'India',
|
|
127
|
+
isDefault: true
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Order Creation Flow
|
|
132
|
+
1. **Create Order Record**:
|
|
133
|
+
```javascript
|
|
134
|
+
const orderResult = await store.orders.create({
|
|
135
|
+
orderItems: [...],
|
|
136
|
+
shippingAddress: {...},
|
|
137
|
+
paymentMethod: 'Razorpay',
|
|
138
|
+
totalPrice: 1500
|
|
139
|
+
});
|
|
140
|
+
const { orderId } = orderResult;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
2. **Initialize Payment**:
|
|
144
|
+
```javascript
|
|
145
|
+
// Get Razorpay Config
|
|
146
|
+
const config = await store.payment.getConfig();
|
|
147
|
+
|
|
148
|
+
// Create Razorpay Order
|
|
149
|
+
const razorpayOrder = await store.payment.createOrder(amount, orderId);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
3. **Verify Payment**:
|
|
153
|
+
After the Razorpay modal interaction:
|
|
154
|
+
```javascript
|
|
155
|
+
const verifyResult = await store.payment.verify({
|
|
156
|
+
razorpay_order_id: response.razorpay_order_id,
|
|
157
|
+
razorpay_payment_id: response.razorpay_payment_id,
|
|
158
|
+
razorpay_signature: response.razorpay_signature,
|
|
159
|
+
orderId: orderId
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Saved Payment Methods
|
|
164
|
+
The SDK supports "One-Click" checkout using saved tokens.
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
// Get saved cards/UPI
|
|
168
|
+
const methods = await store.payment.getSavedMethods();
|
|
169
|
+
|
|
170
|
+
// Charge a saved method directly
|
|
171
|
+
await store.payment.chargeSaved(methodId, amount, orderId);
|
|
172
|
+
|
|
173
|
+
// Save a new method (after successful payment)
|
|
174
|
+
await store.payment.saveMethod({
|
|
175
|
+
token: `tok_${paymentId}`,
|
|
176
|
+
type: 'card'
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 6. User Profile & Utilities
|
|
183
|
+
|
|
184
|
+
### Newsletter
|
|
185
|
+
```javascript
|
|
186
|
+
await store.newsletter.subscribe('user@example.com');
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Profile Updates
|
|
190
|
+
```javascript
|
|
191
|
+
// Update phone number or profile info
|
|
192
|
+
await store.customer.updateProfile({ phoneNumber: '+919999988888' });
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 7. Configuration Reference
|
|
198
|
+
|
|
199
|
+
The SDK is configured in `src/App.jsx`.
|
|
200
|
+
|
|
201
|
+
| Option | Description | Default |
|
|
202
|
+
|--------|-------------|---------|
|
|
203
|
+
| `apiKey` | Public API Key for the merchant | Required |
|
|
204
|
+
| `baseUrl`| API Server URL | Auto-detected (or `http://localhost:5001/api`) |
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 8. Idempotency (Duplicate Prevention)
|
|
209
|
+
|
|
210
|
+
The SDK automatically handles idempotency for critical operations to prevent duplicate orders and payments.
|
|
211
|
+
|
|
212
|
+
### How It Works
|
|
213
|
+
- **Order Creation**: SDK generates an idempotency key based on cart contents
|
|
214
|
+
- **Payment Verification**: Uses Razorpay payment ID as idempotency key (guaranteed unique)
|
|
215
|
+
- **Saved Card Charges**: Uses order ID as idempotency key
|
|
216
|
+
|
|
217
|
+
### Automatic Idempotency (Default Behavior)
|
|
218
|
+
```javascript
|
|
219
|
+
// The SDK automatically adds Idempotency-Key headers for critical operations
|
|
220
|
+
const order = await store.orders.create(orderData);
|
|
221
|
+
// ↑ Automatically generates key: payment_${orderId}
|
|
222
|
+
|
|
223
|
+
await store.payment.verify(paymentData);
|
|
224
|
+
// ↑ Automatically uses: verify_${razorpay_payment_id}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Custom Idempotency Keys
|
|
228
|
+
For checkout flows, provide a stable key to prevent double-click issues:
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
// Generate a stable key once per checkout session
|
|
232
|
+
const checkoutKey = `checkout_${userId}_${Date.now()}`;
|
|
233
|
+
|
|
234
|
+
// Use the same key for the entire checkout
|
|
235
|
+
const order = await store.orders.create(orderData, {
|
|
236
|
+
idempotencyKey: checkoutKey
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Idempotent Response Detection
|
|
241
|
+
```javascript
|
|
242
|
+
const result = await store.orders.create(orderData, { idempotencyKey: 'my-key' });
|
|
243
|
+
|
|
244
|
+
if (result._idempotent) {
|
|
245
|
+
// This is a cached response from a previous identical request
|
|
246
|
+
console.log('Order already created:', result.orderId);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 9. Transactional APIs
|
|
253
|
+
|
|
254
|
+
Critical operations (order creation, payment verification) are transactional:
|
|
255
|
+
|
|
256
|
+
### Order Creation
|
|
257
|
+
- ✅ Create order record
|
|
258
|
+
- ✅ Reduce product stock
|
|
259
|
+
- ✅ Update user (add order, clear cart)
|
|
260
|
+
- ❌ If ANY step fails, ALL changes are rolled back
|
|
261
|
+
|
|
262
|
+
### Payment Verification
|
|
263
|
+
- ✅ Validates Razorpay signature
|
|
264
|
+
- ✅ Atomically updates order status
|
|
265
|
+
- ✅ Prevents double payment crediting
|
|
266
|
+
- ✅ Returns `_alreadyVerified: true` for duplicate requests
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 10. Error Handling Best Practices
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
try {
|
|
274
|
+
const order = await store.orders.create(orderData);
|
|
275
|
+
|
|
276
|
+
if (order._idempotent) {
|
|
277
|
+
// Handle duplicate request gracefully
|
|
278
|
+
showSuccessMessage('Order already placed!');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Proceed with payment
|
|
283
|
+
const razorpay = await store.payment.createOrder(amount, order.orderId);
|
|
284
|
+
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (error.response?.status === 409) {
|
|
287
|
+
// Conflict - request is being processed
|
|
288
|
+
showMessage('Your request is being processed. Please wait...');
|
|
289
|
+
} else if (error.response?.status === 429) {
|
|
290
|
+
// Rate limited
|
|
291
|
+
showError('Too many requests. Please try again later.');
|
|
292
|
+
} else {
|
|
293
|
+
showError(error.message || 'Something went wrong');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## 11. Checkout Page Best Practices
|
|
301
|
+
|
|
302
|
+
### Editable Cart on Checkout
|
|
303
|
+
The checkout page now supports inline cart editing:
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
// Update quantity
|
|
307
|
+
const handleUpdateQty = async (productId, newQty, variant) => {
|
|
308
|
+
await store.cart.update(productId, newQty, variant);
|
|
309
|
+
// Refresh cart state
|
|
310
|
+
const cartData = await store.cart.get();
|
|
311
|
+
setCart(cartData.cart);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// Remove item
|
|
315
|
+
const handleRemoveItem = async (productId, variant) => {
|
|
316
|
+
await store.cart.remove(productId, variant);
|
|
317
|
+
// Refresh cart state
|
|
318
|
+
const cartData = await store.cart.get();
|
|
319
|
+
setCart(cartData.cart);
|
|
320
|
+
};
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Success Modal Pattern
|
|
324
|
+
Replace browser alerts with styled modals:
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
const [successModal, setSuccessModal] = useState({
|
|
328
|
+
show: false,
|
|
329
|
+
trackingNumber: ''
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// After payment verification
|
|
333
|
+
setSuccessModal({
|
|
334
|
+
show: true,
|
|
335
|
+
trackingNumber: orderResult.trackingNumber
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
**Note**: All SDK methods return Promises. Use `async/await` and appropriate `try/catch` blocks for error handling.
|
package/src/status.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/status.js
|
|
2
|
+
import { readTokenStore } from './token-store.js';
|
|
3
|
+
|
|
4
|
+
export async function status() {
|
|
5
|
+
const data = readTokenStore();
|
|
6
|
+
if (!data || Object.keys(data.stores).length === 0) {
|
|
7
|
+
console.error('Not logged in. Run `npx epicmerch-mcp login` to set up OAuth.');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
console.error(`Default store: ${data.defaultStore}`);
|
|
11
|
+
for (const [name, entry] of Object.entries(data.stores)) {
|
|
12
|
+
const accessExp = new Date(entry.accessTokenExpiresAt);
|
|
13
|
+
const refreshExp = new Date(entry.refreshTokenExpiresAt);
|
|
14
|
+
const now = new Date();
|
|
15
|
+
const accessStatus = accessExp > now ? `valid until ${accessExp.toISOString()}` : 'EXPIRED (will refresh)';
|
|
16
|
+
const refreshStatus = refreshExp > now ? `valid until ${refreshExp.toISOString()}` : 'EXPIRED — run `epicmerch-mcp login` again';
|
|
17
|
+
console.error(`\n ${name}${name === data.defaultStore ? ' (default)' : ''}`);
|
|
18
|
+
console.error(` Access token: ${accessStatus}`);
|
|
19
|
+
console.error(` Refresh token: ${refreshStatus}`);
|
|
20
|
+
console.error(` Scope: ${entry.scope}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/token-store.js
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join, dirname } from 'path';
|
|
5
|
+
|
|
6
|
+
export function tokenPath() {
|
|
7
|
+
return join(homedir(), '.epicmerch', 'token.json');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function readTokenStore() {
|
|
11
|
+
const p = tokenPath();
|
|
12
|
+
if (!existsSync(p)) return null;
|
|
13
|
+
let raw;
|
|
14
|
+
try { raw = readFileSync(p, 'utf-8'); }
|
|
15
|
+
catch (e) { throw new Error(`Could not read token file ${p}: ${e.message}`); }
|
|
16
|
+
try { return JSON.parse(raw); }
|
|
17
|
+
catch (e) { throw new Error(`Token file ${p} is corrupt JSON: ${e.message}. Delete it and run 'npx epicmerch-mcp login' again.`); }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function writeTokenStore(data) {
|
|
21
|
+
const p = tokenPath();
|
|
22
|
+
const dir = dirname(p);
|
|
23
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
24
|
+
writeFileSync(p, JSON.stringify(data, null, 2) + '\n');
|
|
25
|
+
try { chmodSync(p, 0o600); } catch (_) { /* Windows no-op */ }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function upsertStore(name, entry) {
|
|
29
|
+
const data = readTokenStore() ?? { version: 1, defaultStore: null, stores: {} };
|
|
30
|
+
data.stores[name] = entry;
|
|
31
|
+
if (!data.defaultStore) data.defaultStore = name;
|
|
32
|
+
writeTokenStore(data);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function removeStore(name) {
|
|
36
|
+
const data = readTokenStore();
|
|
37
|
+
if (!data) return;
|
|
38
|
+
delete data.stores[name];
|
|
39
|
+
if (data.defaultStore === name) {
|
|
40
|
+
const remaining = Object.keys(data.stores);
|
|
41
|
+
data.defaultStore = remaining[0] ?? null;
|
|
42
|
+
}
|
|
43
|
+
writeTokenStore(data);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getActiveEntry() {
|
|
47
|
+
const data = readTokenStore();
|
|
48
|
+
if (!data) return null;
|
|
49
|
+
const targetName = process.env.EPICMERCH_ACTIVE_STORE || data.defaultStore;
|
|
50
|
+
if (!targetName) return null;
|
|
51
|
+
return data.stores[targetName] ?? null;
|
|
52
|
+
}
|